Node.js Sandbox

Run untrusted JavaScript/TypeScript in an isolated sandbox, evaluate model-generated code, run a build, or execute a user script under a file system and network policy you control. The node image ships Node.js ready to run.

Run a snippet

index.ts
import { getOrCreateSandbox } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("node", { image: "node" });

const result = await sandbox.exec(["node", "-e", "console.log(6 * 7)"]);
console.log(result.stdout.trim()); // "42"

Run a script

Write the code into the sandbox, then run it:

index.ts
await sandbox.writeFile("/workspace/main.js", `
const data = Array.from({ length: 5 }, (_, i) => ({ n: i, sq: i * i }));
console.log(JSON.stringify(data));
`);

const { stdout } = await sandbox.exec(["node", "/workspace/main.js"], { cwd: "/workspace" });
console.log(JSON.parse(stdout));

Install only the packages you allow

By default the sandbox can't reach the network. allowedNpmPackages generates the exact egress rules npm needs for the packages you name, any other install is blocked.

index.ts
import { getOrCreateSandbox, allowedNpmPackages } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("node", {
  image: "node",
  egress: [...allowedNpmPackages("lodash")],
});

await sandbox.exec(["npm", "install", "lodash"], { cwd: "/workspace" });
const { stdout } = await sandbox.exec(["node", "-e", "console.log(require('lodash').chunk([1,2,3,4], 2))"], {
  cwd: "/workspace",
});
console.log(stdout.trim());

The allowed* package helpers ship in the TypeScript and Python clients. In Go, add the equivalent npm egress rules to SandboxConfig.Egress directly.

Keep a Node session warm

For repeated evaluations, keep one process alive with execStream and stream code into its stdin, module state persists across calls.

End-to-end: an agent that runs JavaScript

Wire the sandbox up as an agent's code interpreter. Give the model a run_node tool whose handler executes the code in the sandbox and feeds the output back, the model writes code, sees the result, and iterates until it has an answer. Everything the model runs stays isolated in the sandbox.

index.ts
import Anthropic from "@anthropic-ai/sdk";
import { getOrCreateSandbox } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("node-agent", { image: "node" });
const anthropic = new Anthropic(); // reads ANTHROPIC_API_KEY

const runNode = {
  name: "run_node",
  description: "Execute JavaScript with Node.js in a sandbox and return its stdout/stderr.",
  input_schema: {
    type: "object",
    properties: { code: { type: "string" } },
    required: ["code"],
  },
} as const;

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Sum the numbers 1 through 100 and print just the total." },
];

while (true) {
  const res = await anthropic.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    tools: [runNode],
    messages,
  });
  messages.push({ role: "assistant", content: res.content });

  const toolUse = res.content.find((b) => b.type === "tool_use");
  if (res.stop_reason !== "tool_use" || !toolUse) {
    const text = res.content.find((b) => b.type === "text");
    console.log("answer:", text?.text); // "answer: 5050"
    break;
  }

  // Run the model's code inside the sandbox and return the result.
  const { code } = toolUse.input as { code: string };
  const { stdout, stderr, exit_code } = await sandbox.exec(["node", "-e", code]);

  messages.push({
    role: "user",
    content: [{
      type: "tool_result",
      tool_use_id: toolUse.id,
      content: stdout || stderr,
      is_error: exit_code !== 0,
    }],
  });
}

The loop is the whole pattern: model → run_node → sandbox → result → model, repeating until the model stops calling the tool. Swap node -e for writeFile + node file.js to run longer programs, or add allowedNpmPackages so the code can npm install a fixed set of libraries.


Next: Bash Sandbox