展示HN:Zerobox – 用文件和网络限制沙箱化任何命令
Show HN: Zerobox – Sandbox any command with file, network, credential controls

原始链接: https://github.com/afshinm/zerobox

## Zerobox:轻量级、安全的代码沙箱 Zerobox是一个跨平台工具(macOS, Linux,计划支持Windows),用于安全地运行不受信任的代码,由OpenAI Codex的沙箱运行时提供支持。它采用“默认拒绝”原则,除非明确允许,否则会阻止文件访问、网络请求和环境变量继承。 主要特性包括:通过`--secret`和`--secret-host`进行**凭证注入**(传递API密钥,而无需将其暴露给进程),使用`--allow-read`和`--allow-write`进行**文件访问控制**,以及使用`--allow-net`和`--deny-net`进行**网络过滤**。Zerobox提供灵活的环境控制,允许继承所有、特定或不继承父环境变量。 它旨在安全地执行AI生成的代码、运行构建和进行测试,开销最小(~10毫秒,~7MB)。提供了一个TypeScript SDK用于程序化控制。安装通过shell脚本简单,使用通过命令行标志或SDK直接。Zerobox优先考虑安全性,防止在运行潜在有害代码时发生数据泄露和文件损坏。

## Zerobox:本地命令沙箱 Zerobox 是一款新的跨平台 CLI 工具,使用 Rust 编写,允许用户对任何命令进行沙箱隔离,并限制文件和网络访问。它由 afshinmeh 创建,灵感来自 Deno 的安全模型(默认拒绝),为本地沙箱提供了一种轻量级的替代方案,无需虚拟机或 Docker。 Zerobox 通过封装命令工作,利用原生操作系统沙箱(如 Linux 上的 BubbleWrap),并采用中间人代理来阻止网络访问并安全地注入密钥——在需要之前用占位符替换敏感值。这使得运行潜在的不受信任代码(如 AI 代理)变得安全。 开发者强调其易用性和跨不同操作系统的稳定功能。未来的开发目标是集成针对特定 AI 代理(如 Claude 和 OpenClaw)的预配置策略。鼓励提供反馈,特别是来自那些使用本地 AI 代理和工具的人。 项目 GitHub 地址:[github.com/afshinm](https://github.com/afshinm) 演示视频:[https://www.youtube.com/watch?v=wZiPm9BOPCg](https://www.youtube.com/watch?v=wZiPm9BOPCg)
相关文章

原文

Lightweight, cross-platform process sandboxing powered by OpenAI Codex's sandbox runtime.

  • Deny by default: Writes, network, and environment variables are blocked unless you allow them
  • Credential injection: Pass API keys that the process never sees. Zerobox injects real values only for approved hosts
  • File access control: Allow or deny reads and writes to specific paths
  • Network filtering: Allow or deny outbound traffic by domain
  • Clean environment: Only essential env vars (PATH, HOME, etc.) are inherited by default
  • TypeScript SDK: import { Sandbox } from "zerobox" with a Deno-style API
  • Cross-platform: macOS and Linux. Windows support planned
  • Single binary: No Docker, no VMs, ~10ms overhead

Zerobox architecture

curl -fsSL https://raw.githubusercontent.com/afshinm/zerobox/main/install.sh | sh
git clone https://github.com/afshinm/zerobox && cd zerobox
./scripts/sync.sh && cargo build --release -p zerobox

Run a command with no writes and no network access:

zerobox -- node -e "console.log('hello')"

Allow writes to a specific directory:

zerobox --allow-write=. -- node script.js

Allow network to a specific domain:

zerobox --allow-net=api.openai.com -- node agent.js

Pass a secret to a specific host and the inner process never sees the real value:

zerobox --secret OPENAI_API_KEY=sk-proj-123 --secret-host OPENAI_API_KEY=api.openai.com -- node agent.js

Same thing with the TypeScript SDK:

import { Sandbox } from "zerobox";

const sandbox = Sandbox.create({
  secrets: {
    OPENAI_API_KEY: {
      value: process.env.OPENAI_API_KEY,
      hosts: ["api.openai.com"],
    },
  },
});

