From debd645f16383f2f7006c5cd92f7120930a70fcf Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Wed, 20 May 2026 23:25:39 -0700 Subject: [PATCH] docs(ses): document Compartment availability and OOM limits (#2742) Adds a Limitations section to docs/lockdown.md describing three classes of threat that a Compartment cannot mitigate, all intrinsic to running multiple compartments inside a single JavaScript agent. Availability: every compartment shares the agent's single thread, so a guest that enters a non-terminating synchronous computation (an infinite loop, runaway recursion, or any other unresponsive synchronous call, including via a property accessor, proxy trap, or synchronously-resolved `then` callback) denies progress to every sibling and to the start compartment until the engine intervenes. Host code that wants to remain available must interact with guest objects across an asynchronous boundary, or run the guest in a separate worker or process where it can be terminated. Memory exhaustion: the ECMAScript specification does not require an engine to abort the agent on OOM (see the TC39 OOM-fails-fast proposal), so a malicious compartment can intentionally exhaust memory and surface a `RangeError` or other allocation failure inside an unrelated synchronous call, including one into an object exported from another compartment. The victim is then left in a partially updated state, turning the availability concern into an integrity concern on otherwise trusted exports. Timing side-channels: the default Compartment deliberately excludes `Date`, `performance.now`, `setTimeout`, `setImmediate`, and every other host scheduling primitive, but any asynchronous boundary the guest can drive (another worker, another process, a network peer, a host-supplied promise resolved on a host-driven schedule) recovers wall-clock duration from the round-trip. Defeating timing side-channels therefore requires auditing every reachable async boundary, not just enumerating timer-like APIs; the host's mitigation menu includes running the suspect guest in a separate worker or process, throttling outbound message rate, and padding guest-visible message delivery so the round-trip distribution conveys less about host-internal events. For the broader threat catalog, the section cites Agoric's Taxonomy of Security Issues paper. The new section is cross-linked from packages/ses/README.md's Compartment section so readers reach it from the place Compartment is introduced. Fixes #2742 --- docs/lockdown.md | 89 ++++++++++++++++++++++++++++++++++++++++++ packages/ses/README.md | 4 ++ 2 files changed, 93 insertions(+) diff --git a/docs/lockdown.md b/docs/lockdown.md index 1a76278d9f..d3c851cfe7 100644 --- a/docs/lockdown.md +++ b/docs/lockdown.md @@ -1218,3 +1218,92 @@ The "`__`" in the option name indicates that this option is temporary. XS now has a fast native `harden`, but SwingSet currently runs on node/v8, which does not. If node/v8 ever implements a fast native `harden`, we hope to deprecate and eventually remove this option. + +# Limitations + +Compartments isolate the *authority* and *intrinsics* available to guest code, +but every compartment in a realm shares the same JavaScript *agent*. +In engine terms, that is a single thread of execution and a single heap. +The following limitations are intrinsic to running JavaScript in one agent and +are not threats a `Compartment` can mitigate. +A host that needs to defend against them must impose a coarser boundary, +as a separate worker or process around the +suspect code. + +## Availability + +A compartment cannot protect availability for code running in sibling +compartments or in the start compartment. +Because all compartments share the agent's single thread, a guest that enters +an infinite loop (such as `for (;;) {}`), a runaway recursion, or any other +non-terminating synchronous computation denies synchronous progress to every +other compartment until the engine itself intervenes (for example, by hitting +an engine-imposed call-stack limit or by the host terminating the worker or +process). + +This applies even to ostensibly cooperative code. +A guest method invoked synchronously by host code can refuse to return. +This includes calls through a property accessor, a proxy trap, a `then` +callback resolved synchronously, or any other synchronous call. +Host code that wishes to remain available in the face of an unresponsive guest +must arrange to interact with guest objects across an asynchronous boundary +(for example, by using promises and a watchdog timer in a separate agent), or +must run the guest in a separate worker or process where the host can +terminate it. + +## Memory exhaustion + +A compartment cannot protect the integrity of its exports against an +out-of-memory (OOM) attack mounted by another compartment in the same agent. +The ECMAScript specification does not require an engine to abort the agent on +OOM; see the TC39 +[OOM-fails-fast proposal](https://github.com/tc39/proposal-oom-fails-fast) +for the current state of efforts to standardize that behavior. +In its absence, a malicious compartment can intentionally allocate stack +frames or heap objects until the engine raises a `RangeError` or other +allocation failure at an arbitrary point in unrelated code. + +The failure can surface inside an unrelated synchronous call, including a +call into an object exported from another compartment. +The victim object may then be left in a partially updated state. +The result is an *integrity* compromise (broken invariants on otherwise +trusted objects) on top of the *availability* compromise (the agent or +process may exit). +Hosts that need to defend exported objects against this class of attack +should run untrusted guests in a separate agent (such as a worker or +subprocess) with its own heap, and treat any synchronous call into a guest as +potentially failure-inducing. + +## Timing side-channels + +A compartment cannot prevent a guest from measuring durations. +The default `Compartment` deliberately excludes every standard timer: +`Date` is not in the global scope, `performance.now` is unavailable, and +neither `setTimeout`, `setImmediate`, nor any other host scheduling +primitive is exposed. +Removing the obvious clocks is necessary but not sufficient. + +A guest with the ability to make asynchronous calls to any other agent +(another worker, another process, a network peer, or even a host-supplied +promise that resolves on a host-driven schedule) can recover wall-clock +duration from the round-trip. +Two consecutive round-trips to the same correspondent yield a coarse +interval, and many such samples yield a usable timer at the resolution of +the round-trip jitter. +Any asynchronous boundary that the guest can drive at will is therefore a +potential timing source, regardless of whether the host intended it as one. + +Confined code that needs to defeat timing side-channels requires a careful +audit of every async boundary the guest can reach, not just an enumeration +of timer-like APIs in the global scope. +The mitigations available to a host include running the suspect guest in a +separate worker or process so that the timing channel does not leak into +the host's own heap, throttling the rate at which the guest can send +outbound messages, and padding the timing of guest-visible message delivery +so that the round-trip distribution conveys less information about +host-internal events. + +For a fuller threat catalog of side-channels and other JavaScript-confinement +risks, see Agoric's +[Taxonomy of Security Issues](https://papers.agoric.com/taxonomy-of-security-issues/), +which surveys the broader space this section samples. diff --git a/packages/ses/README.md b/packages/ses/README.md index a76779c6a1..0779c316d9 100644 --- a/packages/ses/README.md +++ b/packages/ses/README.md @@ -224,6 +224,10 @@ const powerfulCompartment = new Compartment({ powerfulCompartment.globalThis.Date = Date; ``` +Because every compartment shares one JavaScript agent, see +[Limitations](../../docs/lockdown.md#limitations) for the availability and +memory-exhaustion threats a `Compartment` cannot mitigate. + ### Compartment + Lockdown Together, Compartment and lockdown isolate client code in an environment with