The security model

How Cruft runs code you do not fully trust, a dependency, a plugin, an LLM's output, in the same process as code you do. It covers the three isolation scales, compartments, the sealed-mode CLI flags, and the agent sandbox, what each one enforces today, and where the alpha's coverage is still young.

The problem is running code you don't fully trust in the same process as code you do: a dependency pulled through npm, a plugin, a tenant's script, an LLM's output. Node has no boundary for it. node_modules code runs with your process's full authority: your fs, your network, your process.env, your child processes. The usual fix pushes the untrusted unit into its own process or container and pays for the boundary in latency and operational weight.

Cruft draws the trust boundary inside one process, cheaply, because it owns the whole engine. This page is the account of where that boundary is real today, what it enforces, and where it is young. If you are deciding whether to put Cruft in front of a real security boundary, read the residuals as carefully as the guarantees.

In short: Cruft's in-process isolation (compartments, capabilities, the agent sandbox, workers) is real and enforced; the sealed modes gate the outbound network and directory enumeration as well as the filesystem, exec, env, and process families; and TLS peer authentication validates the chain, hostname, and expiry. What remains is a maturity gap: alpha code with narrow interop and fuzz coverage. The model itself has no open hole.

What Cruft defends against

The threat is specific and modern: you run code you don't fully trust, in the same process as code you do. A dependency, a plugin, a tenant script, a model's output. Cruft draws the line between "your code" and "everything else" and enforces it per-caller, without a second process.

It does not defend against a local attacker with timing side-channels: the bignum and elliptic-curve arithmetic is correctness-first and not constant-time in places. Treat key material as leaking timing under a local-attacker model.

What each mechanism enforces

The table is the current state. "Enforced" means the runtime blocks the bypass; "out of scope" means the mechanism does not claim to cover it.

MechanismStatusWhat it enforces
Compartment realm isolationenforcedCode in a new Compartment() reaches only its granted globals. No ambient process/fetch/require; the Function-constructor trick does not reach host authority; Object.prototype pollution does not cross to the host or a sibling compartment. The realm still carries the standard intrinsics (Object, Array, JSON, Math, and so on), so ordinary code runs.
Compartment timeout_msenforcedAn uncatchable wall-clock budget, including a bare while(true){}, which a try/catch cannot swallow. A 150 ms budget interrupts at roughly 155 ms.
--sealed-depsenforcednode_modules code is denied I/O per-caller while your app code runs: a dependency's read of /etc/hostname is blocked while your app's own read succeeds.
--auditenforcedEvery I/O capability use is recorded together with the module that caused it.
Agent compartmentenforcedFail-closed tools and modules, per-turn budgets, a post-turn tamper check, and a full audit log (see agent).
Worker isolationenforcedNo cross-worker mutable object references; only explicit shared bytes and immutable handles cross, and node:worker_threads messages round-trip through the structured clone.
Capability attributionenforcedEvery I/O op carries its module's provenance: app, dependency, or builtin.
--sealed (fs, exec, env, process)enforcedDeny-by-default: readFileSync/openSync/child_process/process.env/os are denied without a grant.
--sealed (network)enforcedOutbound network is gated: the net families (fetch/http/tls/dns/dgram) route through the capability check, so a sealed program with no network grant is denied its outbound connection.
--sealed (directory enumeration)enforcedopendirSync/opendir/globSync pass through the filesystem list check, so directory listing is denied under the fs seal just as readFileSync is.
--sealed (stdio grant)enforcedThe stdio grant authorizes a directly-run sealed script, so a top-level sealed program can be granted stdout/stderr through the documented mechanism.
TLS peer authenticationenforcedSignature chain, hostname/SAN, and expiry are checked, and CA-constraint enforcement (BasicConstraints, KeyUsage, pathLen, NameConstraints, unknown-critical rejection) lands through the typed x509 decoder, so a leaf certificate reachable to a root can no longer act as an issuer.
Windows CSPRNGenforcedBCryptGenRandom is wired under Windows with a loud failure and no weak fallback on any platform.
CruftScript primitive-type soundnessenforcedA wrong-typed primitive crossing a .fts function boundary is rejected at runtime rather than silently coerced.
Native addons (.node)out of scopeArbitrary in-process machine code, not gated by capabilities or compartments (see native addons).
Timing side-channel resistanceout of scopeBignum and EC arithmetic is not constant-time.

