``` Just Bash: 代理的 Bash ```
Just-bash: Bash for Agents

原始链接: https://github.com/vercel-labs/just-bash

## just-bash:为AI代理提供安全、沙箱化的Bash环境 just-bash是一个TypeScript库,提供模拟的Bash环境,非常适合需要安全隔离执行空间的AI代理。它在内存中运行,并使用虚拟文件系统,防止访问主机系统。 主要特性包括: * **沙箱化:** 限制访问权限,仅限于提供的文件系统,防止意外的系统更改。 * **执行保护:** 防止无限循环和过度递归。 * **可选网络访问:** 支持`curl`,并具有可配置的URL允许列表和方法限制,以进行安全的网络请求。默认禁用。 * **文件系统灵活性:** 提供多种实现方式——内存、覆盖(写时复制)、读写和可挂载,以满足不同的需求。文件甚至可以是函数,用于动态内容。 * **可扩展性:** 可以使用TypeScript定义自定义命令。 * **Vercel沙箱兼容性:** 与`@vercel/sandbox` API兼容,可扩展到完整的虚拟机。 * **CLI工具:** `just-bash`命令行界面提供了一个安全的标准Bash替代方案。 目前处于Beta阶段,旨在进行实验和反馈。它不支持二进制文件或WASM,虽然它能防止基本的DoS攻击,但建议使用操作系统级别的进程隔离来获得强大的安全性。一个专门的工具`bash-tool`,针对AI代理的使用进行了优化。

## Just-Bash: 摘要 Vercel Labs 发布了“just-bash”,一个为 AI 代理设计的 bash 环境,允许它们执行命令。一个新的驱动程序可以连接一个与 S3 同步的完整 Archil 文件系统,提供高性能的数据访问,而无需完整的虚拟机。 讨论的中心在于 bash 是否是代理的*最佳*语言,考虑到它的年代和潜在的代码生成效率低下问题。建议使用替代方案,如 Python(使用受限制的版本,如 Pydantic 的 Monty)、Lua(特别是 Luau 用于沙盒化)和 TypeScript,尽管 bash 拥有大量的训练数据,这仍然是其继续使用的有力论据。 一个关键点是,LLM 似乎能够直观地理解 bash,而对于其他工具(如 Python)则不然,即使提供了明确的指令。人们对潜在的效率低下和安全漏洞表示担忧,一些人提倡使用沙盒虚拟机。还提到了几个相关项目,如 `openfs`(一个用于云存储的 bash 接口)和 `coral`(一个可移植的 shell 运行器)。最终,该项目旨在为代理提供一个可用的环境,即使这意味着接受与标准 bash 的一些不兼容性。
相关文章

原文

A simulated bash environment with an in-memory virtual filesystem, written in TypeScript.

Designed for AI agents that need a secure, sandboxed bash environment.

Supports optional network access via curl with secure-by-default URL filtering.

Note: This is beta software. Use at your own risk and please provide feedback.

  • The shell only has access to the provided file system.
  • Execution is protected against infinite loops or recursion. However, Bash is not fully robust against DOS from input. If you need to be robust against this, use process isolation at the OS level.
  • Binaries or even WASM are inherently unsupported (Use Vercel Sandbox or a similar product if a full VM is needed).
  • There is no network access by default.
  • Network access can be enabled, but requests are checked against URL prefix allow-lists and HTTP-method allow-lists. See network access for details
import { Bash } from "just-bash";

const env = new Bash();
await env.exec('echo "Hello" > greeting.txt');
const result = await env.exec("cat greeting.txt");
console.log(result.stdout); // "Hello\n"
console.log(result.exitCode); // 0
console.log(result.env); // Final environment after execution

Each exec() is isolated—env vars, functions, and cwd don't persist across calls (filesystem does).

const env = new Bash({
  files: { "/data/file.txt": "content" }, // Initial files
  env: { MY_VAR: "value" }, // Initial environment
  cwd: "/app", // Starting directory (default: /home/user)
  executionLimits: { maxCallDepth: 50 }, // See "Execution Protection"
});

// Per-exec overrides
await env.exec("echo $TEMP", { env: { TEMP: "value" }, cwd: "/tmp" });

