4.2 KiB
description, paths
| description | paths | ||||
|---|---|---|---|---|---|
| Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code. |
|
Sim Sandbox — Worker Security Policy
The isolated-vm worker child process at
apps/sim/lib/execution/isolated-vm-worker.cjs runs untrusted user code inside
V8 isolates. The process itself is a trust boundary. Everything in this rule is
about what must never live in that process.
Hard rules
-
No app credentials in the worker process. The worker must not hold, load, or receive via IPC: database URLs, Redis URLs, AWS keys, Stripe keys, session-signing keys, encryption keys, OAuth client secrets, internal API secrets, or any LLM / email / search provider API keys. If you catch yourself
require'ing@/lib/auth,@sim/db,@/lib/uploads/core/storage-service, or anything that importsenvdirectly inside the worker, stop and use a host-side broker instead. -
Host-side brokers own all credentialed work. The worker can only access resources through
ivm.Reference/ivm.Callbackbridges back to the host process. Today the only broker isworkspaceFileBroker(apps/sim/lib/execution/sandbox/brokers/workspace-file.ts); adding a new one requires co-reviewing this file. -
Host-side brokers must scope every resource access to a single tenant. The
SandboxBrokerContextalways carriesworkspaceId. Any new broker that accesses storage, DB, or an external API must usectx.workspaceIdto scope the lookup — never accept a raw path, key, or URL from isolate code without validation. -
Nothing that runs in the isolate is trusted, even if we wrote it. The task
bootstrapandfinalizestrings inapps/sim/sandbox-tasks/execute inside the isolate. They must treatglobalThisas adversarial — no pulling values from it that might have been mutated by user code. The hardening script inexecuteTaskundefines dangerous globals before user code runs.
Why
A V8 JIT bug (Chrome ships these roughly monthly) gives an attacker a native
code primitive inside the process that owns whatever that process can reach.
If the worker only holds isolated-vm + a single narrow workspace-file broker,
a V8 escape leaks one tenant's files. If the worker holds a Stripe key or a DB
connection, a V8 escape leaks the service.
The original doc-worker.cjs vulnerability (CVE-class, 225 production secrets
leaked via /proc/1/environ) was the forcing function for this architecture.
Keep the blast radius small.
Checklist for changes to isolated-vm-worker.cjs
Before landing any change that adds a new require(...) or process.send(...)
payload or ivm.Reference wrapper in the worker:
- Does it load a credential, key, connection string, or secret? If yes, move it host-side and expose as a broker.
- Does it import from
@/lib/auth,@sim/db,@/lib/uploads/core/*,@/lib/core/config/env, or any module that readsprocess.envof the main app? If yes, same — move host-side. - Does it expose a resource that's workspace-scoped without taking a
workspaceId? If yes, re-scope. - Did you update the broker limits (
IVM_MAX_BROKER_ARGS_JSON_CHARS,IVM_MAX_BROKER_RESULT_JSON_CHARS,IVM_MAX_BROKERS_PER_EXECUTION) if the new broker can emit large payloads or fire frequently?
What the worker may hold
isolated-vmmodule- Node built-ins:
node:fs(only for reading the checked-in bundle.cjsfiles) andnode:path - The three prebuilt library bundles under
apps/sim/lib/execution/sandbox/bundles/*.cjs - IPC message handlers for
execute,cancel,fetchResponse,brokerResponse
The worker deliberately has no host-side logger. All errors and
diagnostics flow through IPC back to the host, which has @sim/logger. Do
not add createLogger or console-based logging to the worker — it would
require pulling the main app's config / env, which is exactly what this
rule is preventing.
Anything else is suspect.