Run untrusted code

Execute agent- or user-generated code in a disposable box with no container, client-side or server-side.

You have code you didn't write — generated by an AI agent, pasted by a user, or pulled from somewhere — and you want to run it without handing it your machine. Lifo gives you a disposable box with a real filesystem, shell, and Node, but no access to the host: no native binaries, no host filesystem (unless you explicitly mount one), and everything scoped to that box. The same code runs client-side in a browser tab or server-side in Node.

Run a snippet and capture the result

import { Sandbox } from "@lifo-sh/core";

const sandbox = await Sandbox.create();

await sandbox.fs.writeFile("/home/user/snippet.js", untrustedSource);

const { stdout, stderr, exitCode } = await sandbox.commands.run(
  "node /home/user/snippet.js",
);

The code runs inside the box's Node runtime. It can read and write the box's virtual filesystem, spawn processes, and use npm — but it can't touch your host, because those APIs are reimplemented against the box's in-memory state, not your machine.

Put a leash on it: timeout + cancellation

Untrusted code can loop forever or run away. Bound it with a timeout and an AbortSignal:

const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000); // hard stop after 5s

const result = await sandbox.commands.run("node /home/user/snippet.js", {
  timeout: 5_000,
  signal: controller.signal,
  onStdout: (chunk) => forwardToUser(chunk), // stream output as it comes
});

Commands honor ctx.signal, so aborting stops the run promptly (see Processes).

One box per task

Isolation comes from using a fresh box per untrusted task and throwing it away after. Boxes don't share filesystems, ports, or processes, so two runs can't see each other:

async function runIsolated(source: string) {
  const box = await Sandbox.create();
  try {
    await box.fs.writeFile("/tmp/run.js", source);
    return await box.commands.run("node /tmp/run.js", { timeout: 10_000 });
  } finally {
    box.kernel /* drop the reference; nothing persists unless you asked it to */;
  }
}

In Node you can run many boxes in one process; a standalone binary that isolates VMs per request is on the roadmap.

What the sandbox does and doesn't protect

  • Contained by default — no host filesystem, no host network beyond the CORS proxy / tunnels you opt into, no native binaries.
  • You control the surface — outbound fetch only reaches what you allow; the host filesystem is only visible if you mount it.
  • Not a security boundary against the JS engine itself — untrusted code runs in your JS runtime, so treat it like any in-process sandbox: cap time and memory, and for hard multi-tenant isolation run each box in its own process (server-side) or tab (client-side).

Next