File values can be functions (sync or async). The function is called on first read and the result is cached — if the file is written to before being read, the function is never called:

const env = new Bash({
  files: {
    "/data/config.json": () => JSON.stringify({ key: "value" }),
    "/data/remote.txt": async () => (await fetch("https://example.com")).text(),
    "/data/static.txt": "always loaded",
  },
});

This is useful for large or expensive-to-compute content that may not be needed.

Extend just-bash with your own TypeScript commands using defineCommand:

import { Bash, defineCommand } from "just-bash";

const hello = defineCommand("hello", async (args, ctx) => {
  const name = args[0] || "world";
  return { stdout: `Hello, ${name}!\n`, stderr: "", exitCode: 0 };
});

const upper = defineCommand("upper", async (args, ctx) => {
  return { stdout: ctx.stdin.toUpperCase(), stderr: "", exitCode: 0 };
});

const bash = new Bash({ customCommands: [hello, upper] });

await bash.exec("hello Alice"); // "Hello, Alice!\n"
await bash.exec("echo 'test' | upper"); // "TEST\n"

Custom commands receive the full CommandContext with access to fs, cwd, env, stdin, and exec for running subcommands.

Four filesystem implementations are available:

InMemoryFs (default) - Pure in-memory filesystem, no disk access:

import { Bash } from "just-bash";
const env = new Bash(); // Uses InMemoryFs by default

OverlayFs - Copy-on-write over a real directory. Reads come from disk, writes stay in memory:

import { Bash } from "just-bash";
import { OverlayFs } from "just-bash/fs/overlay-fs";

const overlay = new OverlayFs({ root: "/path/to/project" });
const env = new Bash({ fs: overlay, cwd: overlay.getMountPoint() });

await env.exec("cat package.json"); // reads from disk
await env.exec('echo "modified" > package.json'); // stays in memory

ReadWriteFs - Direct read-write access to a real directory. Use this if you want the agent to be agle to write to your disk:

import { Bash } from "just-bash";
import { ReadWriteFs } from "just-bash/fs/read-write-fs";

const rwfs = new ReadWriteFs({ root: "/path/to/sandbox" });
const env = new Bash({ fs: rwfs });

await env.exec('echo "hello" > file.txt'); // writes to real filesystem

MountableFs - Mount multiple filesystems at different paths. Combines read-only and read-write filesystems into a unified namespace:

import { Bash, MountableFs, InMemoryFs } from "just-bash";
import { OverlayFs } from "just-bash/fs/overlay-fs";
import { ReadWriteFs } from "just-bash/fs/read-write-fs";

const fs = new MountableFs({ base: new InMemoryFs() });

// Mount read-only knowledge base
fs.mount("/mnt/knowledge", new OverlayFs({ root: "/path/to/knowledge", readOnly: true }));

// Mount read-write workspace
fs.mount("/home/agent", new ReadWriteFs({ root: "/path/to/workspace" }));

const bash = new Bash({ fs, cwd: "/home/agent" });

await bash.exec("ls /mnt/knowledge"); // reads from knowledge base
await bash.exec("cp /mnt/knowledge/doc.txt ./"); // cross-mount copy
await bash.exec('echo "notes" > notes.txt'); // writes to workspace

You can also configure mounts in the constructor:

import { MountableFs, InMemoryFs } from "just-bash";
import { OverlayFs } from "just-bash/fs/overlay-fs";
import { ReadWriteFs } from "just-bash/fs/read-write-fs";

const fs = new MountableFs({
  base: new InMemoryFs(),
  mounts: [
    { mountPoint: "/data", filesystem: new OverlayFs({ root: "/shared/data" }) },
    { mountPoint: "/workspace", filesystem: new ReadWriteFs({ root: "/tmp/work" }) },
  ],
});

For AI agents, use bash-tool which is optimized for just-bash and provides a ready-to-use AI SDK tool:

import { createBashTool } from "bash-tool";
import { generateText } from "ai";

const bashTool = createBashTool({
  files: { "/data/users.json": '[{"name": "Alice"}, {"name": "Bob"}]' },
});

