--- slug: v8-heap-sandbox type: decision summary: "The decision to isolate V8's JavaScript heap in a one-terabyte address region using 40-bit offsets instead of native pointers, so heap arbitrary read/write can't reach host memory. Default-on in Chrome 123, March 2024." created: 2026-05-12 updated: 2026-05-16 last_link_verified: 2026-05-13 related: exploit-chain: relation: refined-by note: "The V8 heap sandbox bypass is the second link of the canonical three-link Chromium exploit chain; the chain's vocabulary depends on the decision this entry names." escape-chain: relation: complements note: "The Sandbox Escape Chain concept names the broader three-vulnerability requirement under the trust-model framing; the V8 heap sandbox is one of the chain's links viewed under the security-response framing." browser-renderer-split: relation: enables note: "The privilege gradient between renderer and browser is what makes a heap-level containment mechanism inside the renderer worth its pointer-indirection cost." untrusted-renderer-axiom: relation: enforces note: "Containment of in-renderer arbitrary read/write makes the axiom's claim that 'renderer state is hostile' survive a JIT bug." multi-process-architecture: relation: builds-on note: "The 2008 multi-process decision is the substrate the heap sandbox sits on; the heap sandbox extends the boundary structure inward from the OS process to the JavaScript heap." stateless-ipc-interface: relation: complements note: "Stateless IPC Interface defends the renderer-to-browser boundary; the V8 heap sandbox defends the renderer-internal boundary that has to fail before the IPC boundary is exercised." ipc-integer-discipline: relation: complements note: "Both are pointer-and-integer hygiene mechanisms resisting the same vulnerability class; the discipline guards the IPC boundary, the heap sandbox guards the in-process boundary." embargoed-disclosure: relation: related note: "The heap sandbox shortens the average embargo by reducing the blast radius any single V8 bug carries: a contained bug is one link, not a chain." vrp-bug-bounty: relation: related note: "The Vulnerability Rewards Program treats the heap sandbox bypass as a separately payable link of a full chain, which calibrates the bounty structure around the containment the decision establishes." site-isolation: relation: complements note: "Site Isolation is the cross-process boundary defense; the heap sandbox is the in-process boundary defense that complements it for the renderer's most exposed component." mseal-forward-cfi: relation: complements note: "The CFI program defeats the control-hijack step a successful heap-sandbox bypass would otherwise convert into attacker-chosen code execution. The Alternatives Considered table here dismisses 'CFI and ACG only' in isolation; the project's actual strategy is the heap sandbox plus the CFI program, not one or the other." --- # V8 Heap Sandbox > **Decision** > > A one-time architectural or governance choice whose consequences still govern current work. *The decision to isolate V8's JavaScript heap inside a reserved one-terabyte virtual address region using 40-bit offsets in place of native 64-bit pointers, so that an attacker who achieves arbitrary read/write inside the heap can't directly reach host memory. Enabled by default in Chrome 123 in March 2024.* > "The V8 Sandbox is a software-based sandbox for the JavaScript and WebAssembly engine. The goal is to limit the damage that an attacker who has gained code execution inside V8 can do." > — Samuel Groß, "The V8 Sandbox" blog post, v8.dev, April 2024 ## Decision Statement The V8 team chose to contain a class of V8-internal vulnerabilities by isolating the JavaScript heap inside a reserved one-terabyte virtual address space, replacing native 64-bit pointers with 40-bit `sandbox_ptr_t` offsets for every intra-heap reference and routing every reference that crosses the heap boundary through a guarded `external_ptr_t` indirection table. The design assumes the attacker has already achieved arbitrary read/write inside V8's heap through a JIT logic bug and confines that capability so it can't be directly turned into corruption of host process memory. ## Context By the early 2020s, V8 had been the single largest source of high-severity Chromium vulnerabilities for years. The JIT compilers (TurboFan, then Maglev, then Sparkplug) emit machine code that's correct by construction only when the optimizer's type assumptions hold; a single mistaken type-inference decision is a memory-corruption primitive. Public Project Zero writeups and the Chrome security bug tracker show that the rate at which new V8 bugs of this shape arrive is essentially constant. The optimizer is too large and too fast-moving to ever be empirically bug-free, and a memory-safe rewrite at the optimizer level was prohibitive on any near-term schedule. The V8 team accepted that conclusion explicitly. Rather than try to eliminate the bug class, they reframed the problem: assume the attacker has won inside the V8 heap, then make sure that win doesn't directly compromise the surrounding renderer process. The design work began in 2021 under the codename "V8 Sandbox" or "Heap Sandbox," shipped behind a build flag in late 2023, and switched to the default enabled state in Chrome 123 in March 2024. The decision rides on top of the [*Multi-Process Architecture*](multi-process-architecture.md) decision from 2008 and the [*Browser-Renderer Privilege Split*](browser-renderer-split.md) it produced: the heap sandbox would be far less interesting if a renderer compromise were already a host compromise, and far more interesting because it isn't. ## Alternatives Considered | Alternative | Description | Reason rejected | |---|---|---| | Memory-safe rewrite of the JIT compilers | Replace TurboFan, Maglev, and Sparkplug with implementations in a memory-safe language, or with verified C++ subsets enforced by tooling. | Schedule and scope. The optimizers are hundreds of thousands of lines of fast-moving code with a long tail of architecture-specific paths. Even an optimistic rewrite estimate ran years, and the bug rate during the transition would dominate. Memory-safe rewrites of selected V8 components remain on the long-term roadmap but were not the right tool for the contained timeline. | | CFI and ACG only (no in-heap containment) | Rely entirely on Control-Flow Integrity, Arbitrary Code Guard, and OS-level mitigations to defeat the post-corruption stage of an exploit. | These mitigations defeat code-execution corruptions but not data-only corruptions. An attacker who can read and write any byte in the V8 heap, but can't yet hijack control flow, has many paths to escalate that don't require new executable code: rewrite internal object fields, swap function pointers between trusted call sites, corrupt the JIT-compiler's data structures to influence the next compilation. CFI and ACG miss most of these. | | Process-per-Origin V8 isolates | Run each origin's V8 in its own renderer process so that one origin's heap-corruption bug can't reach another origin's data. | Site Isolation already does this for the cross-origin case; the heap sandbox addresses a different threat: corruption inside one origin's V8 reaching the renderer's non-V8 memory (Blink layout objects, Mojo handles, decoded image buffers). Process granularity is the wrong axis. | | Hardware memory tagging | Use ARM Memory Tagging Extension or Intel LAM to tag the V8 heap so that pointers outside the heap can't dereference inside it. | Hardware support wasn't and isn't universal across the renderer-process target hardware; Chrome ships on a heterogeneous device base where any defense conditioned on a hardware feature still needs a software fallback. The fallback would have to be the heap sandbox anyway. | | Software-enforced heap cage with 40-bit offsets (chosen) | Reserve a one-terabyte virtual address region as the heap cage; rewrite intra-heap references as 40-bit offsets so a 64-bit pointer dereferenced inside the heap can only land inside the heap; route external references through a guarded table indexed by handle, not addressed by pointer. | Deployable on the existing hardware base, paid for in pointer-indirection cost and 40-bit address-range constraint rather than in process count or scheduling cost, and complete enough that the threat model (attacker has full read/write inside the heap) is meaningful to reason about. | The alternative-elimination logic above paraphrases the V8 Sandbox design document, the v8.dev blog post that introduced the design to a broader audience, and the Project Zero series on V8 sandbox bypasses. ## Rationale Three properties of the chosen alternative carried the decision. **The boundary is a value transformation, not a check.** Every intra-heap reference is stored as a 40-bit offset from the heap base, not as a 64-bit pointer. A `sandbox_ptr_t` whose value an attacker has corrupted still gets dereferenced inside the one-terabyte cage, because the high 24 bits of the resulting address are fixed at the cage's base; the corrupted value can't address memory outside the cage no matter what bits the attacker writes into it. That's strictly stronger than a bounds check on every dereference, because there's no check to forget and no fast path that skips one. **External references go through a handle table, not a pointer.** When V8 needs to refer to something outside the heap (a C++ object, a Mojo handle, a Wasm module's compiled code), the reference is an integer index into a guarded `external_ptr_t` table held at a known address. The table's slots carry the actual pointer plus a type tag; the consumer checks the tag before using the pointer. An attacker who controls a slot's index controls which entry in the table they reach, but the table's entries are populated only by V8 internals and the type-tag check refuses mis-typed dereferences. Corrupting the index doesn't produce a forged pointer; at worst it produces a wrong-but-valid reference to another typed entry. **The threat model is honest about what the boundary doesn't catch.** The design assumes the attacker has already won inside the heap. It doesn't try to defend against the JIT bug; it defends the rest of the renderer from it. That honesty is what made the design tractable: the heap sandbox isn't a sandbox in the OS sense (it doesn't deny syscalls; it doesn't enforce a process boundary). It's an in-process containment mechanism with a precise effect. Any V8 bug that could once read or write the entire renderer can now read or write the V8 heap and nothing else, modulo bypasses. The bypass class is real and tracked under its own bug category, so the project knows what it's asking the boundary to do. The costs were judged acceptable: pointer indirection on intra-heap accesses pays a small per-operation overhead, the 40-bit address-range cap bounds the maximum heap size to roughly one terabyte (orders of magnitude beyond any realistic workload), and the handle-table indirection adds a load to every external reference. Internal microbenchmarks reported in the launch blog post showed single-digit-percent slowdowns on the JavaScript benchmark suite, with no measurable impact on real-world page-load metrics. ## Ongoing Consequences The decision rewrites what "a V8 type-confusion bug" can do. For security response, V8 vulnerabilities are now graded against the sandbox boundary. A bug that produces read/write inside the heap is a high-severity bug, not a critical one, because it can't directly compromise the renderer process; the attacker still needs a separate heap-sandbox bypass. The Chromium severity guidelines were updated to reflect this distinction, and the Vulnerability Rewards Program now pays a separately-tracked bounty for heap-sandbox bypasses on the order of $20,000 to $30,000 depending on the bypass's reliability. The [*Exploit Chain Anatomy*](exploit-chain.md) concept treats the heap-sandbox bypass as the canonical second link of a three-link chain. The [*Sandbox Escape Chain*](escape-chain.md) concept names the same structure from the trust-model side. For V8 contributors and reviewers, the constraint is direct. Code that runs inside the V8 heap cannot use a `T*` for any intra-heap reference; the type system enforces `sandbox_ptr_t` for those slots, and a contributor who pattern-matches "store a pointer here" onto a raw pointer field has written code that won't compile. External references must go through the handle table; reaching for a C++ object pointer by address is a category error. The discipline shows up in every patch that touches the heap layout and is one of the standing review questions API owners ask when a Mojo interface exposes V8 internals to other parts of the renderer. For Chromium-based-product engineers, the consequence is a sharper threat model. A CVE reading "V8 type confusion, High" no longer means "one click to host compromise." It means "one link of a chain, and there are at least two more the attacker still has to find." Downstream vendors evaluating their patch posture can use this to calibrate which CVEs warrant emergency releases and which can ride the normal cycle. The shift is well-documented enough that the [*Embargoed Disclosure*](embargoed-disclosure.md) timeline reflects it: bugs the heap sandbox contains tend to get shorter embargoes than bugs that bypass it. For AI coding agents working in or near the V8 heap, the consequence is a hard rule the agent's training data doesn't carry. Generating C++ that stores a `MyType*` in a heap-resident slot and expecting it to round-trip through GC is generating code that will fail to compile in the modern V8 tree, and the diagnostic the compiler emits names the right type but doesn't explain why. The constraint has to come from the agent's harness, because it can't be discovered from generic C++ knowledge. The decision also reshapes how the project talks about renderer compromise. Before the heap sandbox, a "renderer is fully compromised" outcome was the assumed result of any V8 remote-code-execution bug. After it, the assumed result is "V8 heap is fully compromised, and the rest of the renderer is still standing modulo bypasses." The reframing shows up in the trust-model documentation, the severity guidelines, and the way both the project and downstream vendors describe their security posture. ## Reversal Conditions The decision is effectively permanent. Three things would have to be true for it to be revisited. A memory-safe V8 would have to ship at parity. If a future V8 (in Rust, in a verified C++ subset, or in some not-yet-named language) reached production with no measurable JIT-class bug rate, the in-heap containment would lose its purpose. Selected components of V8 are being incrementally rewritten in memory-safe languages; none currently approaches the JIT-compiler footprint at which the heap sandbox would be the wrong tool. The cost profile would have to invert. Two scenarios qualify. A future workload that reads every heap pointer once per millisecond would grow the pointer-indirection cost into a material drag. A future workload that needs more than a terabyte of JavaScript heap would make the 40-bit cap binding. Either would put pressure on the project to widen the cage or to abandon the offset scheme. Neither is on the radar. The threat model would have to dissolve. If the JIT-compiler bug class went away (perhaps through formal verification of optimization passes), the heap sandbox's reason for existing would weaken. Verification work on selected passes exists; nothing that approaches whole-optimizer coverage does. None of these conditions is close. The project treats the heap sandbox as a permanent feature of V8's architecture, and reasoning that assumes the cage holds is the default register for design discussion in V8 and the renderer code that touches it. ## Notes for Agent Context An AI coding agent generating C++ that touches the V8 heap must hold the cage model explicitly. Inside the heap (any code under `v8/src/objects/`, `v8/src/heap/`, or a `Tagged` slot), use the in-heap reference type the V8 type system prescribes: never a raw `T*` for an intra-heap reference and never a `uintptr_t` that the surrounding code dereferences as if it pointed to anywhere outside the cage. A `sandbox_ptr_t` value isn't a pointer; it's a 40-bit offset that gets resolved against the cage base on dereference, and the agent must not perform pointer arithmetic against it as if it were a raw address. For any reference that crosses the heap boundary (a callback into Blink, a Mojo handle, a compiled Wasm module), route it through the `external_ptr_t` handle table; don't store a host pointer directly in a heap-resident field and don't bypass the table by casting an `ExternalReference` to a `void*` and storing it. When asked whether a particular value is "just a pointer," check the type. The compiler's diagnostic is the primary signal, and the agent should treat a type-mismatch error in `v8/src/objects/` as a category error to fix at the type level, not by inserting a cast. Never propose a design that "stores a C++ object pointer directly in the heap to avoid the handle-table lookup"; that proposal violates the architecture this decision established. ## Sources The canonical primary source is the V8 team's own design documentation: the V8 Sandbox design document inside the V8 source tree, which sets out the threat model, the in-heap pointer transformation, and the external-reference handle-table mechanism in the form V8 contributors review against. The 2024 v8.dev blog post by Samuel Groß ("The V8 Sandbox") is the public introduction to the design and the first place outside the V8 tree where the decision was framed for a broader audience; it states the assumed-attacker model in the form quoted in the epigraph. The Chrome 123 launch announcement on the Chromium blog records the default-enable event in March 2024 and the public severity-reclassification that followed. Project Zero's V8 Sandbox series (blog posts on early bypass research) documents the bypass class the design treats as a separate vulnerability category and supplies the empirical grounding for the bypass-bounty calibration. The Chromium Security Severity Guidelines record the post-sandbox grading rules (heap-contained V8 bugs as High, bugs that bypass the sandbox as Critical) and are the source of truth for downstream-vendor patch prioritization. Reis, Moshchuk, and Oskov's 2019 USENIX Security paper on Site Isolation isn't about the V8 heap sandbox specifically but supplies the cross-process boundary context against which the in-process boundary's value is read. ## Technical Drill-Down - [`v8/src/sandbox/`](https://chromium.googlesource.com/v8/v8/+/main/src/sandbox/) — the V8 heap sandbox implementation; `sandbox.h` carries the cage geometry and the `sandbox_ptr_t` type, `external-pointer-table.h` carries the handle-table implementation. - ["The V8 Sandbox," Samuel Groß, v8.dev blog, April 2024](https://v8.dev/blog/sandbox) — the design's public introduction; the assumed-attacker framing and the rationale for the 40-bit offset scheme are stated here. - [V8 Sandbox design document (`docs/design.md` in the V8 sandbox source tree)](https://chromium.googlesource.com/v8/v8/+/main/src/sandbox/) — the V8 contributor's reference; the alternative-elimination logic and the address-region geometry are documented in their original form. - [Chrome 123 stable release announcement, March 2024](https://chromereleases.googleblog.com/2024/03/stable-channel-update-for-desktop_19.html) — the default-enable event; the public moment the design moved from opt-in to default. - [Chromium Severity Guidelines (security-bugs)](https://chromium.googlesource.com/chromium/src/+/main/docs/security/severity-guidelines.md) — the rules that grade heap-contained V8 bugs as High and heap-sandbox bypasses as Critical. - [Project Zero, "An Analysis of Speculative Type Confusion Vulnerabilities in the Wild," 2021](https://googleprojectzero.blogspot.com/2021/06/an-analysis-of-speculative-type.html) — representative of the bug class the heap sandbox is calibrated against; reads as a worked example of the threat model in operation. --- - [Next: V8 Trusted Space](v8-trusted-space.md) - [Previous: Downstream Advance Access](downstream-advance-access.md)