JavaScript Code Execution in the Sandbox
This document details how our runtime security suite automatically discovers, inspects, and executes arbitrary NPM package functions inside an isolated sandbox environment.
The primary objective of the Universal Package Runner is not unit-test perfection, but runtime reachability. By invoking every accessible export across an NPM package with smart synthetic parameters, we force hidden or delayed malicious payloads to trigger during execution—enabling real-time monitoring of system calls, network activity, and process spawning.
1. Why This Approach?
Static code analysis alone often fails to detect modern supply chain attacks, obfuscated payloads, or dynamically evaluated scripts (eval, Function(), obfuscated strings).
To overcome these limitations, we chose Dynamic Runtime Inspection via Recursive Tree Traversal:
- Payload Execution on Entry: Malicious routines (such as credential harvesting or reverse shells) frequently execute immediately upon function entry—even if the function later throws a
TypeErrordue to invalid parameters. - Zero Custom Test Harnesses: Rather than maintaining manually written test suites for thousands of individual NPM packages, our automated runner dynamically adapts to any package interface.
- Code Reachability Over Pure Pass Rates: A function call resulting in
❌ FAILstill fulfills our security mandate: the underlying code path was executed, and any malicious side effects were exposed to our sandbox hooks.
In security testing, execution coverage matters far more than function pass rates. A function that crashes after making an unauthorized HTTP request has still successfully exposed its malicious behavior to the sandbox.
2. How It Works
The runner operates through a multi-phase pipeline designed to navigate complex package structures safely:
┌─────────────────────────────────────────────────────────────┐
│ 1. PACKAGE RESOLUTION │
│ Dynamic resolution and loading of target package │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. RECURSIVE EXPORT INSPECTION │
│ • Object graph traversal via Reflect.ownKeys() │
│ • Cycle prevention via WeakSet tracking │
│ • Safe property getter evaluation & depth limit guard │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. PARAMETER & BLUEPRINT SYNTHESIS │
│ • Function signature parsing & arity detection │
│ • Pattern-based argument generation (callbacks, paths) │
│ • Context binding determination (DIRECT/METHOD/CTOR) │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. PROTECTED SANDBOX EXECUTION │
│ • Execution under process.exit() & error overrides │
│ • Async promise resolution & lifecycle flushing │
└─────────────────────────────────────────────────────────────┘
Phase 1: Recursive Package Traversal
The scanner accepts any target NPM package name and recursively traverses its export graph (including nested objects, exported classes, and static utility methods).
A WeakSet keeps track of visited references to prevent circular object loops.
Phase 2: Parameter Heuristics & Blueprint Generation
For every discovered function, the parser inspects its signature:
- Arity Detection: Evaluates
fn.lengthand parameter tokens via regex/AST parsing. - Context Binding: Identifies whether the function is a direct export (
pkg.fn), a method needingthisbinding (pkg.sub.method), or a constructor class (new pkg.Class). - Smart Argument Synthesis: Generates positional fake arguments based on parameter naming patterns (e.g., passing dummy callbacks for
cb, mock URLs forpath, or resolve wrappers foraction).
Phase 3: Isolated Sandbox Execution
The target function is invoked using its corresponding execution blueprint (DIRECT, METHOD, or CONSTRUCTOR). Asynchronous Promises are awaited to capture async rejections safely.
3. Key Capabilities & Safety Features
Our executor includes several resilience mechanisms designed to keep the test runner alive even when executing unstable or hostile code:
| Capability | Mechanism | Why It Matters |
|---|---|---|
process.exit Interception | Overrides process.exit() with a no-op handler | Prevents tested CLI packages from terminating the runner prematurely. |
| Global Rejection Hooks | Listens to uncaughtException & unhandledRejection | Prevents async background errors from crashing the Node.js process. |
| Safe Getter Evaluation | Evaluates property getters with try...catch blocks | Unlocks builder-style chaining APIs (e.g., chalk.red.bold) without throwing. |
| Recursion Depth Limits | Enforces a configurable MAX_DEPTH constraint | Prevents infinite loops caused by self-referencing builder patterns. |
Preserved this Binding | Resolves parent object references before calling .apply() | Ensures object methods maintain access to internal properties. |
While the runner suppresses process termination internally, full containment requires running this script inside a dedicated isolated container (e.g., Docker, Firecracker, or a gVisor sandbox) to block host system modification.
We are runnig this in our Sandbox System.
4. Execution Example & Output Format
Below is an example of the runner scanning and executing the figlet package:
Execution Command
node UniversalPackageRunner.js figlet
Console Output
🔍 Discovered 13 function(s) in "figlet".
🚀 Executing functions...
✅ SUCCESS [DIRECT]: pkg("test_string", ()=>{}, ()=>{})
✅ SUCCESS [DIRECT]: pkg.text("test_string", ()=>{}, ()=>{})
✅ SUCCESS [SUCCESS]: pkg.textSync("test_string", {"testKey":"testValue","debug":true})
✅ SUCCESS [DIRECT]: pkg.metadata("Standard", ()=>{})
✅ SUCCESS [DIRECT]: pkg.defaults({"testKey":"testValue","debug":true})
❌ FAIL [DIRECT]: pkg.parseFont("Standard", "test_string", 1)
↳ Reason: FIGlet header contains invalid values.
✅ SUCCESS [DIRECT]: pkg.loadedFonts()
✅ SUCCESS [DIRECT]: pkg.clearLoadedFonts()
❌ FAIL [DIRECT]: pkg.loadFont("test_string", ()=>{})
↳ Reason: ENOENT: no such file or directory, open '.../fonts/test_string.flf'
✅ SUCCESS [DIRECT]: pkg.loadFontSync("Standard")
✅ SUCCESS [DIRECT]: pkg.preloadFonts("Standard", ()=>{})
===================================
📊 SUMMARY FOR FIGLET
Successful: 11
Failed: 2
===================================
Notice that pkg.loadFont failed with ENOENT because "test_string" is not a real font file.
However, the function was successfully entered, meaning any potential file read or network request inside loadFont was exposed to our sandbox monitoring layer.
5. Directory Structure & Modularization
The runner is split into small, single-responsibility modules:
├── UniversalPackageRunner.js # Main orchestrator and execution loop
└── utils/
├── GenerateFakeArg.js # Smart synthetic argument generator
└── GetParamNames.js # Function signature & parameter parser
Module Responsibilities
UniversalPackageRunner.js: Resolves the target package, controls the recursive scanner, handles process overrides, and logs execution summaries.GenerateFakeArg.js: Maps parameter string patterns to appropriate runtime types (callbacks, booleans, objects, arrays, integers).GetParamNames.js: Parses function strings to extract parameter names for accurate argument mapping.