NIKA-SEC-* error your workflow can read, not a stack trace.
The threat model in one table
| You write | The attacker controls | The defense |
|---|---|---|
invoke: nika:fetch | the URL (or a page that redirects) | 3-layer SSRF defense, on by default |
exec: | argument values flowing from upstream tasks | shell blocklist + data-channel pattern |
infer: β tool use | the fetched content the model reads | trust levels + spotlight + injection scanning |
secrets.* bindings | log access | masked 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):
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.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.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:
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 layer | Error surface |
|---|---|
| dangerous tool invoked on untrusted data β blocked | NIKA-SEC family (security_error) |
| strict-mode trust-level violation | NIKA-SEC family |
| exfiltration canary token found in output | NIKA-SEC family |
| prompt-injection scanner (+ ML detection) | NIKA-SEC family |
| untrusted data reached a prompt without spotlight wrapping | NIKA-SEC family |
| workflow recursion depth / self-launch blocked | NIKA-SEC-003 (registered) |
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()insrc/(CI-enforced)cargo denyon every PR Β·cargo auditon 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.