↓ 跳过正文
  1. 归档/

做 RunSeal 的初衷:给本地 Agent 设一道命令执行边界

AI 研读与导出
目录
AI Coding Security - 这篇文章属于一个选集。
§ : 本文

RunSeal 这个项目其实很早就成型了。

核心逻辑和测试早早跑通,当时想顺手写篇记录,结果后来忙着各种业务搬砖,开源整理一拖就拖到了现在。最近大家因为隐私和越界行为,又开始讨论 Agent 的本地安全,索性把这个项目当时的背景、踩过的坑以及技术实现理一遍。


本地命令执行的信任缺口
#

这几年 Coding Agent 成了很多人的主力工具。从早期的 Aider、Open Interpreter,到后来天天挂在后台的 Cursor、Claude Code、Devin、Codex,大家习惯了在终端里把执行权交给 AI:改完代码顺手跑个测试、调个脚本、装个依赖。

但只要去看一眼它跑出来的底层日志,问题很明显:

  1. 它就是你机器上的一个普通子进程。 你的终端有什么权限,它就有什么权限。读写当前项目代码是正常的,但如果顺手读一下 ~/.ssh/id_rsa、扫一眼 ~/.aws/credentials,或者翻翻旁边其他项目的代码,操作系统根本不会拦截。
  2. [Y/n] 确认框容易变成心理安慰。 真让 Agent 排查一个复杂 Bug,一轮任务动辄调用几十次终端命令。没有人能每隔几秒认真看一行 Bash。连续按回车变成机械动作后,一旦被注入恶意指令,最后也是自己按下去的。
  3. 已有的防护方式不够趁手。
    • 扔进 Docker 跑:在 Mac 上通过虚拟机挂载本地目录,文件 I/O 很慢,npm install 能卡半天;本地配好的虚拟环境、编译器缓存和系统依赖用不上;为了下包查文档往往还得开外网,真有恶意 prompt 注入,该往外发的依然能发出去。
    • 正则黑名单:拦截 rm -rf / 或 cat ~/.ssh 看着直接,但稍微用点字符串拼接、Base64 或简单脚本间接执行,静态正则就绕过去了。
    • 社区小脚本:多是 Linux 上的 demo,依赖 bwrap 或 landlock。在 Mac 或 Windows 上用不上,网络策略通常也是二选一:要么拔网线,要么全开。

两边的体验很分裂:要么图省事直接跑,要么为了安全把开发体验搞得很别扭。


RunSeal 的设计范围
#

动手写 RunSeal 时,我在 AGENTS.md 里明确了边界:

RunSeal 是一个 OS-native、受策略约束的本地命令安全执行环境——专注在本地命令隔离,不承接审批流、策略仪表盘或通用自动化框架。

核心目标很明确:在不依赖虚拟机、不牺牲本地开发速度的前提下,在操作系统内核层把 Agent 的执行范围限定在当前工作区内。

flowchart TD
    A[Agent 准备跑命令] --> B[RunSeal CLI / RPC]
    B --> C{检查宿主策略能力}
    C -->|能力不满足| D[Fail-Closed 立即退出报错
不静默放行] C -->|策略校验通过| E[构建 OS 原生执行沙箱] subgraph OS[OS 原生内核边界] E --> F[文件系统: Workspace-Contained
仅限当前目录 + 临时 Root] E --> G[网络受控: Managed Proxy
阻断外部直连与未授权 loopback] end F & G --> H[执行命令并捕获退出状态] H --> I[写入本地 JSONL 审计日志] I --> J[结果返回 Agent]

设计主要围绕这几个方面:

1. 毫秒级原生拉起,不依赖虚拟化
#

RunSeal 不启动 VM,也不用后台常驻 daemon。它直接调用操作系统底层的隔离原语:

  • Windows 作为参考实现:通过受限令牌(Restricted Token)、Job Objects 和专有 ACL 落地,策略到执行计划的映射统一走 PlatformSandboxPlan 契约;
  • macOS / Linux 功能对齐:macOS 用 Seatbelt(sandbox-exec),Linux 用 Bubblewrap 加 Landlock,三级沙箱与三种网络模式全部原生执行,managed proxy 边界也已在两个平台落地(RFC-0019 / RFC-0020)。

因为是系统原生调用,没有虚拟机中转损耗,启动是毫秒级的,本地编译器缓存和工具链照常使用。

2. 工作区隔离(Workspace Containment)
#

在文件系统层面,RunSeal 规范了四个沙箱级别:read-only、workspace-write、workspace-contained,外加显式放弃保证的 danger-full-access。

