Skip to content

Sandbox

@whisq/sandbox provides a secure execution environment for running untrusted code. It’s designed for interactive playgrounds, code editors, and educational tools where user-submitted code needs to run safely.

Terminal window
npm install @whisq/sandbox
import { createSandbox } from "@whisq/sandbox";
const sandbox = createSandbox();
const result = await sandbox.execute(`
const x = 2 + 2;
x;
`);
console.log(result);
// { success: true, value: 4 }
sandbox.dispose();

When code throws an error, the sandbox catches it:

const result = await sandbox.execute(`
throw new Error("something broke");
`);
console.log(result);
// { success: false, error: "Error: something broke" }

Your application doesn’t crash — the error is captured and returned.

Set a maximum execution time to prevent infinite loops:

const sandbox = createSandbox({ timeout: 3000 }); // 3 seconds
const result = await sandbox.execute(`
while (true) {} // infinite loop
`);
console.log(result);
// { success: false, error: "Execution timed out" }

The default timeout is 5,000ms (5 seconds).

The sandbox blocks access to browser APIs that could be dangerous:

  • document, window, globalThis
  • fetch, XMLHttpRequest, WebSocket
  • localStorage, sessionStorage, indexedDB
  • navigator, location, history
  • alert, confirm, prompt

Attempting to access any of these throws an error:

const result = await sandbox.execute(`
fetch("/api/data");
`);
// { success: false, error: "fetch is not defined" }

Send messages between the sandbox and your application:

const sandbox = createSandbox();
// Listen for messages from sandboxed code
sandbox.onMessage((msg) => {
console.log("From sandbox:", msg);
});
await sandbox.execute(`
postMessage({ type: "result", value: 42 });
`);

Combine the sandbox with Whisq UI for an interactive playground:

import { signal, component, div, textarea, button, pre, mount } from "@whisq/core";
import { createSandbox } from "@whisq/sandbox";
const Playground = component(() => {
const code = signal('const x = 2 + 2;\nx;');
const output = signal("");
const sandbox = createSandbox({ timeout: 3000 });
const run = async () => {
const result = await sandbox.execute(code.value);
output.value = result.success
? String(result.value)
: `Error: ${result.error}`;
};
return div({ class: "playground" },
textarea({
value: () => code.value,
oninput: (e) => code.value = e.target.value,
rows: "8",
}),
button({ onclick: run }, "Run"),
pre({ class: "output" }, () => output.value),
);
});
mount(Playground({}), document.getElementById("app")!);

Always dispose the sandbox when you’re done:

const sandbox = createSandbox();
// ... use the sandbox ...
sandbox.dispose(); // clean up resources

In a Whisq component, use onCleanup:

import { onMount, onCleanup } from "@whisq/core";
import { createSandbox } from "@whisq/sandbox";
const PlaygroundComponent = component(() => {
let sandbox: Sandbox;
onMount(() => {
sandbox = createSandbox();
onCleanup(() => sandbox.dispose());
});
// ... use sandbox in event handlers
});
OptionTypeDefaultDescription
timeoutnumber5000Maximum execution time in ms

Returns Promise<ExecutionResult>:

interface ExecutionResult {
success: boolean;
value?: unknown; // result of the last expression
error?: string; // error message if failed
}

Listen for postMessage() calls from sandboxed code.

Clean up sandbox resources.