Skip to main content
Executing Code

Execute & Evaluate

Run untrusted JavaScript with evaluate and execute, pass data in with inputs, and read structured results.

Evaluate an expression

evaluate runs one expression and returns its JSON-serializable value.

import { evaluate } from "secure-exec";

// Each call runs in a fresh VM that is disposed when the call finishes.
const sum = await evaluate<number>("1 + 2");
console.log(sum.outcome === "succeeded" ? sum.value : sum.error); // 3

The expression may be a promise, which is awaited. Returning undefined, a function, a symbol, or a circular value fails instead of silently losing the value.

Several statements go in a function:

// `evaluate` takes one expression, so several statements go in a function.
const report = await evaluate<{ count: number; max: number }>(`
	(() => {
		const values = [3, 9, 4];
		return { count: values.length, max: Math.max(...values) };
	})()
`);
console.log(report.outcome === "succeeded" ? report.value : report.error);

Execute a module

execute runs source as an ES module, so import and top-level await work. It returns captured output instead of a value.

// `execute` runs a whole ES module for its side effects. Capture output to read
// it back.
const run = await execute(
	`
	import { platform } from "node:os";
	console.log("running on", platform());
	`,
	{ output: { capture: "all" } },
);
console.log(run.stdout?.trim()); // running on linux

Run a file

executeFile runs a JavaScript file that is already in the VM, by its guest path. Mount a host directory to run a script in a one-shot call. On a VM, write the file with vm.filesystem first. secure-exec/typescript has the same function for TypeScript files.

// `executeFile` runs a file that is already in the VM. With a mount, that works
// in a one-shot call: the script lives on the host and runs inside the VM.
const report = await executeFile("/mnt/data/report.mjs", {
	mounts: [hostDirMount("/mnt/data", hostData)],
	output: { capture: "all" },
});
console.log(report.stdout?.trim()); // report: hello from the host

A file resolves its imports from its own directory, the same as in Node.js, so a file under /workspace finds packages installed there.

Pass data in

// `inputs` hands host values to the code as real objects, so data is never
// interpolated into source.
const total = await evaluate<number>(
	"inputs.prices.reduce((a, b) => a + b, 0)",
	{ inputs: { prices: [5, 10, 27] } },
);
console.log(total.outcome === "succeeded" ? total.value : total.error); // 42

TypeScript

secure-exec/typescript has the same execute, evaluate, and executeFile functions for TypeScript source. They strip types rather than check them, and a separate check type-checks without running anything. That is useful for validating model-generated code before you execute it.

See TypeScript.

Results

Both functions resolve to a result with an outcome.

outcomeMeaning
succeededThe code finished. evaluate results carry value.
failedThe code threw or exited non-zero.
timed_outtimeoutMs elapsed.
cancelledThe signal you passed was aborted.
// Guest errors are returned, not thrown. Capture stderr to see the stack.
const failed = await evaluate(`JSON.parse("not json")`, {
	output: { capture: "stderr" },
});
if (failed.outcome !== "succeeded") {
	console.log(failed.outcome, failed.stderr?.split("\n")[0]); // failed SyntaxError: ...
}

Guest failures are returned

Anything the guest code does wrong comes back as a result, never as an exception, so untrusted code can never crash your process.

// What the guest code does wrong is returned as a result, never thrown.
const failed = await evaluate(`null.length`, { output: { capture: "stderr" } });
console.log(failed.outcome); // failed
if (failed.outcome !== "succeeded") {
	console.log(failed.error.code); // execution_failed
	console.log(failed.stderr?.split("\n")[0]); // TypeError: Cannot read properties of null ...
}
outcomeerror.codeCause
failedexecution_failedThe code threw, or exited non-zero
failedevaluation_serialization_failedevaluate produced a value that is not JSON
timed_outtimeoutMs elapsed
cancelledThe signal you passed was aborted

The guest’s own message and stack trace arrive on stderr. Capture it with output: { capture: "stderr" } when you want to show it to a user or a model.

Host mistakes are thrown

A call rejects only when the host asked for something that cannot run.

// What the host asks for wrong is thrown, as a typed error with a stable code.
const vm = await createVm();
const context = await vm.createContext();
const slow = context.evaluate(
	"new Promise((resolve) => setTimeout(resolve, 500))",
);
try {
	await context.evaluate("1 + 1"); // The context is still busy with `slow`.
} catch (error) {
	if (error instanceof SidecarRejectedError) {
		console.log(error.detail.code); // execution_busy
	}
}
await slow;
await vm.dispose();
ErrorWhen
SidecarRejectedErrorThe sidecar refused the request. error.detail.code says why
SidecarProcessExitedThe sidecar process died
SidecarSilenceTimeoutThe sidecar stopped responding
KernelErrorA host-side filesystem or process operation failed, with a POSIX-style code
TypeError / validation errorAn option is missing or malformed

Common detail.code values on SidecarRejectedError:

CodeMeaning
execution_busyThe context is already running a call
context_not_foundThe context was disposed, or its VM was

Errors that hit a limit name the limit and the option that raises it. See Resource Limits.

Options

OptionPurpose
inputsJSON values the code reads from inputs
timeoutMsStop the code after this long. Set it for any code you did not write
signalAn AbortSignal that cancels the call
output, onStdout, onStderrCapture or stream output
env, cwd, args, stdinThe process environment the code sees. The working directory defaults to /workspace
filePathThe path the code reports in stack traces and resolves imports from

The top-level functions also take VM options, such as permissions, limits, and mounts, because each call creates its own VM. To keep anything between calls, create a VM and call the same methods on vm.javascript.

Edit this page Last updated September 21, 2026