Jan 27, 2026·5 min read·45 visits
SandboxJS locked the front door (`Function`) but left the side door (`AsyncFunction`) wide open. By accessing the constructor of an async arrow function, attackers can instantiate code that executes in the host's context, not the sandbox, achieving instant Remote Code Execution (RCE).
A critical oversight in SandboxJS allowed attackers to bypass the execution environment completely by leveraging the AsyncFunction constructor. While the standard Function constructor was proxied, its asynchronous sibling was left unguarded.
JavaScript sandboxing is effectively an arms race between library maintainers and the ECMAScript specification. The goal of SandboxJS is simple: take untrusted code, parse it, identify the scary bits, and execute it in a padded cell where it can't touch process, require, or your AWS keys.
To achieve this, SandboxJS doesn't just run code; it dissects it. It creates an execution context that mimics the global scope but replaces dangerous natives with shimmed, safe versions. You call eval()? You get a safe eval. You call Function()? You get a safe function factory.
But here's the problem with JavaScript: there is never just one way to do anything. While the developers were busy barricading the standard Function constructor, they forgot that ECMAScript 2017 introduced a trendy new sibling: AsyncFunction. This oversight turns the entire sandbox into a suggestion rather than a rule.
The root cause of CVE-2026-23830 is a classic "allowlist miss." The library maintains a WeakMap called evals that maps native constructors to their sandboxed counterparts. When the sandbox encounters code trying to create a new function, it checks this map. If it sees the Function constructor, it redirects the call to a safe implementation.
However, the AsyncFunction constructor is not a global object you can just type into the console (like Array or Date). It is hidden. It effectively doesn't exist in the global namespace.
Because it wasn't sitting out in the open, the developers seemingly forgot it existed. They didn't add it to the evals map. This meant that if an attacker could get a reference to it, the sandbox would look at it, shrug, and say, "I don't have a rule for this, so here is the real, native host object." And just like that, the prisoner is given the keys to the warden's office.
Let's look at src/utils.ts before the patch. The code is explicitly setting up protections for specific dangerous globals. It's almost tragic to see Function and eval being handled so carefully right next to the gaping hole.
// BEFORE FIX
if (evalContext) {
const func = evalContext.sandboxFunction(execContext);
evals.set(Function, func);
// <--- The silence here is deafening.
evals.set(eval, evalContext.sandboxedEval(func));
}The fix involves acknowledging that AsyncFunction exists. Since it's not globally accessible by name, the patch has to perform a bit of prototype gymnastics to capture it:
// AFTER FIX (src/utils.ts)
// 1. Capture the elusive constructor
export const AsyncFunction: Function = Object.getPrototypeOf(async function () {}).constructor;
// 2. Map it to a safe version
if (evalContext) {
const func = evalContext.sandboxFunction(execContext);
const asyncFunc = evalContext.sandboxAsyncFunction(execContext);
evals.set(Function, func);
evals.set(AsyncFunction, asyncFunc); // <--- The door is now locked.
evals.set(eval, evalContext.sandboxedEval(func));
}This highlights a fundamental fragility in JS sandboxes: you have to know every possible way to generate code to stop code generation.
So, how do we weaponize this? We can't just type new AsyncFunction(...) because AsyncFunction isn't a global variable. But we can derive it. Every async function is an instance of AsyncFunction. By defining a throwaway async arrow function and checking its .constructor property, we get a handle on the Native Host Constructor.
Here is the step-by-step kill chain:
.constructor property. Because the sandbox doesn't recognize this object in its evals map, it hands you the real host constructor.// The PoC - One Line of Doom
const hostProcess = await (async () => {}).constructor("return process")();
// Taking it further (RCE)
const rce = (async () => {}).constructor(
"return process.mainModule.require('child_process').execSync('id').toString()"
);
console.log(await rce());
// Output: uid=0(root) gid=0(root) ...This is elegant in its simplicity. No memory corruption, no race conditions, just asking JavaScript for a feature it happily provides.
The impact here is maximum severity. If you are using SandboxJS to run user-submitted scripts—perhaps for a plugin system, a rules engine, or a 'code playground'—you are compromised.
Because the code generated by AsyncFunction runs in the host context (the global scope where the Node.js process lives), the attacker has access to everything the host process has access to:
process.env).fs module via require).This isn't just a data leak; it is full remote server control. The CVSS score of 9.8 is well-deserved.
The immediate fix is to upgrade SandboxJS to the version containing commit 345aee6. If you cannot upgrade, you are theoretically out of luck, as monkey-patching the library externally is difficult due to how it initializes its context maps.
For Developers & Researchers:
This vulnerability serves as a reminder that Function is not the only way to generate code. When auditing sandboxes, always check for the "exotic" constructors:
AsyncFunction: (async()=>{}).constructorGeneratorFunction: (function*(){}).constructorAsyncGeneratorFunction: (async function*(){}).constructorIf the sandbox blocks Function but misses any of the above, it's game over. Defense in depth suggests that relying solely on a JS-based sandbox for high-risk code execution is inherently risky. Consider using actual isolation technologies like WebAssembly, Firecracker microVMs, or Deno sub-processes for true security.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
SandboxJS nyariv | < Commit 345aee6 | Commit 345aee6 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 (Code Injection) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network |
| Impact | Full Sandbox Escape / RCE |
| Exploit Status | PoC Available |
| Affected Component | evals WeakMap / AsyncFunction Constructor |
Improper Control of Generation of Code ('Code Injection')
An improper authentication vulnerability (CWE-287) exists in the legacy, deprecated Internet Key Exchange version 1 (IKEv1) key exchange protocol implementation in Check Point Security Gateways. The vulnerability is caused by a logic flow weakness during the certificate validation process for Remote Access VPN and Mobile Access (SSL VPN) connections. An unauthenticated remote attacker can exploit this weakness to bypass user authentication entirely, establishing a fully functional Remote Access VPN connection without a valid password.
GeoNode versions prior to 4.4.5 and 5.0.2 are vulnerable to Server-Side Request Forgery (SSRF) in the service registration endpoint. Authenticated attackers with low privileges can exploit insufficient input validation in the Web Map Service (WMS) registration module to force the application server to make outbound network queries to loopback addresses, private RFC1918 subnets, link-local scopes, and cloud metadata endpoints. This technical report details the mechanics of the vulnerability, the underlying architectural flaw, and how to effectively remediate and mitigate the associated security risks.
CVE-2022-0492 is a high-severity missing authorization vulnerability in the Linux kernel's Control Groups (cgroups) v1 implementation. The flaw resides within the cgroup_release_agent_write function in kernel/cgroup/cgroup-v1.c, where the kernel fails to validate if the process writing to the release_agent file possesses administrative capabilities in the initial user namespace. This allows a local attacker inside a container with root privileges (UID 0) to abuse user namespaces, mount a cgroups v1 directory, modify the release_agent parameter, and execute arbitrary commands on the host system as host root, effectively achieving a complete container escape.
NocoDB is subject to an insufficient session expiration vulnerability where OAuth access and refresh tokens are not invalidated or revoked during security-sensitive actions such as password changes, forgot-password requests, or password resets. This allows an attacker possessing an active OAuth token to maintain unauthorized persistence.
A vulnerability in the vantage6 federated learning framework allows unauthenticated remote attackers to gain administrative control of the server via hardcoded default credentials (root/root) when deployed under default configurations in versions 4.2.3 and below.
An improper access control vulnerability in the vantage6 node component allows concurrently running algorithm containers to read and modify sensitive input and output files of other tasks. The lack of strict workspace directory isolation exposes a significant attack surface in multi-tenant or federated environments where untrusted algorithms are executed.