const result = await generateText({
  model: "anthropic/claude-sonnet-4",
  tools: { bash: bashTool },
  prompt: "Count the users in /data/users.json",
});

See the bash-tool documentation for more details and examples.

Vercel Sandbox Compatible API

Bash provides a Sandbox class that's API-compatible with @vercel/sandbox, making it easy to swap implementations. You can start with Bash and switch to a real sandbox when you need the power of a full VM (e.g. to run node, python, or custom binaries).

import { Sandbox } from "just-bash";

// Create a sandbox instance
const sandbox = await Sandbox.create({ cwd: "/app" });

// Write files to the virtual filesystem
await sandbox.writeFiles({
  "/app/script.sh": 'echo "Hello World"',
  "/app/data.json": '{"key": "value"}',
});

// Run commands and get results
const cmd = await sandbox.runCommand("bash /app/script.sh");
const output = await cmd.stdout(); // "Hello World\n"
const exitCode = (await cmd.wait()).exitCode; // 0

// Read files back
const content = await sandbox.readFile("/app/data.json");

// Create directories
await sandbox.mkDir("/app/logs", { recursive: true });

// Clean up (no-op for Bash, but API-compatible)
await sandbox.stop();

After installing globally (npm install -g just-bash), use the just-bash command as a secure alternative to bash for AI agents:

# Execute inline script
just-bash -c 'ls -la && cat package.json | head -5'

# Execute with specific project root
just-bash -c 'grep -r "TODO" src/' --root /path/to/project

# Pipe script from stdin
echo 'find . -name "*.ts" | wc -l' | just-bash

# Execute a script file
just-bash ./scripts/deploy.sh

# Get JSON output for programmatic use
just-bash -c 'echo hello' --json
# Output: {"stdout":"hello\n","stderr":"","exitCode":0}

The CLI uses OverlayFS - reads come from the real filesystem, but all writes stay in memory and are discarded after execution. The project root is mounted at /home/user/project.

Options:

  • -c <script> - Execute script from argument
  • --root <path> - Root directory (default: current directory)
  • --cwd <path> - Working directory in sandbox
  • -e, --errexit - Exit on first error
  • --json - Output as JSON

The interactive shell has full internet access enabled by default, allowing you to use curl to fetch data from any URL. Use --no-network to disable this:

cat, cp, file, ln, ls, mkdir, mv, readlink, rm, rmdir, split, stat, touch, tree

awk, base64, column, comm, cut, diff, expand, fold, grep (+ egrep, fgrep), head, join, md5sum, nl, od, paste, printf, rev, rg, sed, sha1sum, sha256sum, sort, strings, tac, tail, tr, unexpand, uniq, wc, xargs

jq (JSON), python3/python (Python via Pyodide; required opt-in), sqlite3 (SQLite), xan (CSV), yq (YAML/XML/TOML/CSV)

gzip (+ gunzip, zcat), tar

basename, cd, dirname, du, echo, env, export, find, hostname, printenv, pwd, tee

alias, bash, chmod, clear, date, expr, false, help, history, seq, sh, sleep, time, timeout, true, unalias, which, whoami

curl, html-to-markdown

All commands support --help for usage information.

  • Pipes: cmd1 | cmd2
  • Redirections: >, >>, 2>, 2>&1, <
  • Command chaining: &&, ||, ;
  • Variables: $VAR, ${VAR}, ${VAR:-default}
  • Positional parameters: $1, $2, $@, $#
  • Glob patterns: *, ?, [...]
  • If statements: if COND; then CMD; elif COND; then CMD; else CMD; fi
  • Functions: function name { ... } or name() { ... }
  • Local variables: local VAR=value
  • Loops: for, while, until
  • Symbolic links: ln -s target link
  • Hard links: ln target link

When created without options, Bash provides a Unix-like directory structure:

  • /home/user - Default working directory (and $HOME)
  • /bin - Contains stubs for all built-in commands
  • /usr/bin - Additional binary directory
  • /tmp - Temporary files directory

Commands can be invoked by path (e.g., /bin/ls) or by name.

Network access (and the curl command) is disabled by default for security. To enable it, configure the network option:

