Simple and secure JavaScript execution in an isolated Deno subprocess. Register async host functions, run untrusted code with explicit permissions, and optionally stream logs and stderr back to the host in real time.
- Secure sandboxing with Deno permissions
- JSON-RPC IPC between host and sandbox process
- Async host handlers exposed directly inside sandboxed code
- Streaming logs via
onLog(text, level)beforeexecute()resolves - Streaming stderr via
onStderr(text) - Process controls for timeout, memory, permissions,
cwd,env, and extra Deno CLI args - Zero-config runtime discovery from the bundled
denopackage
# npm
npm install @mcpc-tech/handle-sandbox
# jsr
npx jsr add @mcpc/handle-sandbox
deno add @mcpc/handle-sandbox --allow-scripts=npm:denoimport { Sandbox } from "@mcpc/handle-sandbox";
const sandbox = new Sandbox();
sandbox.start();
const result = await sandbox.execute(`
console.log("Hello from sandbox!");
return 1 + 1;
`);
console.log(result.logs); // ["Hello from sandbox!"]
console.log(result.result); // 2
console.log(result.error); // undefined
sandbox.stop();Registered handlers are available inside sandboxed code by the same name.
import { Sandbox } from "@mcpc/handle-sandbox";
const sandbox = new Sandbox();
sandbox.registerHandler("fetchUser", async (userId) => {
return { id: userId, name: "Alice" };
});
sandbox.registerHandler("saveAuditLog", async (payload) => {
return { ok: true, payload };
});
sandbox.start();
const result = await sandbox.execute(`
const user = await fetchUser(123);
await saveAuditLog({ action: "login", userId: user.id });
console.log("User:", user.name);
return user;
`);
console.log(result.result); // { id: 123, name: "Alice" }
console.log(result.logs); // ["User: Alice"]
sandbox.stop();import { type LogLevel, Sandbox } from "@mcpc/handle-sandbox";
const sandbox = new Sandbox({
onLog: (text: string, level: LogLevel) => {
console.log(`[sandbox:${level}]`, text);
},
onStderr: (text) => {
process.stderr.write(`[sandbox:stderr] ${text}`);
},
});
sandbox.start();
const result = await sandbox.execute(`
console.log("step 1");
console.warn("step 2");
await Deno.stderr.write(new TextEncoder().encode("warning from stderr\n"));
return "done";
`);
console.log(result.result); // "done"
sandbox.stop();import { Sandbox } from "@mcpc/handle-sandbox";
const sandbox = new Sandbox({
timeout: 5_000,
memoryLimit: 256,
permissions: ["--allow-net=api.example.com", "--allow-env=API_TOKEN"],
cwd: process.cwd(),
env: {
...process.env,
API_TOKEN: "example-token",
},
extraArgs: ["--quiet"],
});
sandbox.start();
const result = await sandbox.execute(`
return {
cwd: Deno.cwd(),
tokenAvailable: Boolean(Deno.env.get("API_TOKEN")),
};
`);
console.log(result.result);
sandbox.stop();Create a sandbox instance.
timeout?: number- Execution timeout in milliseconds. Default:30000memoryLimit?: number- V8 max old space size in MBpermissions?: string[]- Deno permission flags such as"--allow-net=api.example.com"extraArgs?: string[]- Additional Deno CLI args such as"--quiet"cwd?: string- Working directory for the sandbox subprocessenv?: Record<string, string | undefined>- Environment variables for the sandbox subprocessonLog?: (text: string, level: LogLevel) => void- Streaming console output callbackonStderr?: (text: string) => void- Streaming stderr callback
Register an async host function that sandboxed code can call directly by name.
sandbox.registerHandler("double", async (value) => {
return Number(value) * 2;
});
const result = await sandbox.execute(`
return await double(21);
`);Start the Deno subprocess. Call this before execute().
Execute JavaScript code inside the sandbox.
code: string- JavaScript source to runcontext?: Record<string, unknown>- Optional execution context
Promise<{
logs: string[];
result?: unknown;
error?: string;
}>;Stop the sandbox process and clean up resources.
type LogLevel = "log" | "error" | "warn" | "info";- The host starts a Deno subprocess.
- The host sends user code to the subprocess over JSON-RPC.
- The sandbox executes the code in an isolated environment.
- Registered handlers are exposed as async functions inside the sandbox.
- Console output is returned in the final result and can also be streamed via
onLog. - The subprocess stderr stream can be observed via
onStderr.
The sandbox runs with minimal Deno permissions by default. Grant only the permissions your use case needs.
// No permissions by default
new Sandbox();
// Allow network access to specific domains
new Sandbox({
permissions: ["--allow-net=github.com,api.example.com"],
});
// Allow reading specific directories
new Sandbox({
permissions: ["--allow-read=/tmp,/var/log"],
});
// Allow access to selected environment variables
new Sandbox({
permissions: ["--allow-env=API_TOKEN"],
});Without the corresponding Deno permission flags, operations like file access, network access, and reading environment variables will fail.
# format
deno fmt
# test
deno test --allow-all tests/MIT