核心是 workspace-contained:

  • 进程只能看见当前的工作区目录、运行时的私有临时目录、显式声明的只读路径,以及系统必需的最小只读基线;
  • 宿主机上的其他位置(如 ~/.ssh、~/.aws、~/.config,或其他目录的源码),在沙箱里不可见或直接报错;
  • 哪怕 Agent 遇到 Prompt 注入去执行 cat ~/.ssh/id_rsa,或者试图通过路径穿越往外翻,内核都会直接拒绝(实测返回 Operation not permitted)。

3. 受控出网(network.proxy)
#

开发时完全断网并不现实,装依赖和查文档都需要网络。但直接放开公网,外发行为就不可控。

RunSeal 增加了 network.proxy 模式:

  • 限制直连外部公网 IP 或域名;
  • 阻断未授权的本地 loopback 和宿主机 IPC 通信;
  • 所有出网流量强制重定向到指定的 Managed Proxy 端点。

这样代理层可以统一处理白名单路由、凭证过滤和流量审计。

4. 无法满足策略时直接报错(Fail-Closed)
#

做安全隔离,最怕为了让命令跑通而默默放开限制。

如果策略指定了 workspace-contained 和 network.proxy,但当前系统权限不够或版本不支持,RunSeal 会直接 Fail-Closed 报错退出,不会静默降级为未受限环境执行。

项目里配合了黑盒对抗测试套件(RFC-0016),覆盖软链接穿透、父目录逃逸、环境变量污染、孤儿进程清理等常见路径,确保标明 supported 的能力经过了实际验证。


怎么用起来?
#

RunSeal 的 CLI 刻意做得很简单:参数就那么几个,很容易嵌进各种 Agent 自动化脚本或框架里。

1. 查一下当前机器支持什么能力
#

runseal capabilities

输出一段 JSON,告诉你当前机器能保证什么(节选,完整输出还包含 features、capability_probes 等字段):

{
  "platform": "macos",
  "sandbox_levels": {
    "read-only": "supported",
    "workspace-write": "supported",
    "workspace-contained": "supported",
    "danger-full-access": "supported"
  },
  "network_modes": {
    "unmanaged": "supported",
    "disabled": "supported",
    "proxy": "supported"
  }
}

协议与策略版本号则由 runseal version --json 单独给出。

2. 跑一条受限命令
#

把要执行的命令限定在当前工作区,同时彻底断网:

runseal exec \
  --policy workspace-contained \
  --network disabled \
  --cwd /Users/ferstar/myprojects/demo \
  -- python3 test_pipeline.py

如果命令里试图越界读取 ~/.ssh,内核会直接拒绝这次读取。默认模式下终端不会回显任何报错——但命令已经被拦死了,加 --json 就能看到完整结果(节选):

runseal exec --json \
  --policy workspace-contained \
  --network disabled \
  --cwd /Users/ferstar/myprojects/demo \
  -- cat ~/.ssh/id_rsa
{
  "exit_code": 1,
  "stderr": "cat: /Users/ferstar/.ssh/id_rsa: Operation not permitted",
  "sandbox": {"enforced": true, "level": "workspace-contained"}
}

注意 runseal exec 自身的退出码目前固定为 0,子进程的真实退出状态以 JSON 结果和审计日志为准;如果要把结果接进自动化流程,--json 输出结构化结果,--events 则按行流式吐出执行事件。

3. 本地结构化审计
#

每次执行到底用了什么策略、对应的 Canonical Policy Hash 是多少、网络做没做阻断,都会实时落盘成 JSONL 审计记录(节选,每条事件还带有 execution_id、session_id、policy_epoch 等字段):

{"type":"policy.resolved","policy_id":"workspace-contained","policy_hash":"sha256:7cb64a73...","sandbox_level":"workspace-contained","network":{"mode":"disabled","routes":[]},"time":"2026-09-26T11:21:26.967279Z"}
{"type":"execution.finished","exit_code":1,"status":"finished","time":"2026-09-26T11:21:26.971042Z"}

一点想法
#

前阵子 ZCode 那个静默上传代码仓的事情闹得沸沸扬扬,其实那只是把“本地 Agent 权限裸奔”这个隐患,以最戏剧化的一种方式曝光在大众面前而已。

AI 编程工具迭代得太快了。模型越来越聪明,自主执行的链路也越来越长。但我们不能一边享受着几倍的代码产出速度,一边把主机的“万能钥匙”全押在服务商的道德自觉或者一份不可证伪的用户协议上。

平时在开发机上干活,没必要天天跟防贼一样提心吊胆,但也别等哪天真的被端了底裤才想起来穿衣服。

把不可逾越的物理边界稳稳卡在操作系统内核层,该放行的放行,该掐死的掐死——这才是让 Agent 能够放手跑下去的长久之计。

(RunSeal 的 RFC 规范和代码都在 GitHub 上公开维护,感兴趣的可以来看看:github.com/runseal-labs)

AI Coding Security - 这篇文章属于一个选集。
§ : 本文

相关文章