// Allow specific URLs with GET/HEAD only (safest)
const env = new Bash({
  network: {
    allowedUrlPrefixes: [
      "https://api.github.com/repos/myorg/",
      "https://api.example.com",
    ],
  },
});

// Allow specific URLs with additional methods
const env = new Bash({
  network: {
    allowedUrlPrefixes: ["https://api.example.com"],
    allowedMethods: ["GET", "HEAD", "POST"], // Default: ["GET", "HEAD"]
  },
});

// Allow all URLs and methods (use with caution)
const env = new Bash({
  network: { dangerouslyAllowFullInternetAccess: true },
});

Note: The curl command only exists when network is configured. Without network configuration, curl returns "command not found".

Python support via Pyodide is opt-in due to additional security surface. Enable it explicitly, but be aware of the risk:

const env = new Bash({
  python: true,
});

// Execute Python code
await env.exec('python3 -c "print(1 + 2)"');

// Run Python scripts
await env.exec('python3 script.py');

Note: The python3 and python commands only exist when python: true is configured. Python is not available in browser environments.

The sqlite3 command uses sql.js (WASM-based SQLite) which is fully sandboxed and cannot access the real filesystem:

const env = new Bash();

// Query in-memory database
await env.exec('sqlite3 :memory: "SELECT 1 + 1"');

// Query file-based database
await env.exec('sqlite3 data.db "SELECT * FROM users"');

Note: SQLite is not available in browser environments. Queries run in a worker thread with a configurable timeout (default: 5 seconds) to prevent runaway queries from blocking execution.

The allow-list enforces:

  • Origin matching: URLs must match the exact origin (scheme + host + port)
  • Path prefix: Only paths starting with the specified prefix are allowed
  • HTTP method restrictions: Only GET and HEAD by default (configure allowedMethods for more)
  • Redirect protection: Redirects to non-allowed URLs are blocked
# Fetch and process data
curl -s https://api.example.com/data | grep pattern

# Download and convert HTML to Markdown
curl -s https://example.com | html-to-markdown

# POST JSON data
curl -X POST -H "Content-Type: application/json" \
  -d '{"key":"value"}' https://api.example.com/endpoint

Bash protects against infinite loops and deep recursion with configurable limits:

const env = new Bash({
  executionLimits: {
    maxCallDepth: 100, // Max function recursion depth
    maxCommandCount: 10000, // Max total commands executed
    maxLoopIterations: 10000, // Max iterations per loop
    maxAwkIterations: 10000, // Max iterations in awk programs
    maxSedIterations: 10000, // Max iterations in sed scripts
  },
});

All limits have sensible defaults. Error messages include hints on which limit to increase. Feel free to increase if your scripts intentionally go beyond them.

Parse bash scripts into an AST, run transform plugins, and serialize back to executable bash. Useful for instrumenting scripts (e.g., capturing per-command stdout/stderr) or analyzing them (e.g., extracting command names) before execution.

import { Bash, BashTransformPipeline, TeePlugin, CommandCollectorPlugin } from "just-bash";

// Standalone pipeline — output can be run by any shell
const pipeline = new BashTransformPipeline()
  .use(new TeePlugin({ outputDir: "/tmp/logs" }))
  .use(new CommandCollectorPlugin());
const result = pipeline.transform("echo hello | grep hello");
result.script;             // transformed bash string
result.metadata.commands;  // ["echo", "grep", "tee"]

// Integrated API — exec() auto-applies transforms and returns metadata
const bash = new Bash();
bash.registerTransformPlugin(new CommandCollectorPlugin());
const execResult = await bash.exec("echo hello | grep hello");
execResult.metadata?.commands; // ["echo", "grep"]

See src/transform/README.md for the full API, built-in plugins, and how to write custom plugins.

pnpm test        # Run tests in watch mode
pnpm test:run    # Run tests once
pnpm typecheck   # Type check without emitting
pnpm build       # Build TypeScript
pnpm shell       # Run interactive shell

For AI agents, we recommend using bash-tool which is optimized for just-bash and provides additional guidance in its AGENTS.md:

cat node_modules/bash-tool/dist/AGENTS.md

Apache-2.0

联系我们 contact @ memedata.com