SandboxJS has Sandbox Escape via Unprotected AsyncFunction Constructor
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.
| Package | Affected versions | Patched versions |
|---|---|---|
@nyariv/sandboxjsnpm | < 0.8.26 | 0.8.26 |
Affected products
1Patches
1345aee6566e4fix(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- github.com/advisories/GHSA-wxhw-j4hc-fmq6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-23830ghsaADVISORY
- github.com/nyariv/SandboxJS/commit/345aee6566e47979dee5c337b925b141e7f78ccdghsax_refsource_MISCWEB
- github.com/nyariv/SandboxJS/security/advisories/GHSA-wxhw-j4hc-fmq6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.