Skip to content

[JSC] Let an embedder allow a Proxy in the prototype chain of its global object - #626

Open
robobun wants to merge 1 commit into
mainfrom
robobun/a1f7783b/nodevm-proxy-in-global-prototype-chain
Open

[JSC] Let an embedder allow a Proxy in the prototype chain of its global object#626
robobun wants to merge 1 commit into
mainfrom
robobun/a1f7783b/nodevm-proxy-in-global-prototype-chain

Conversation

@robobun

@robobun robobun commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • vm.runInContext("1 + 1", ctx) throws TypeError: Proxy is not allowed in the global prototype chain. when the global of a node:vm context has a Proxy anywhere in its prototype chain. jsdom 28+ puts one there (WindowProperties), so jsdom under node:vm and Vitest vmThreads fail. Node prints 2. node:vm rejects a Proxy in a DONT_CONTEXTIFY global prototype chain, breaking jsdom with Vitest vmThreads bun#42331.
  • Bun's main global has the same problem: after Object.setPrototypeOf(globalThis, Object.create(new Proxy({}, {}))), vm.runInThisContext() and CommonJS require() throw the same error. Node runs both.
  • The check is in ProgramExecutable::initializeGlobalProperties (runtime/ProgramExecutable.cpp:120). It runs before every program links.

Fix

  • Add JSGlobalObject::setAllowsProxyInPrototypeChain() under USE(BUN_JSC_ADDITIONS). Default false. When a global object opts in, the check is skipped. Nothing else changes. Bun opts in from Bun::GlobalScope::finishCreation, the base of every Bun global (main, workers, shadow realms, node:vm contexts).
  • Correct because global declaration instantiation only reads own properties of the global object (hasRestrictedGlobalProperty, canDeclareGlobalVar, canDeclareGlobalFunction, createGlobalVarBinding, and abstractAccess in JSScope.cpp), so no Proxy trap runs while a program links. Run time name resolution walks the chain through the ordinary [[HasProperty]], [[Get]] and [[Set]] paths, which handle a Proxy. That is also what V8 does.
  • Verified: a Bun debug build against this branch runs programs, declares var and function globals, reads and writes through the Proxy, and throws ReferenceError for a missing name, in DONT_CONTEXTIFY contexts, contextified contexts, and the main realm (runInThisContext, require). The whole test/js/node/vm suite and the 95 vendored test-vm-*.js pass. The Bun side is the matching PR in oven-sh/bun.

Background

  • A global object is normally an immutable prototype exotic object, so a Proxy can never enter its chain. The check was added for the web (bug 165205, r209149), where an intermediate prototype such as Window.prototype is mutable and var declaration used hasProperty at the time.
  • Every Bun global clears IsImmutablePrototypeExoticObject from its structure (ZigGlobalObject.cpp:443, NodeVM.cpp:941), in part so that jsdom can set the prototype of the window. That is how a Proxy reaches a global's chain in Bun, and V8 permits it for Node's globals.

…bal object

ProgramExecutable::initializeGlobalProperties throws "Proxy is not allowed
in the global prototype chain" when any link of the global object's
prototype chain is a Proxy. A global object is normally an immutable
prototype exotic object, so this only matters for an embedder that made
the prototype mutable. Bun's node:vm global does that, and jsdom 28+ puts
a Proxy (WindowProperties) in the chain of its window. V8 runs programs
against such a chain, so Node accepts it.

Add JSGlobalObject::setAllowsProxyInPrototypeChain() under
USE(BUN_JSC_ADDITIONS). When a global object opts in, the check is
skipped. Global declaration instantiation only reads own properties of
the global object, so no Proxy trap runs while the program links. Name
resolution at run time already walks the chain through the ordinary
[[HasProperty]], [[Get]] and [[Set]] paths, which handle a Proxy.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Because it relaxes a security-originated invariant in ProgramExecutable::initializeGlobalProperties and Source/JavaScriptCore is CODEOWNER-covered, a human look would still be worthwhile.

