The V8 sandbox
V8 is a high-value attack target: a memory-safety bug in the engine can become a full exploit of the host process (a browser tab, a server). The V8 sandbox is a defense-in-depth mechanism that assumes an attacker can corrupt memory inside V8 and tries to stop that corruption from reaching the rest of the process. This page is a high-level orientation, not an exploitation guide.
::: info Ubiquitous language Sandbox: a reserved virtual-address region V8 places its objects in. Sandboxed pointer: an intra-sandbox reference stored as an offset, not a raw pointer. External pointer table: an indirection table for references to memory outside the sandbox. :::
The threat model
The sandbox starts from a humbling premise, stated in the header:
// When enabled, V8 reserves a large region of virtual address space - the
// sandbox - and places most of its objects inside of it. It is then assumed
// that an attacker can, by exploiting a vulnerability in V8, corrupt memory
// inside the sandbox arbitrarily and from different threads. The sandbox
// attempts to stop an attacker from corrupting other memory in the process.
— src/sandbox/sandbox.h#L23-L150
In other words: V8 does not claim its bugs are unreachable. It accepts that type-confusion and out-of-bounds bugs happen, and builds a wall so that their blast radius is limited to the sandbox region instead of the whole address space.
The layout
+- ~~~ -+---------------------------------------- ~~~ -+- ~~~ -+
| 32 GB | (Ideally) 1 TB | 32 GB |
| Guard | 4 GB : ArrayBuffer backing stores, | Guard |
| Region | V8 Heap : WASM memory, other sandboxed | Region |
| (front) | Region : objects | (back) |
+- ~~~ -+----------------+----------------------- ~~~ -+- ~~~ -+
— layout comment in src/sandbox/sandbox.h
The pointer-compression cage — the 4 GB region holding most V8 objects — sits at the front of the sandbox. The rest holds large buffers (ArrayBuffers, WASM memory). Large guard regions flank it so that an out-of-bounds access near the edges faults instead of hitting valid memory.
How it contains corruption
Two indirection mechanisms do the heavy lifting:
Sandboxed (compressed) pointers. Object-to-object references inside the cage are 32-bit offsets from the cage base (see pointer compression). A corrupted reference can therefore only ever name an address within the 4 GB cage — it is structurally incapable of pointing at arbitrary process memory. Memory efficiency and security come from the same design.
External pointer table. References to memory outside the sandbox (host C++ objects, native callbacks) are not stored as raw pointers inside sandboxed objects. They are stored as indices into a per-isolate external pointer table; the real pointer lives in the table, which is outside the sandbox. A corrupted index just selects a different (type-checked) table slot — it cannot forge an arbitrary external pointer.
The reserved Smi address range is also kept inaccessible to help catch Smi↔HeapObject confusion:
bool smi_address_range_is_inaccessible() const {
return smi_address_range_reserved_;
}
What it is and isn't
It is an in-process mitigation: it raises the bar from "memory bug → process compromise" to "memory bug → contained corruption that still needs a second bug to escape."
It is not a substitute for the OS process sandbox (site isolation) or for fixing the underlying bugs. It is one layer of defense in depth.
It is gated behind
V8_ENABLE_SANDBOXand interacts with pointer compression (the cage is its core).
::: tip Why a language-runtime author should care The sandbox is a case study in designing for the inevitability of bugs. Rather than only trying to be bug-free, V8 reshapes its pointer representations so that the common corruption primitives are structurally weaker. The lesson — choose representations (offsets, table indices) that bound an attacker's reach — applies to any runtime that runs untrusted input. :::
See also
Pointer compression — the cage the sandbox is built around.
Tagged values & Smis — the Smi/pointer distinction the sandbox hardens.
Isolates — the external pointer table is per-isolate.