Flow Script Sandbox Threat Model
Scope and security claim
Section titled “Scope and security claim”Custom TypeScript source is untrusted even when a Platform Admin created it.
The platform does not claim that quickjs-emscripten has completed an external,
comprehensive security audit. QuickJS isolation, a dedicated Pod and negative
tests form defense in depth; the production gate therefore includes a security
review in the target cluster.
The sandbox may transform bounded JSON input into bounded JSON output. It may not persist data, create measurement or Action intents, access another tenant, read secrets, contact a network service other than NATS request/reply, or retain state across requests.
Trust boundaries
Section titled “Trust boundaries”Platform API / flow-worker -- checksum + compiled artifact + bounded JSON --> NATS sandbox subject -- success or classified safe failure ---------> reply inboxscript-sandbox-worker Pod -- fresh WASM runtime + context per request ---> untrusted codeThe Platform API and flow worker use requester-only NATS identities. The
sandbox identity may subscribe only to sens.flow.sandbox.execute.v1 and
publish only to reply inboxes. It receives no database, SMTP, tenant or platform
credentials, no service-account token and no writable application volume.
Threats and controls
Section titled “Threats and controls”| Threat | Preventive and detective controls |
|---|---|
| Import or host capability access | TypeScript AST rejects static/dynamic imports and forbidden globals; no QuickJS module loader or host callbacks are registered. |
| Time, randomness or code generation | Date, timers, randomness, eval and Function are rejected statically and removed at runtime. |
| CPU denial of service | Interrupt handler enforces 100 ms execution; caller and NATS deadlines are bounded; timeout is non-retryable. |
| Memory or stack denial of service | 16 MiB runtime memory limit, bounded stack, 256 KiB input/output/artifact limits and fresh runtime lifecycle. |
| Cross-request state | Mutable top-level state and class instances are rejected; a new runtime and context is created for every request. |
| Artifact substitution | Release stores SHA-256; compiled plans pin artifact, manifest and schema checksums; API, worker and sandbox compare them. |
| Data exfiltration | NetworkPolicy permits only NATS egress, the Pod has no secrets, and source/input/output are excluded from logs. |
| Tenant schema forgery | Server allocates custom schema IDs and resolves only built-in, released platform or same-tenant released schemas. Foreign schemas appear absent. |
| Resource-handle leak | Runtime, context, values and interrupt resources are released in success, script-error and host-error paths; repeated failure tests monitor memory. |
Failure classification
Section titled “Failure classification”NATS timeout and sandbox unavailability are technical and retryable. Script
timeout, memory or stack exhaustion, script exceptions, invalid output,
checksum mismatch and contract mismatch are non-retryable. Release returns
structured 422 issues for technical validation failures. An unavailable
sandbox returns 503 and leaves the draft unchanged.
Required evidence before production
Section titled “Required evidence before production”- static negative tests for imports, async functions, global state and every forbidden API;
- runtime tests for infinite loops, memory pressure, stack exhaustion, checksum mismatch and malformed output;
- Pod inspection proving non-root, read-only root filesystem, no service token,
no secrets and
RuntimeDefaultseccomp; - NetworkPolicy test proving all egress except the NATS destination fails;
- NATS permission tests proving requester and sandbox identities cannot access another service contract;
- soak test demonstrating that handles and WASM memory do not accumulate;
- recorded review of the exact
quickjs-emscriptenand compiler versions.
Until this evidence exists for the target cluster,
SENS_FLOW_SANDBOX_REQUESTS_ENABLED and the flow workload remain disabled in
production values.