Skip to main content
Workflows run attacker-influenced inputs by nature: they fetch URLs, shell out, and feed model output into tools. Nika’s position is that the mechanism must be safe before policy makes it configurable β€” the dangerous primitives ship hardened by default, and a violation is a typed NIKA-SEC-* error your workflow can read, not a stack trace.

The threat model in one table

You writeThe attacker controlsThe defense
invoke: nika:fetchthe URL (or a page that redirects)3-layer SSRF defense, on by default
exec:argument values flowing from upstream tasksshell blocklist + data-channel pattern
infer: β†’ tool usethe fetched content the model readstrust levels + spotlight + injection scanning
secrets.* bindingslog accessmasked at the binding layer, never logged

SSRF defense (on by default)

nika:fetch refuses to be bounced into private space. Three layers, verbatim from the engine (nika-http):
1

Static checks (pure)

Scheme allow-list (http/https only), blocked hostnames (localhost, cloud metadata), literal-IP range checks β€” loopback, RFC 1918, link-local/metadata (169.254.169.254), CGN, IPv6 local ranges, v4-mapped v6.
2

DNS resolution check

Non-literal hosts are resolved and every address is range-checked β€” this kills decimal-IP tricks (http://2130706433/) and public names that resolve to private addresses.
3

Per-hop redirect re-check

Client-level redirects are disabled; the engine follows redirects itself and re-runs layers 1+2 on every hop. A public host cannot 302 the client into your VPC.
Response sizes are capped (64 MiB default) and self-signed TLS is rejected by default. Private-network access is opt-in configuration, never the default.

exec: the data-channel rule

exec in shell mode runs every command against a blocklist before spawn. But the structural defense is in how you write workflows β€” tainted data goes through data channels, never code channels:
# ❌ tainted output interpolated into a command string
- id: bad
  exec:
    command: "echo ${{ tasks.fetch_comment.output }}"

# βœ… tainted output passed as data
- id: good
  exec:
    command: tee
    args: ["/tmp/comment.txt"]
    stdin: "${{ tasks.fetch_comment.output }}"
Passing tainted data to a privileged sink without going through a data channel raises a typed taint violation β€” taint is a property of the data, not of who runs the workflow.

Trust levels + prompt injection

Content entering a workflow carries a trust level. Untrusted content (fetched pages, user comments) is tracked as it flows into prompts and tools:
Defense layerError surface
dangerous tool invoked on untrusted data β†’ blockedNIKA-SEC family (security_error)
strict-mode trust-level violationNIKA-SEC family
exfiltration canary token found in outputNIKA-SEC family
prompt-injection scanner (+ ML detection)NIKA-SEC family
untrusted data reached a prompt without spotlight wrappingNIKA-SEC family
workflow recursion depth / self-launch blockedNIKA-SEC-003 (registered)
The three codes the spec REGISTERS today: NIKA-SEC-001 (exec blocklist hit), NIKA-SEC-002 (agent tool outside the whitelist), NIKA-SEC-003 (run-recursion bound). The runtime trust layers above are engine-side (the Shield) β€” they emit within the NIKA-SEC namespace as they land. Every failure is a typed error with a stable code and a transient flag β€” your on_error: can act on it.

Secrets

${{ secrets.* }} is a first-class namespace (vault/env/file-backed). Secret values are masked in logs and traces at the binding layer β€” they never appear in the event stream, and they are not readable back from a task’s recorded output.

Supply-chain hardening (the engine itself)

  • unsafe_code = "forbid" workspace-wide Β· zero .unwrap() in src/ (CI-enforced)
  • cargo deny on every PR Β· cargo audit on every dependency change
  • Sealed kernel traits β€” external code cannot impersonate engine I/O
  • lib tests Β· clippy warnings Β· every crate passes 12 admission gates
  • AGPL-3.0-or-later β€” the source travels with the binary, always auditable

Reporting a vulnerability

security@supernovae.studio β€” please not via public issues. Acknowledgement ≀72h, triage ≀7 days, disclosure ≀90 days. Full policy, disclosure process and the out-of-scope list: SECURITY.md.

See also

Error codes

The NIKA-SEC namespace + every typed failure.

Bindings

Taint, scopes, and the secrets.* namespace.

Builtins β€” fetch

The nika:fetch contract (engines MUST ship SSRF defense).

SECURITY.md

Reporting, disclosure timeline, hall of fame.