---
description: Isolated-vm sandbox worker security policy. Hard rules for anything that lives in the worker child process that runs user code.
paths:
  - "apps/sim/lib/execution/isolated-vm-worker.cjs"
  - "apps/sim/lib/execution/isolated-vm.ts"
  - "apps/sim/lib/execution/sandbox/**"
  - "apps/sim/sandbox-tasks/**"
---

# 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

1. **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 imports `env` directly inside the worker, stop and use a
   host-side broker instead.

2. **Host-side brokers own all credentialed work**. The worker can only access
   resources through `ivm.Reference` / `ivm.Callback` bridges back to the host
   process. Today the only broker is `workspaceFileBroker`
   (`apps/sim/lib/execution/sandbox/brokers/workspace-file.ts`); adding a new
   one requires co-reviewing this file.

3. **Host-side brokers must scope every resource access to a single tenant**.
   The `SandboxBrokerContext` always carries `workspaceId`. Any new broker that
   accesses storage, DB, or an external API must use `ctx.workspaceId` to scope
   the lookup — never accept a raw path, key, or URL from isolate code without
   validation.

4. **Nothing that runs in the isolate is trusted, even if we wrote it**. The
   task `bootstrap` and `finalize` strings in `apps/sim/sandbox-tasks/` execute
   inside the isolate. They must treat `globalThis` as adversarial — no pulling
   values from it that might have been mutated by user code. The hardening
   script in `executeTask` undefines 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 reads `process.env` of 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-vm` module
- Node built-ins: `node:fs` (only for reading the checked-in bundle `.cjs`
  files) and `node: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.