const output = await sandbox.sh`node agent.js`.text();

Secrets are API keys, tokens, or credentials that should never be visible inside the sandbox. The sandboxed process sees a placeholder in the environment variable and the real value is substituted at the network proxy level only for requested hosts:

sandbox process: echo $OPENAI_API_KEY
  -> ZEROBOX_SECRET_a1b2c3d4e5...  (placeholder)

sandbox process: curl -H "Authorization: Bearer $OPENAI_API_KEY" https://api.openai.com/...
  -> proxy intercepts, replaces placeholder with real key
  -> server receives: Authorization: Bearer sk-proj-123

Pass a secret with --secret and restrict it to a specific domain with --secret-host:

zerobox --secret OPENAI_API_KEY=sk-proj-123 --secret-host OPENAI_API_KEY=api.openai.com -- node app.js

Without --secret-host, the secret is pass to all domains:

zerobox --secret TOKEN=abc123 -- node app.js

You can also pass multiple secrets with different domains:

zerobox \
  --secret OPENAI_API_KEY=sk-proj-123 --secret-host OPENAI_API_KEY=api.openai.com \
  --secret GITHUB_TOKEN=ghp-456 --secret-host GITHUB_TOKEN=api.github.com \
  -- node app.js

Node.js fetch does not respect HTTPS_PROXY by default. When running Node.js inside a sandbox with secrets, make sure to pass the --use-env-proxy argument.

import { Sandbox } from "zerobox";

const sandbox = Sandbox.create({
  secrets: {
    OPENAI_API_KEY: {
      value: process.env.OPENAI_API_KEY,
      hosts: ["api.openai.com"],
    },
    GITHUB_TOKEN: {
      value: process.env.GITHUB_TOKEN,
      hosts: ["api.github.com"],
    },
  },
});

await sandbox.sh`node agent.js`.text();

By default, only essential variables are passed to the sandbox e.g. PATH, HOME, USER, SHELL, TERM, LANG.

Inherit all parent env vars

The --allow-env flag allows all parent environment variables to be inherited by the sandboxed process:

zerobox --allow-env -- node app.js

Inherit specific env vars only

zerobox --allow-env=PATH,HOME,DATABASE_URL -- node app.js
zerobox --allow-env --deny-env=AWS_SECRET_ACCESS_KEY -- node app.js

or set a specific variable:

zerobox --env NODE_ENV=production --env DEBUG=false -- node app.js
const sandbox = Sandbox.create({
  env: { NODE_ENV: "production" },
  allowEnv: ["PATH", "HOME"],
  denyEnv: ["AWS_SECRET_ACCESS_KEY"],
});

Run AI-generated code safely

Run AI generated code without risking file corruption or data leaks:

zerobox -- python3 /tmp/task.py

Or allow writes only to an output directory:

zerobox --allow-write=/tmp/output -- python3 /tmp/task.py

Or via the TypeScript SDK:

import { Sandbox } from "zerobox";

const sandbox = Sandbox.create({
  allowWrite: ["/tmp/output"],
  allowNet: ["api.openai.com"],
});

const result = await sandbox.sh`python3 /tmp/task.py`.output();
console.log(result.code, result.stdout);

Each AI tool call can also be sandboxed individually. The parent agent process runs normally and only some operations are sandboxed:

import { Sandbox } from "zerobox";

const reader = Sandbox.create();
const writer = Sandbox.create({ allowWrite: ["/tmp"] });
const fetcher = Sandbox.create({ allowNet: ["example.com"] });

const data = await reader.js`
  const content = require("fs").readFileSync("/tmp/input.txt", "utf8");
  console.log(JSON.stringify({ content }));
`.json();

await writer.js`
  require("fs").writeFileSync("/tmp/output.txt", "result");
  console.log("ok");
`.text();

const result = await fetcher.js`
  const res = await fetch("https://example.com");
  console.log(JSON.stringify({ status: res.status }));
`.json();

Full working examples:

Protect your repo during builds