What was reviewed:

  • Verified the new m_allowsProxyInPrototypeChain flag defaults to false and both the header member/accessors and the .cpp guard compile to the pre-existing behavior when USE(BUN_JSC_ADDITIONS) is off (the #else branch hardcodes rejectProxyInPrototypeChain = true).
  • Checked that the moved prototype-walk loop is byte-for-byte identical to the original inside the new if — no semantic drift when the flag is unset.
  • Confirmed the new flag follows the neighboring m_needsSiteSpecificQuirks pattern (private m_ bool + inline getter/setter) per the JSC CLAUDE.md convention of copying existing patterns.
Extended reasoning...

Overview

This PR adds an opt-in boolean flag m_allowsProxyInPrototypeChain to JSGlobalObject (Source/JavaScriptCore/runtime/JSGlobalObject.h) and consults it in ProgramExecutable::initializeGlobalProperties (Source/JavaScriptCore/runtime/ProgramExecutable.cpp) to skip the loop that throws TypeError: Proxy is not allowed in the global prototype chain. Both additions are guarded by #if USE(BUN_JSC_ADDITIONS); the non-Bun path sets rejectProxyInPrototypeChain = true and preserves the original behavior verbatim. The motivation is jsdom 28+ under node:vm, which installs a Proxy (WindowProperties) in the window's prototype chain.

Security risks

The original check (WebKit bug 165205) was added as a security/correctness invariant to prevent Proxy traps from interfering with global scope resolution. This change makes that check bypassable, but only when an embedder explicitly opts a specific JSGlobalObject in via setAllowsProxyInPrototypeChain(true) — the default remains false, and non-Bun builds cannot reach the opt-out at all. The PR argues global declaration instantiation only touches own properties of the global object, so no Proxy trap fires during program linking; runtime name resolution already goes through ordinary [[Get]]/[[Set]]/[[HasProperty]] which handle Proxies. That reasoning is plausible, but whether any JIT/IC path (e.g., global property resolution watchpoints, GlobalVar/GlobalLexicalVar resolve types, or scope-chain assumptions in DFG/FTL) independently assumes "no Proxy in the global's prototype chain" is a JSC-domain question I can't fully verify from this diff alone.

Level of scrutiny

Medium-high. The diff itself is tiny, mechanical, and follows the established USE(BUN_JSC_ADDITIONS) + neighboring-bool-flag pattern from CLAUDE.md. However, it touches execution semantics at the global-object boundary, an area historically sensitive enough that upstream WebKit added an explicit rejection. .github/CODEOWNERS assigns /Source/JavaScriptCore to WebKit/jsc-reviewers, which per the approval guidelines means a human owner should review rather than auto-approve.

Other factors

The change is default-off and additive, so blast radius is limited to embedders that call the new setter (the matching Bun-side PR). No tests are added here; the PR description reports manual verification against a Bun debug build. The flag placement between m_canDoASCIIUCADUCETLocaleCompare and m_globalLexicalBindingEpoch keeps the bool cluster together and doesn't perturb non-Bun object layout (it's #if-guarded). Given CODEOWNERS coverage and the security-adjacent subject matter, deferring is the appropriate call even though I found no defects in the diff.

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Essentials

Run ID: d65621d7-9065-49e8-b4cb-ef7dcd4a3818

📥 Commits

Reviewing files that changed from the base of the PR and between cf1b36e and f1e23f9.

📒 Files selected for processing (2)
  • Source/JavaScriptCore/runtime/JSGlobalObject.h
  • Source/JavaScriptCore/runtime/ProgramExecutable.cpp

Included review availability: Your plan provides up to 5 included reviews per hour; 1 remains after this review.


Walkthrough

The change adds Bun-specific state and accessors for allowing proxies in a global object's prototype chain. Global prototype validation uses this setting in Bun builds and retains unconditional rejection in other builds.

Changes

Proxy Prototype Chain Handling

Layer / File(s) Summary
Proxy policy state
Source/JavaScriptCore/runtime/JSGlobalObject.h
JSGlobalObject stores whether proxies are allowed in its prototype chain and exposes Bun-specific getter and setter methods.
Prototype validation
Source/JavaScriptCore/runtime/ProgramExecutable.cpp
Bun builds consult the global object's proxy policy. Other builds continue to reject proxies with the existing TypeError.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to f1e23

The opt-in remains disabled by default and the existing rejection behavior is retained outside Bun builds.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: allowing an embedder to permit a Proxy in a global object's prototype chain.
Description check ✅ Passed The description is detailed and directly explains the problem, fix, rationale, affected behavior, and verification. It does not follow the repository template completely because it omits a WebKit Bugz…

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
f1e23f97 autobuild-preview-pr-626-f1e23f97 2026-09-11 15:32:05 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant