Deep Agents 5. FilesystemPermission、Sandbox 与安全代码执行


1. 为什么代码执行需要独立的安全模型

上一篇介绍了虚拟文件系统与 Backend。当 Backend 具备命令执行能力后,Agent 可以运行测试、处理数据、构建项目并生成报告,但风险也从“回答不准确”升级成了“在真实系统中产生副作用”。

python analyze.py
pip install some-package
git clone https://example.com/project.git
curl https://example.com/script.sh | sh

命令本身不是唯一风险源。用户输入、网页内容、MCP 返回值、Skill、依赖包和模型输出都可能影响最终命令。只要其中存在一段不可信内容,整条执行链就不能按照普通业务代码对待。

1.1 Sandbox 是封闭实验室,而不是绝对安全证明

可以把 Sandbox 类比为封闭实验室:隔离的目的不是保证内部永不出错,而是限制错误或攻击的影响范围。

一个远程容器即使与 Host 隔离,仍然可能发生:

  • Prompt Injection 诱导 Agent 执行错误操作;
  • 注入 Sandbox 的密钥被代码读取并输出;
  • 开放网络被用于外传数据或访问内网;
  • 无限循环、Fork Bomb 或超大日志耗尽资源;
  • 多租户复用工作区造成数据串读;
  • 恶意依赖在构建期间运行安装脚本;
  • 恶意产物下载后重新跨越隔离边界。

因此,Sandbox 只是纵深防御的一层。它必须与 Tool 权限、人工审批、网络策略、资源配额、密钥边界、产物检查和生命周期清理共同使用。

1.2 先画出信任边界

Sandbox 信任边界

在选择 Provider 之前,先列出资产、入口和允许的数据流:

需要保护的对象 典型风险 首要控制
Host 文件和进程 读取 .env、SSH Key,修改源码,终止服务 真实执行隔离,不挂载无关目录
内部网络 访问 Metadata Endpoint、扫描内网服务 默认禁网或目的地白名单
第三方系统 使用 Token 修改数据、产生费用 短期凭据、窄接口 Tool、HITL
其他租户 缓存、进程和历史文件串读 独立 Sandbox、身份绑定、TTL
Agent 上下文 敏感输出重新发送给模型 输出过滤、最小上下文、审计
下载产物 宏、脚本、压缩炸弹、路径穿越 白名单下载、类型和恶意内容扫描

计算两个整数、分析内部财务 CSV、编译陌生 Git 仓库,所需的隔离等级完全不同。安全设计必须从威胁模型出发,而不是看到 “Sandbox” 这个名称就默认可信。

1.3 四类控制各自解决什么问题

控制 回答的问题 无法独立解决的问题
FilesystemPermission 内置文件 Tool 能访问哪些虚拟路径 Shell、自定义 Tool、MCP 和 SDK 直接访问
interrupt_on / HITL 哪次敏感 Tool Call 需要人审查 审批后代码在什么环境运行
Sandbox 命令在哪个隔离环境执行 路径业务规则、网络出口和凭据最小化
Backend Policy 自定义校验、审计、限流和内容检查 OS 级隔离本身

例如,read_file 被禁止读取 /secrets/token,不代表 execute(“cat /secrets/token”) 也会被禁止;两者走的是不同能力入口。

2. FilesystemPermission 的规则与真实边界

本文主线锁定 deepagents==0.6.12。以下默认行为、Tool 清单和异常类型均以这一安装版本的源码与测试为准;快速演进项目不能把“当前官网”自动等同于“旧版本运行时”。

2.1 三个字段

from deepagents import FilesystemPermission

rule = FilesystemPermission(
    operations=["read", "write"],
    paths=["/workspace/**"],
    mode="allow",
)
字段 可选值 含义
operations read、write 一条规则可以覆盖一种或两种操作
paths 绝对 Glob 列表 支持 **、{a,b} 等匹配语法
mode allow、deny、interrupt 放行、拒绝或进入人工审批

路径 Pattern 必须以 / 开头,不能包含 ..,~ 也不受支持。错误配置会在构造规则时尽早失败:

FilesystemPermission(
    operations=["read"],
    paths=["relative/**"],  # ValueError
)

2.2 锁定版本与当前文档的 delete 差异

这是本章最容易被版本变化误导的地方。

信息来源 read 覆盖 write 覆盖
本文安装的 deepagents==0.6.12 ls、read_file、glob、grep write_file、edit_file
当前官方 Permissions 文档 上述读取 Tool write_file、edit_file、delete

本文运行时的 _DEFAULT_FS_TOOL_OPS 和实际 Tool 列表都没有 delete。P05 测试明确断言了这一点,避免把较新文档中的能力倒写成 0.6.12 已有功能。

较新版本已经把内置 delete 纳入 write 权限,并对目录执行“目标路径及所有后代均通过才整体删除”的检查。升级后应增加以下测试:

  • 删除允许路径;
  • 删除受保护文件;
  • 删除包含受保护后代的目录时整体失败;
  • 删除需要审批的目录时先中断、审批后再执行。

如果必须停留在 0.6.12,不要自行暴露一个无鉴权的删除 Tool。自定义删除能力必须在 Tool 内完成路径归一化、根目录约束、后代检查、审批和审计,因为 FilesystemPermission 不会自动保护自定义 Tool。

相关版本说明可对照 Deep Agents Permissions

2.3 第一条匹配规则生效,未匹配默认允许

规则按声明顺序检查,第一个同时匹配 operation 和 path 的规则立即生效。下面的宽泛规则会提前放行 .env:

wrong_order = [
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/**"],
        mode="allow",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/.env"],
        mode="deny",
    ),
]

另一个容易误解的规则是:没有任何规则匹配时默认 allow。仅允许 /workspace/** 并不会自动拒绝 /outside/**,最小权限策略必须在末尾增加兜底拒绝。

PERMISSIONS = [
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/.env", "/secrets/**"],
        mode="deny",
    ),
    FilesystemPermission(
        operations=["write"],
        paths=["/reports/**"],
        mode="interrupt",
    ),
    FilesystemPermission(
        operations=["read"],
        paths=["/workspace/**", "/reports/**"],
        mode="allow",
    ),
    FilesystemPermission(
        operations=["write"],
        paths=["/workspace/**"],
        mode="allow",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/**"],
        mode="deny",
    ),
]

这组策略的确定性矩阵如下:

操作 路径 结果
读取 /workspace/readme.md allow
写入 /workspace/result.md allow
读取 /workspace/.env deny
写入 /secrets/token.txt deny
写入 /reports/final.md interrupt
读写 /outside/** deny

FilesystemPermission 决策流程

2.4 deny 必须同时验证消息和副作用

在锁定版本中,直接调用内置 write_file 底层函数,拒绝结果是 status=”error” 的 ToolMessage,Backend 不会收到写入请求:

denied = write_file.func(
    file_path="/secrets/token.txt",
    content="must-not-exist",
    runtime=runtime,
)

assert denied.status == "error"
assert "permission denied" in str(denied.content)
assert not (root / "secrets" / "token.txt").exists()

不过 .func 是当前 StructuredTool 的实现入口,不能单独代表长期稳定的 Agent 契约。因此本文还用确定性 Tool Calling 模型走过完整的 Agent → ToolNode → write_file 链路,并断言:

  1. 模型确实生成 write_file Tool Call;
  2. Agent State 中出现错误 ToolMessage;
  3. 目标文件没有产生;
  4. 模型收到 Tool 结果后继续生成最终回答。

这种测试比只比较异常文本更可靠,因为安全控制最终要保证“副作用没有发生”。

2.5 interrupt 的完整恢复流程

interrupt 不是稍后自动重试。应用必须读取 action_requests,为每项请求按顺序提交 decision,再用同一 thread_id 和 Command(resume=…) 从中断点恢复。

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=PERMISSIONS,
    checkpointer=InMemorySaver(),
)
config = {"configurable": {"thread_id": "fs-review-001"}}

paused = agent.invoke(
    {"messages": [{"role": "user", "content": "发布最终报告"}]},
    config=config,
    version="v2",
)

requests = paused.interrupts[0].value["action_requests"]
assert requests[0]["name"] == "write_file"

resumed = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config,
    version="v2",
)

本文版本自动生成的 review config 允许 approve、edit、reject 和 respond。edit 是否应该开放,必须结合具体 Tool 参数决定;不应允许审核人把受控 Tool 改名成另一种能力。多个 Tool Call 同时中断时,decision 数量和顺序必须与 action_requests 一一对应。仅再次调用相同线程不会自动批准。

ls、glob、grep 等批量工具会根据搜索子树是否可能覆盖受保护前缀进行保守判断。应优先使用 /secrets/** 这类带固定前缀的 Pattern;/**/secrets 可能退化到根范围并造成过度中断。

完整恢复协议可参考 Deep Agents Human-in-the-loop

2.6 权限不覆盖哪些入口

在锁定版本中,permissions= 保护的内置 Tool 是:

ls, read_file, glob, grep, write_file, edit_file

它不会自动保护:

  • execute Shell 命令;
  • 自定义 Python Tool;
  • MCP Tool 和 Provider 原生 Tool;
  • Interpreter 通过 PTC 暴露的 Tool;
  • Sandbox SDK 的 upload_files() / download_files();
  • 应用代码直接访问文件、数据库或对象存储。

对 SandboxBackendProtocol Backend 直接配置覆盖 Sandbox default 区域的文件权限,会在 Agent / FilesystemMiddleware 装配阶段抛出 NotImplementedError。这不是 Backend 执行命令时动态拒绝,而是框架拒绝制造一个虚假的路径安全边界。

CompositeBackend 的例外是:权限 Pattern 可以全部落在明确的非 Sandbox route 下。例如 default 是 Sandbox,而 /memories/ 路由到 StoreBackend,则可以限制 /memories/;/workspace/ 或 /** 触及 Sandbox default 时仍会在装配阶段失败。P05 同时测试了允许与拒绝这两种组合。

2.7 SubAgent 是继承还是替换

SubAgent 未声明 permissions 时继承主 Agent 规则;一旦显式声明,就是完整替换,不是追加。

readonly_reviewer = {
    "name": "reviewer",
    "description": "审核报告,不得修改文件",
    "system_prompt": "只检查事实、数字和引用。",
    "permissions": [
        FilesystemPermission(
            operations=["read"],
            paths=["/reports/**"],
            mode="allow",
        ),
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
}

子智能体规则同样需要兜底拒绝。角色是“内部 Reviewer”并不意味着它天然可信。

3. execute 能力与 LocalShellBackend

3.1 SandboxBackendProtocol 是能力协议

在 Deep Agents 类型系统中,实现 SandboxBackendProtocol 表示 Backend 能提供 execute() / aexecute() 和唯一 id,从而使文件中间件可以调用命令执行能力。

协议名中的 Sandbox 不等于实现已经具备容器、VM 或 MicroVM 隔离。隔离强度取决于实际 Provider。LocalShellBackend 就是最明确的反例。

执行结果使用结构化对象:

ExecuteResponse(
    output="...",
    exit_code=0,
    truncated=False,
)

调用方必须同时检查 exit_code、output 和 truncated。看到 stdout 不代表命令成功。

3.2 LocalShellBackend 直接运行宿主 Shell

import os
from deepagents.backends import LocalShellBackend

backend = LocalShellBackend(
    root_dir=workspace.resolve(),
    virtual_mode=True,
    timeout=5,
    max_output_bytes=256,
    env={"PATH": os.environ.get("PATH", "/usr/bin:/bin")},
    inherit_env=False,
)

锁定版本内部使用:

subprocess.run(
    command,
    check=False,
    shell=True,
    capture_output=True,
    stdin=subprocess.DEVNULL,
    text=True,
    timeout=effective_timeout,
    env=self._env,
    cwd=str(self.cwd),
)

这意味着管道、重定向、变量展开、命令替换和其他 Shell 元字符都会生效。P05 在临时目录中执行了固定命令:

printf 'safe fixture' | tr 'a-z' 'A-Z' > shell.txt

结果文件是 SAFE FIXTURE,证明这里具有完整 Shell 语义,而不是参数数组式进程调用。

LocalShellBackend 不仅缺少执行隔离,还会用 shell=True 解释命令。绝不能把用户输入、网页文本或模型生成的未经约束参数拼接进“可信”命令模板。virtual_mode、超时和环境变量白名单都不能阻止命令注入。

它只适合可信个人项目的临时调试,或已经位于独立隔离 Worker 内的受控测试。不适合多租户服务、不可信仓库和持有生产凭据的 Host。

3.3 virtual_mode=True 限制不了 Shell

virtual_mode=True 约束文件 Backend 的虚拟路径解析,但 Shell 仍然可以访问 Host 可见的绝对路径:

cat /etc/hosts
python -c "from pathlib import Path; print(Path.home())"

root_dir 只是命令工作目录,不是 chroot,也不是 mount namespace。P05 只在 Pytest 创建的临时目录里运行确定性命令,这用于验证 API,不能作为生产安全方案。

3.4 环境变量要显式白名单

示例设置 inherit_env=False,只传入执行 Python 所需的 PATH。测试先向 Host 环境加入一个临时敏感变量,再确认子命令看不到它。

不要把 Host 环境整体复制给模型可控进程。云密钥、数据库密码、代理地址、Tracing Token 和 CI 凭据都可能被命令打印或发送到网络。

远程 Sandbox 同样应使用按任务注入的短期凭据。如果任务只是调用某个业务 API,更安全的方案通常是把密钥留在 Host,通过参数受限的 Tool 代为调用,而不是把通用 Token 放进 Sandbox。

3.5 超时与输出上限不是进程隔离

timeout=5
max_output_bytes=256

max_output_bytes 限制保留到返回对象的文本,截断后还会追加说明,所以最终字符串可能略大于 256 字节。应判断 truncated is True,不能只断言字符串长度。

timeout 首先是一次调用的等待上限,不能自动当作完整进程树终止保证。不同 Backend / Provider 对子进程、后台服务和超时清理的语义不同。生产环境仍需要 Provider 级进程管理、PID 限制、cgroup / 配额和 Sandbox 生命周期清理。

4. BaseSandbox 与 Daytona 适配

4.1 BaseSandbox 提供什么抽象

锁定版本的 BaseSandbox 以 execute() 为核心,并在其上实现 ls、read、glob、grep、write 和 edit 等能力。子类的抽象成员是:

id
execute(command, *, timeout=None) -> ExecuteResponse
upload_files(files) -> list[FileUploadResponse]
download_files(paths) -> list[FileDownloadResponse]

仅定义四个同名函数还不够。实现必须遵守准确签名、返回类型、绝对路径、错误和部分失败契约,并处理 Provider 身份、生命周期、超时和异常转换。

BaseSandbox 是适配器基类,不会凭空创造安全隔离。真正边界仍来自容器、MicroVM、虚拟机、内核策略和网络控制。

4.2 Daytona SDK 与 DaytonaSandbox 是两层对象

daytona.Daytona / daytona.Sandbox
    创建和删除环境,提供原生 Process、Session 与文件能力

langchain_daytona.DaytonaSandbox
    把 SDK Sandbox 适配成 Deep Agents SandboxBackendProtocol

本文锁定环境的实际签名是:

Daytona.create(params=None, *, timeout=60, ...) -> Sandbox
Daytona.delete(sandbox, timeout=60) -> None
DaytonaSandbox(*, sandbox, timeout=1800, sync_polling_interval=0.1)

因此示例使用 Client 生命周期 API,而不是未经该版本验证的 sandbox.delete():

from daytona import Daytona
from langchain_daytona import DaytonaSandbox

daytona = Daytona()
sandbox = daytona.create()
try:
    backend = DaytonaSandbox(sandbox=sandbox, timeout=60)
    # upload -> execute -> download
finally:
    daytona.delete(sandbox)

Daytona SDK 会演进;其他版本可能同时提供实例方法。升级时应重新运行 inspect.signature() 和真实云端 Contract Test,不能只复制旧博客中的清理写法。

4.3 Host 和 Agent 有两条文件通道

通道 调用者 用途 权限来源
Agent 文件 Tool 模型 任务中读取、编辑和搜索 Sandbox 文件 Agent Tool / Backend Policy
SDK 上传下载 Host 应用 运行前注入输入,运行后取回产物 应用白名单与身份授权

Host 不应允许模型自由决定任意下载路径。更安全的流程是由应用预先定义输入清单与允许导出的产物清单,然后分别调用 upload_files() 和 download_files()。

4.4 上传、执行、下载和清理

daytona = Daytona()
sandbox = daytona.create()
try:
    backend = DaytonaSandbox(sandbox=sandbox, timeout=60)
    upload = backend.upload_files(
        [("/workspace/input.txt", b"1\n2\n3\n4\n5\n")]
    )[0]
    if upload.error:
        raise RuntimeError(f"upload failed: {upload.error}")

    result = backend.execute(command, timeout=30)
    if result.exit_code != 0:
        raise RuntimeError(f"command failed: {result.output}")

    download = backend.download_files(["/workspace/result.txt"])[0]
    if download.error or download.content is None:
        raise RuntimeError(f"download failed: {download.error}")
    assert download.content.decode() == "15"
finally:
    daytona.delete(sandbox)

Deep Agents 的 DaytonaSandbox Adapter 统一要求传输路径是绝对路径;相对路径返回 invalid_path。这属于 Sandbox Backend Adapter 契约,不等于 Daytona 原生 SDK 的所有文件接口都采用相同路径规则。批量上传下载还应逐项检查 error,因为部分请求可能成功、部分失败。

4.5 本地 Fake SDK 能证明什么

当前环境没有 DAYTONA_API_KEY,因此真实 Daytona Cloud 测试没有执行。本文使用 Fake SDK 驱动真实 langchain_daytona.DaytonaSandbox,覆盖:

  • Session 创建与删除;
  • 命令提交、状态查询和日志读取;
  • 字节上传与下载;
  • 相对路径拒绝;
  • 缺少 DAYTONA_API_KEY 时入口明确失败。

这只能证明本文 Fake SDK 与 Adapter 之间的本地交互路径正确,不能证明真实返回字段、网络错误、日志分页、权限、限流和 SDK / Cloud 版本兼容。真实云端 Contract Test 仍是上线前的必要步骤,本文不会把 Fake 结果写成云端成功记录。

配置真实密钥后可执行:

cd source/_posts/deepagent/examples
export DAYTONA_API_KEY="从密钥管理器注入的值"
.venv_deepagent/bin/python -m p05_permissions_sandbox.daytona_execute

预期脱敏输出是 sandbox result: 15。缺少密钥时程序抛出明确的配置错误,不会创建匿名资源,也不会把其他环境变量输出到日志。

5. 生产 Sandbox 还需要哪些控制

5.1 生命周期与租户隔离

作用域 状态复用 成本 主要风险
每 Tool Call 最低 最高 启动频繁、文件难复用
每 Run 同一任务 较高 多轮对话不能直接续用
每 Thread 同一会话 中等 必须绑定身份并设置 TTL
每 Assistant 跨会话 较低 多租户串读和残留进程风险最高

Thread-scoped 是官方文档重点介绍的常见方案之一:同一对话复用依赖和文件,再通过 TTL 清理。它不是唯一标准答案。最终选择取决于并发、成本、恢复、敏感度和租户隔离要求。

5.2 网络出口和内网访问

Sandbox 隔离 Host 文件,不代表网络默认关闭。允许互联网访问时,Prompt Injection 可以借助 HTTP、DNS、Git Push 或包管理请求外传数据。

生产策略通常包括:

  • 默认拒绝出口,按任务开放域名或服务;
  • 禁止云 Metadata、控制面和内部网段;
  • 包下载通过固定镜像、代理和哈希校验;
  • 记录目的域名、方法、流量和失败原因;
  • 将公开网页抓取与内部敏感数据处理放入不同环境。

5.3 CPU、内存、磁盘和进程

除了命令 timeout,还要设置:

  • CPU 核数或时间配额;
  • 内存上限和 OOM 行为;
  • 临时磁盘容量、inode 与文件数;
  • 最大进程数、线程数和文件描述符;
  • stdout / stderr、上传和下载大小;
  • Sandbox 最长生存时间和后台进程清理。

超时只让调用方返回错误并不够,Provider 必须确保残留进程最终被终止,资源被可靠回收。

5.4 密钥边界

安全优先级从高到低通常是:

  1. 不把密钥放入 Sandbox,通过窄接口 Tool 代办;
  2. 注入短期、单用途、最小范围凭据;
  3. 限制凭据可访问的网络目标和资源;
  4. 禁止凭据进入 stdout、Trace、报告和产物;
  5. 任务结束后立即撤销或等待短 TTL 失效。

持有生产管理员密钥的环境不应直接执行模型生成的代码。

5.5 产物下载会重新穿过信任边界

应用不应自动打开或执行下载文件。至少要:

  • 仅下载预先声明路径;
  • 限制文件数量、单文件和总大小;
  • 校验扩展名、MIME 和 Magic Number;
  • 防止 Zip Slip、符号链接逃逸和压缩炸弹;
  • 扫描脚本、二进制和 Office 宏;
  • 移除报告中的密钥、内部地址和绝对路径;
  • 先写入隔离对象存储,而不是覆盖 Host 项目。

5.6 Docker 不自动等于安全 Sandbox

企业可以基于容器、MicroVM 或虚拟机,并通过 BaseSandbox 适配器建设内部执行环境。但仅仅启动一个 Docker 容器仍然不够:

  • 不挂载 Docker Socket,不使用 privileged;
  • 使用非 root 用户和只读基础文件系统;
  • 仅挂载必要目录,并为任务分配独立临时盘;
  • 收紧 Linux Capability、Seccomp、AppArmor 等策略;
  • 隔离控制面和执行面;
  • 固定镜像摘要并扫描依赖;
  • 设置网络、资源、租户和生命周期限制。

高风险任务可以使用 MicroVM 或独立虚拟机增强内核边界,但仍不能省略网络、密钥与产物控制。

6. QuickJS、Tool 与 Sandbox 怎样选

6.1 QuickJS Interpreter 的定位

当前官方文档把 Interpreters 标记为 Beta。QuickJS 运行在应用进程内的嵌入式 JavaScript Runtime,适合在 Agent Loop 内完成循环、分支、聚合和受控 Tool 编排。默认没有文件、网络和 Agent Tool 能力;通过 Programmatic Tool Calling 暴露的 Tool 构成能力白名单。

本文锁定环境没有安装 langchain-quickjs,也没有执行 PTC 与 HITL 的交互测试,因此不会断言“PTC 一定绕过”或“一定经过”普通 interrupt_on。升级或引入 Interpreter 时,应针对锁定版本分别测试普通 Tool Call 与 PTC Call 是否进入 Middleware 和审批链。

官方能力状态可参考 Deep Agents Interpreters

6.2 多维选型,而不是线性升级

代码执行环境选型矩阵

方案 系统能力 隔离强度 启动成本 典型用途
普通 Tool 单一窄能力 由 Tool 实现决定 API 查询、确定性业务操作
QuickJS JS 控制流和 PTC 同进程能力边界 聚合、循环、受控 Tool 编排
LocalShell 完整 Host Shell 可信本地临时调试
容器 Sandbox Shell、文件、依赖 取决于容器加固 构建、测试、数据处理
MicroVM / 远程 Sandbox 完整执行环境 较强 中到高 多租户、不可信代码、高风险构建

Tool、QuickJS、LocalShell 和远程 Sandbox 不是“越来越高级”的单一直线。应同时评估代码可信度、系统能力、隔离、状态复用、网络、依赖安装和审计要求。

6.3 纵深防御清单

Agent 代码执行纵深防御

完整执行链至少包括:

  1. 输入信任:标记用户、网页、文件和 Skill 中的不可信内容;
  2. Tool Policy:最小化文件和业务能力;
  3. HITL:高风险调用暂停并人工审查;
  4. Sandbox:代码进入隔离执行面;
  5. 资源限制:限制 CPU、内存、进程、时间和磁盘;
  6. 网络策略:默认拒绝或目的地白名单;
  7. 密钥边界:短期、最小权限、按任务注入;
  8. 产物检查:限制路径、大小、格式和恶意内容;
  9. 生命周期:身份绑定、TTL、停止和删除;
  10. 审计:记录 Tool Call、审批、命令、退出码和产物哈希。

每一层都可能失败,不能把其中任何一层单独作为安全证明。

7. 测试、验证状态与故障排查

7.1 锁定环境

组件 版本 验证状态
Python 3.12.11 本地执行
deepagents 0.6.12 源码核对、离线集成测试
langchain 1.3.13 离线集成测试
langchain-daytona 0.0.7 Fake SDK 路径测试
daytona 0.197.0 已安装、签名核对,未连接 Cloud
Daytona Cloud 未执行:缺少 DAYTONA_API_KEY
QuickJS Interpreter 未安装、未验证
本地系统 macOS / arm64 LocalShell 临时目录测试

版本已安装不等于外部服务已验证。表中的验证等级必须与文章结论严格对应。

7.2 实际运行命令与结果

cd source/_posts/deepagent/examples
.venv_deepagent/bin/python -m ruff check p05_permissions_sandbox
.venv_deepagent/bin/pytest p05_permissions_sandbox -q

实际结果:

All checks passed!
......................                                                   [100%]
22 passed

22 项离线测试覆盖:

  1. 七组 allow、deny、interrupt 权限矩阵;
  2. 相对 Pattern 在构造阶段失败;
  3. 底层 write_file.func 的允许与拒绝;
  4. 完整 Agent / ToolNode 路径返回错误 ToolMessage 且无副作用;
  5. interrupt 暂停、action_requests、同线程 Command(resume) 与审批后写入;
  6. 0.6.12 没有内置 delete Tool;
  7. Sandbox default 与全局权限在装配阶段冲突;
  8. Composite 非 Sandbox route 可以独立配置权限;
  9. LocalShell 确定性执行、完整 Shell 语义、环境变量不继承和输出截断;
  10. Daytona Adapter Session、日志、传输与绝对路径;
  11. 缺少 Daytona 密钥时云端入口明确失败。

真实 Daytona Cloud:not run。这 22 项不能被解释成云端服务已经成功创建 Sandbox。

7.3 常见错误

现象 根因 处理方式
/outside 仍可写 未匹配默认 allow 最后一条增加 /** deny
.env 仍可读 宽泛 allow 排在前面 具体 deny 放在首位
找不到内置 delete 运行的是 0.6.12 按版本能力编写测试,升级后重新验收
重复调用线程仍未恢复 没有提交 resume decisions 使用相同 config 与 Command(resume=…)
大量 glob 要求审批 interrupt Pattern 无固定前缀 改成 /prefix/**
自定义 Tool 绕过权限 Permissions 只保护内置文件 Tool 在 Tool 或 Backend Policy 中授权
配权限后 Agent 构造失败 Pattern 触及 Sandbox default 限定 Composite 非 Sandbox route
virtual_mode 后仍可读 Host 它不是 OS 隔离 使用真实 Sandbox
命令有输出但任务失败 未检查 exit_code 检查退出码、输出和截断状态
Daytona 留下资源 异常路径未执行清理 Client delete() 放进 finally
下载后 Host 出现风险 产物跨越隔离边界 白名单下载并扫描

8. 总结

FilesystemPermission 解决的是内置文件 Tool 的虚拟路径规则,不是通用文件 ACL。第一条匹配生效、未匹配默认允许、SubAgent 显式规则会替换父规则,这三点必须用测试固定。deny 要同时检查错误消息和副作用;interrupt 则必须配合 Checkpointer、同一 thread_id、按序 decisions 与 Command(resume) 才能完成闭环。

版本边界同样属于正确性的一部分:当前官方文档已经包含 delete,但本文锁定的 deepagents==0.6.12 运行时没有该 Tool,不能伪造删除测试。升级后应针对目录后代权限重新验收。

SandboxBackendProtocol 表示命令执行能力,不等于安全隔离。LocalShellBackend 使用 shell=True 直接运行 Host Shell,virtual_mode、超时和环境白名单都不能把它变成 Sandbox。生产代码执行应使用经过加固的远程或独立执行环境,并同步控制网络、资源、密钥、产物、生命周期和审计。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录