GHSA-hg73-4w7g-q96w: SandboxJS: Sandbox Escape via Prop Object Leak in New Handler
DescriptionA scope modification vulnerability exists in `@nyariv/sandboxjs` version 0.8.35 and below. The vulnerability allows untrusted sandboxed code to leak internal interpreter objects through the `new` operator, exposing sandbox scope objects in the scope hierarchy to untrusted code; an unexpected and undesired exploit. While this could allow modifying scopes inside the sandbox, code evaluation remains sandboxed and prototypes remain protected throughout the execution.Vulnerable Code LocationPrimary: The `New` Operator Handler**File**: `src/executor.ts`, lines 1275–1280This handler has **two missing sanitization steps**:**Arguments (`b`) are not passed through `valueOrProp()`** — Constructor arguments contain raw `Prop` objects (internal interpreter wrappers) instead of extracted values.**Return value is not passed through `getGlobalProp()` or `sanitizeArray()`** — The constructed object is returned directly to the execution tree without any sanitization.Comparison: The `Call` Handler (Correctly Implemented)**File**: `src/executor.ts`, lines 493–605The `Call` handler correctly sanitizes both arguments (via `valueOrProp`) and return values (via `getGlobalProp` and `sanitizeArray`). The `New` handler does neither.Why This Is VulnerableStep 1: What is a Prop Object?The sandbox interpreter wraps every value access in a `Prop` object (defined at `src/utils.ts`, lines 565–582). A `Prop` has:When sandboxed code accesses a variable like `isNaN`, the interpreter creates `Prop(scope.allVars, 'isNaN')`. The `context` field is a direct reference to the scope's variable storage object.Step 2: What is in `scope.allVars`?At the global scope level, `scope.allVars` is the same object as `options.globals` — the SAFE_GLOBALS object containing:These are the **real** host JavaScript objects. The sandbox normally protects them by intercepting reads through the Prop handler and replacing dangerous ones via the evals Map.Step 3: How the Prop Leaks Through `new`When sandboxed code executes `new Constructor(someVariable)`:The interpreter evaluates `someVariable` — this produces a `Prop` object: `Prop(scope.allVars, 'someVariable')`The `New` handler receives this `Prop` as-is in the `b` array (no `valueOrProp()` call)`new Constructor(...[Prop])` passes the raw `Prop` object to the constructor functionInside the constructor, the `Prop` is received as a named parameterThe constructor reads `arg.context` — this is the raw `scope.allVars` object containing all real globalsThe constructor stores this reference: `this.scope = arg.context`The constructed object is returned without sanitizationProof of ConceptStep-by-Step Reproduction (Terminal)Step 1: Create a new directory and initializeStep 2: Set module type to ESMStep 3: Install the vulnerable packageStep 4: Create the minimal exploitStep 5: Run itImpactAn attacker who can control code executed inside the sandbox can modify scope variables above its current available scopeThe attack requires **no authentication**, **no user interaction**, and works with **default sandbox configuration**. The only requirement is that the host application reads the return value from `sandbox.compile(code)({}).run()`, which is the standard and documented usage pattern.Suggested RemediationFix 1: Sanitize New Handler Arguments (Critical)Add `valueOrProp()` to constructor arguments, matching the Call handler's behavior:Fix 2: Sanitize Sandbox Return Values (Defense in Depth)Add deep sanitization in `Sandbox.ts` to strip internal references from any value returned to the host, regardless of how it was produced.Fix 3: Freeze the Globals Object (Defense in Depth)Freeze or seal `options.globals` and `scope.allVars` after construction to prevent mutation via the Prop leak:
CVSS v4.0: 6.9