VYPR
Critical severityNVD Advisory· Published Mar 13, 2026· Updated Mar 16, 2026

SandboxJS has a Sandbox Escape

CVE-2026-26954

Description

SandboxJS is a JavaScript sandboxing library. Prior to 0.8.34, it is possible to obtain arrays containing Function, which allows escaping the sandbox. Given an array containing Function, and Object.fromEntries, it is possible to construct {[p]: Function} where p is any constructible property. This vulnerability is fixed in 0.8.34.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@nyariv/sandboxjsnpm
< 0.8.340.8.34

Affected products

1

Patches

1
e01505b1ea49

Merge commit from fork

https://github.com/nyariv/SandboxJSnyarivMar 13, 2026via ghsa
5 files changed · +68 8
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "@nyariv/sandboxjs",
    -  "version": "0.8.33",
    +  "version": "0.8.34",
       "description": "Javascript sandboxing library.",
       "main": "dist/node/Sandbox.js",
       "module": "./build/Sandbox.js",
    
  • src/executor.ts+28 4 modified
    @@ -421,7 +421,7 @@ addOps<unknown, PropertyKey>(LispType.Prop, ({ done, a, b, obj, context, scope }
         throw new SandboxAccessError(`Access to prototype of global object is not permitted`);
       }
     
    -  const p = getGlobalProp(val, context);
    +  const p = getGlobalProp(val, context, new Prop(a, b, false, false));
       if (p) {
         done(undefined, p);
         return;
    @@ -456,7 +456,7 @@ function getGlobalProp(val: unknown, context: IExecContext, prop?: Prop) {
           prop?.isVariable || false,
         );
       }
    -  const evl = isFunc && context.evals.get(val);
    +  const evl = isFunc && context.evals.get(val as Function);
       if (evl) {
         return new Prop(
           {
    @@ -470,6 +470,26 @@ function getGlobalProp(val: unknown, context: IExecContext, prop?: Prop) {
       }
     }
     
    +function sanitizeArray<T>(val: T, context: IExecContext, cache = new WeakSet<object>()): T {
    +  if (!Array.isArray(val)) return val;
    +  if (cache.has(val)) return val;
    +  cache.add(val);
    +  for (let i = 0; i < val.length; i++) {
    +    const item = val[i];
    +    if (item === globalThis) {
    +      val[i] = context.ctx.sandboxGlobal;
    +    } else if (typeof item === 'function') {
    +      const replacement = context.evals.get(item);
    +      if (replacement) {
    +        val[i] = replacement;
    +      }
    +    } else {
    +      sanitizeArray(item, context, cache);
    +    }
    +  }
    +  return val;
    +}
    +
     addOps<unknown, Lisp[], any>(LispType.Call, ({ done, a, b, obj, context }) => {
       if (context.ctx.options.forbidFunctionCalls)
         throw new SandboxCapabilityError('Function invocations are not allowed');
    @@ -490,8 +510,10 @@ addOps<unknown, Lisp[], any>(LispType.Call, ({ done, a, b, obj, context }) => {
         .map((item) => valueOrProp(item, context));
     
       if (typeof obj === 'function') {
    -    let ret = obj(...vals);
    +    const evl = context.evals.get(obj);
    +    let ret = evl ? evl(obj, ...vals) : obj(...vals);
         ret = getGlobalProp(ret, context) || ret;
    +    sanitizeArray(ret, context);
         done(undefined, ret);
         return;
       }
    @@ -575,8 +597,10 @@ addOps<unknown, Lisp[], any>(LispType.Call, ({ done, a, b, obj, context }) => {
         }
       }
       obj.get(context);
    -  let ret = obj.context[obj.prop](...vals) as unknown;
    +  const evl = context.evals.get(obj.context[obj.prop] as any);
    +  let ret = evl ? evl(obj.context[obj.prop], ...vals) : (obj.context[obj.prop](...vals) as unknown);
       ret = getGlobalProp(ret, context) || ret;
    +  sanitizeArray(ret, context);
       done(undefined, ret);
     });
     
    
  • src/utils.ts+2 2 modified
    @@ -69,7 +69,7 @@ export interface IExecContext extends IExecutionTree {
       >;
       changeSubscriptionsGlobal: WeakMap<SubscriptionSubject, Set<(modification: Change) => void>>;
       registerSandboxFunction: (fn: (...args: any[]) => any) => void;
    -  evals: Map<any, any>;
    +  evals: Map<Function, Function>;
       allowJit: boolean;
       evalContext?: IEvalContext;
     }
    @@ -373,7 +373,7 @@ export class Scope {
           throw new TypeError(`Cannot set properties of null, (setting '${key}')`);
         }
         if (prop.isConst) {
    -      throw new TypeError(`Cannot assign to const variable '${key}'`);
    +      throw new TypeError(`Assignment to constant variable`);
         }
         if (prop.isGlobal) {
           throw new SandboxError(`Cannot override global variable '${key}'`);
    
  • test/eval/testCases/security.data.ts+36 0 modified
    @@ -279,4 +279,40 @@ export const tests: TestCase[] = [
         safeExpect: '/hasOwnProperty is not defined/',
         category: 'Security',
       },
    +  {
    +    code: `Object.values(this).includes(Function)`,
    +    evalExpect: true,
    +    safeExpect: true,
    +    category: 'Security',
    +  },
    +  {
    +    code: `Object.values(this).at(1)('bypassed=1')()`,
    +    evalExpect: 'bypassed',
    +    safeExpect: '/bypassed is not defined/',
    +    category: 'Security',
    +  },
    +  {
    +    code: `
    +const p = (async function () {})();
    +({
    +  "finally": p.finally,
    +  ...Object.fromEntries([['then', ...Object.values(this).slice(1)]]),
    +}).finally('bypassed=1')();
    +`,
    +    evalExpect: 'bypassed',
    +    safeExpect: '/bypassed is not defined/',
    +    category: 'Security',
    +  },
    +  {
    +    code: `const a = Function; a.anything = 1; return a.anything;`,
    +    evalExpect: 1,
    +    safeExpect: "/Cannot assign property 'anything' of a global object/",
    +    category: 'Security',
    +  },
    +  {
    +    code: 'this.Function = 1',
    +    evalExpect: 1,
    +    safeExpect: "/Cannot assign property 'Function' of a global object/",
    +    category: 'Security',
    +  },
     ];
    
  • TODO.md+1 1 modified
    @@ -4,7 +4,7 @@
     
     This document describes the current implementation status of ECMAScript features in SandboxJS.
     
    -**Test Coverage**: 908 total tests | Code Coverage: ~96% statement coverage, ~90% branch coverage
    +**Test Coverage**: 1001 total tests | Code Coverage: ~96% statement coverage, ~90% branch coverage
     
     ---
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.