Run a build script with network access:

zerobox --allow-write=./dist --allow-net -- npm run build

Run tests with no network and catch accidental external calls:

zerobox --allow-write=/tmp -- npm test
import { Sandbox } from "zerobox";

const sandbox = Sandbox.create({ allowWrite: ["/tmp"] });
const output = await sandbox.sh`echo hello`.text();
const data = await sandbox.sh`cat data.json`.json();

Raw output (doesn't throw on non-zero exit)

const result = await sandbox.sh`exit 42`.output();
// { code: 42, stdout: "", stderr: "" }
await sandbox.exec("node", ["-e", "console.log('hi')"]).text();
const data = await sandbox.js`
  console.log(JSON.stringify({ sum: 1 + 2 }));
`.json();

Non-zero exit codes throw SandboxCommandError:

import { Sandbox, SandboxCommandError } from "zerobox";

const sandbox = Sandbox.create();
try {
  await sandbox.sh`exit 1`.text();
} catch (e) {
  if (e instanceof SandboxCommandError) {
    console.log(e.code);   // 1
    console.log(e.stderr);
  }
}
const controller = new AbortController();
await sandbox.sh`sleep 60`.text({ signal: controller.signal });

Sandbox overhead is minimal, typically ~10ms and ~7MB:

Command Bare Sandboxed Overhead Bare Mem Sandbox Mem
echo hello <1ms 10ms +10ms 1.2 MB 8.4 MB
node -e '...' 10ms 20ms +10ms 39.3 MB 39.1 MB
python3 -c '...' 10ms 20ms +10ms 12.9 MB 13.0 MB
cat 10MB file <1ms 10ms +10ms 1.9 MB 8.4 MB
curl https://... 50ms 60ms +10ms 7.2 MB 8.4 MB

Best of 10 runs with warmup on Apple M5 Pro. Run ./bench/run.sh to reproduce.

Platform Backend Status
macOS Seatbelt (sandbox-exec) Fully supported
Linux Bubblewrap + Seccomp + Namespaces Fully supported
Windows Restricted Tokens + ACLs + Firewall Planned
Flag Example Description
--allow-read <paths> --allow-read=/tmp,/data Restrict readable user data to listed paths. System libraries remain accessible. Default: all reads allowed.
--deny-read <paths> --deny-read=/secret Block reading from these paths. Takes precedence over --allow-read.
--allow-write [paths] --allow-write=. Allow writing to these paths. Without a value, allows writing everywhere. Default: no writes.
--deny-write <paths> --deny-write=./.git Block writing to these paths. Takes precedence over --allow-write.
--allow-net [domains] --allow-net=example.com Allow outbound network. Without a value, allows all domains. Default: no network.
--deny-net <domains> --deny-net=evil.com Block network to these domains. Takes precedence over --allow-net.
--env <KEY=VALUE> --env NODE_ENV=prod Set env var in the sandbox. Can be repeated.
--allow-env [keys] --allow-env=PATH,HOME Inherit parent env vars. Without a value, inherits all. Default: only PATH, HOME, USER, SHELL, TERM, LANG.
--deny-env <keys> --deny-env=SECRET Drop these parent env vars. Takes precedence over --allow-env.
--secret <KEY=VALUE> --secret API_KEY=sk-123 Pass a secret. The process sees a placeholder; the real value is injected at the proxy for approved hosts.
--secret-host <KEY=HOSTS> --secret-host API_KEY=api.openai.com Restrict a secret to specific hosts. Without this, the secret is substituted for all hosts.
-A, --allow-all -A Grant all filesystem and network permissions. Env and secrets still apply.
--no-sandbox --no-sandbox Disable the sandbox entirely.
--strict-sandbox --strict-sandbox Require full sandbox (bubblewrap). Fail instead of falling back to weaker isolation.
--debug --debug Print sandbox config and proxy decisions to stderr.
-C <dir> -C /workspace Set working directory for the sandboxed command.
-V, --version --version Print version.
-h, --help --help Print help.

Apache-2.0

联系我们 contact @ memedata.com