VYPR
Critical severityOSV Advisory· Published Jan 27, 2026· Updated Jan 28, 2026

SandboxJS has Sandbox Escape via Unprotected AsyncFunction Constructor

CVE-2026-23830

Description

SandboxJS is a JavaScript sandboxing library. Versions prior to 0.8.26 have a sandbox escape vulnerability due to AsyncFunction not being isolated in SandboxFunction. The library attempts to sandbox code execution by replacing the global Function constructor with a safe, sandboxed version (SandboxFunction). This is handled in utils.ts by mapping Function to sandboxFunction within a map used for lookups. However, before version 0.8.26, the library did not include mappings for AsyncFunction, GeneratorFunction, and AsyncGeneratorFunction. These constructors are not global properties but can be accessed via the .constructor property of an instance (e.g., (async () => {}).constructor). In executor.ts, property access is handled. When code running inside the sandbox accesses .constructor on an async function (which the sandbox allows creating), the executor retrieves the property value. Since AsyncFunction was not in the safe-replacement map, the executor returns the actual native host AsyncFunction constructor. Constructors for functions in JavaScript (like Function, AsyncFunction) create functions that execute in the global scope. By obtaining the host AsyncFunction constructor, an attacker can create a new async function that executes entirely outside the sandbox context, bypassing all restrictions and gaining full access to the host environment (Remote Code Execution). Version 0.8.26 patches this vulnerability.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@nyariv/sandboxjsnpm
< 0.8.260.8.26

Affected products

1

Patches

1
345aee6566e4

fix(security): harden sandbox against code execution bypass (GHSA-wxhw-j4hc-fmq6)

4 files changed · +39 1
  • src/eval.ts+24 1 modified
    @@ -1,9 +1,10 @@
    -import { createFunction, currentTicks } from './executor.js';
    +import { createFunction, createFunctionAsync, currentTicks } from './executor.js';
     import parse, { lispifyFunction } from './parser.js';
     import { IExecContext, Ticks } from './utils.js';
     
     export interface IEvalContext {
       sandboxFunction: typeof sandboxFunction;
    +  sandboxAsyncFunction: typeof sandboxAsyncFunction;
       sandboxedEval: typeof sandboxedEval;
       sandboxedSetTimeout: typeof sandboxedSetTimeout;
       sandboxedSetInterval: typeof sandboxedSetInterval;
    @@ -25,6 +26,7 @@ export type SandboxSetInterval = (
     export function createEvalContext(): IEvalContext {
       return {
         sandboxFunction,
    +    sandboxAsyncFunction,
         sandboxedEval,
         sandboxedSetTimeout,
         sandboxedSetInterval,
    @@ -52,6 +54,27 @@ export function sandboxFunction(context: IExecContext, ticks?: Ticks): SandboxFu
       }
     }
     
    +export type SandboxAsyncFunction = (code: string, ...args: string[]) => () => Promise<unknown>;
    +export function sandboxAsyncFunction(context: IExecContext, ticks?: Ticks): SandboxAsyncFunction {
    +  return SandboxAsyncFunction;
    +  function SandboxAsyncFunction(...params: string[]) {
    +    const code = params.pop() || '';
    +    const parsed = parse(code);
    +    return createFunctionAsync(
    +      params,
    +      parsed.tree,
    +      ticks || currentTicks.current,
    +      {
    +        ...context,
    +        constants: parsed.constants,
    +        tree: parsed.tree,
    +      },
    +      undefined,
    +      'anonymous'
    +    );
    +  }
    +}
    +
     export function sandboxedEval(func: SandboxFunction): SandboxEval {
       return sandboxEval;
       function sandboxEval(code: string) {
    
  • src/SandboxExec.ts+1 0 modified
    @@ -1,6 +1,7 @@
     import { IEvalContext } from './eval.js';
     import { Change, ExecReturn, executeTree, executeTreeAsync } from './executor.js';
     import {
    +  AsyncFunction,
       createContext,
       IContext,
       IExecContext,
    
  • src/utils.ts+4 0 modified
    @@ -1,3 +1,5 @@
    +// Reusable AsyncFunction constructor reference
    +export const AsyncFunction: Function = Object.getPrototypeOf(async function () {}).constructor;
     import { IEvalContext } from './eval';
     import { Change, Unknown } from './executor';
     import { IConstants, IExecutionTree, Lisp, LispItem } from './parser';
    @@ -160,7 +162,9 @@ export function createExecContext(
       );
       if (evalContext) {
         const func = evalContext.sandboxFunction(execContext);
    +    const asyncFunc = evalContext.sandboxAsyncFunction(execContext);
         evals.set(Function, func);
    +    evals.set(AsyncFunction, asyncFunc);
         evals.set(eval, evalContext.sandboxedEval(func));
         evals.set(setTimeout, evalContext.sandboxedSetTimeout(func));
         evals.set(setInterval, evalContext.sandboxedSetInterval(func));
    
  • test/tests.json+10 0 modified
    @@ -150,6 +150,16 @@
         "evalExpect": "Function",
         "safeExpect": "SandboxFunction"
       },
    +  {
    +    "code": "(()=>{}).constructor.name",
    +    "evalExpect": "Function",
    +    "safeExpect": "SandboxFunction"
    +  },
    +  {
    +    "code": "(async ()=>{}).constructor.name",
    +    "evalExpect": "AsyncFunction",
    +    "safeExpect": "SandboxAsyncFunction"
    +  },
       {
         "code": "[].anything = 1",
         "evalExpect": 1,
    

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

4

News mentions

0

No linked articles in our index yet.