The three isolation scales

Cruft offers isolation at three scales, and their guarantees differ.

1. In-program: the Compartment

A Compartment is a fresh realm with an empty global scope. This is the strongest boundary Cruft offers, because it is structural: the authority available inside is exactly what you place in globals, and there is no ambient authority to escape to, nothing to misconfigure. Inside a bare compartment typeof process is undefined, the Function-constructor trick does not reach host functions, Object.prototype pollution does not cross to the host or a sibling compartment, and a bare while(true){} is stopped by the uncatchable timeout_ms. This is the right tool for plugins, tenant code, and model output. Details: compartments and capabilities.

2. Whole-process: the sandbox flags

The CLI flags constrain the process from the outside. By default they enforce the filesystem, exec, env, process, network, and directory-enumeration families: a sealed or sealed-deps program is denied readFileSync/openSync/child_process/process.env/os, its outbound connections, and its directory listings, all without a grant. So --sealed-deps removes a compromised dependency's authority over every one of those families, per-caller, and --audit records what is actually used.

Outbound network once routed around the capability check; it now goes through it, so a sealed program or a sealed dependency with no network grant cannot connect. Directory enumeration is closed the same way. And a directly-run sealed script can be granted stdout/stderr through the stdio grant, which earlier top-level sealed programs could not reach.

3. The agent compartment

cruft agent productizes the compartment for untrusted, typically LLM-authored, code: fail-closed tools and modules, per-turn budgets on events, bytes, steps, memory, and time, a post-turn tamper check, and a complete replayable audit log. This is a genuine, enforced sandbox. Two controls are not available yet: full process-tool supervision and a full worker membrane. See the agent page.

The capability model underneath

All three scales sit on one capability system. Every I/O operation is attributed to a module identity with provenance, and each capability family (filesystem with path policies, stdio, clock with coarsenable resolution, scheduler, network) is a typed grant rather than a boolean flag. That is what lets --sealed-deps decide per-caller (app vs. dependency) rather than by guessing at paths, and what lets a compartment be handed a coarsened clock to blunt timing side-channels. Details: compartments and capabilities.

Limitations

The isolation and grant mechanisms above are enforced. What remains is maturity and two narrow edges, worth knowing before you deploy:

  • rejectUnauthorized: false is thread-global, not per-connection. Disabling TLS validation for one connection can leak to concurrent ones on the same thread. Do not disable validation selectively while other connections in the same program depend on it.
  • The crypto is not constant-time. It is correctness-first; assume it leaks timing for key material under a local-attacker model. Do not rely on it where a local attacker can measure your process.
  • Coverage is young. This is alpha (0.0.10). The isolation is tested against adversarial escape attempts and the TLS path against a deterministic handshake, but interop against other stacks (for example OpenSSL) and fuzz coverage are narrow. The Windows CSPRNG is wired and fails loud, but was validated on a non-Windows host, so treat the Windows path as correct-by-construction rather than run-in-anger. Don't put the TLS client on an actively hostile network yet.

What to lean on today

For a real deployment, the controls that are enforced and ready:

  1. --sealed-deps for supply-chain defense: the single highest-value, zero-config control, since it strips a compromised dependency's authority without touching your app code.
  2. Compartments with globals grants and timeout_ms for running untrusted code regions in-process.
  3. cruft agent for LLM and agent execution, with its audit log as the record.
  4. --audit to learn any program's real footprint before you tighten the grants.