拓冰建站拓冰建站
首页 / 资讯中心 / 正文

DeerFlow Lark CLI Broker(Pattern B):用 Sidecar 镜像把飞书凭据从 Agent 沙箱中隔离出去

DeerFlow Lark CLI BrokerPattern B用 Sidecar 镜像把飞书凭据从 Agent 沙箱中隔离出去【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow在 DeerFlow 的 K8s 沙箱部署中Agent 需要在沙箱里调用lark-cli完成飞书集成而每个用户的appSecret与 OAuth token 又绝不能出现在沙箱文件系统里——否则一个被提示注入的 Agent 就能直接cat走凭据。本文以 docker/lark-cli-broker/README.md 为主线完整拆解 Pattern BBroker方案的镜像构建、双模入口、shim 转发机制、provisioner 接线、loopback HTTP 契约与子命令拒绝清单并结合 lark_broker.py 与 provisioner/app.py 的源码实现说明其底层原理与生产加固要点。1. 为什么需要 BrokerPattern A 的沙箱凭据暴露面背景来自 issue #4338 的修复方案。DeerFlow 此前采用Pattern A把lark-cli二进制预置进沙箱运行时目录但每用户的凭据目录仍然以卷挂载方式进入沙箱容器——config目录里有长期有效的appSecretdata目录里有 OAuth token。由于 Agent 在沙箱内拥有bash工具任何拿到 shell 的进程包括被恶意网页内容提示注入的 Agent都能直接读取这些明文文件。Pattern B 的核心思路是凭据留在外面命令面留在里面。一个长驻的 sidecar 容器持有真实的lark-cli二进制和凭据目录只通过 loopback HTTP 提供“命令执行”这一最小接口沙箱里放一个极小的lark-clishim垫片把 argv/stdin 原样转发给 sidecar。结果是原始的appSecret/ OAuth token 文件在沙箱文件系统中根本不存在被攻陷的沙箱无法cat/外泄它们但任何合法的lark-cli子命令仍然可以正常执行——对用户和 Agent 来说命令面完全无感。这正是 lark_broker.py 模块开头文档化的设计目标broker 侧半部分是一个“拥有lark-cli 凭据、只暴露命令面的长驻进程”且整个模块仅用 Python 3 标准库实现这样同一个模块既能跑在最小化的 broker sidecar 镜像里也无需安装任何第三方依赖。2. 一个镜像、两种角色install-shim与serveBroker 镜像通过第一个 CLI 参数分发两种模式见 entrypoint.sh 中的case分发逻辑模式角色行为install-shim destinit 容器把 launcher Python shim 运行时标记文件.deerflow-lark-cli-runtime.json内容为{version: ..., kind: shim}写入共享emptyDirdest默认/mnt/integrations/lark-cli/runtime然后以退出码 0 结束serve默认CMDsidecar在127.0.0.1:8788上运行 broker HTTP 服务加载真实lark-cli凭据环境变量指向 sidecar 独占的/var/lark/{config,data}挂载两种模式最终都收敛到同一个标准库模块entrypoint 只是exec python3 /opt/broker/lark_broker.py [install-shim ...]镜像里没有其它运行时代码。2.1install-shim产出与 Pattern A 相同的布局install-shim的实现是 lark_broker.py 中的install_shim()它向目标目录写入三个东西bin/lark-cli—— 位于PATH上的可执行文件实际是一个/bin/shlauncherbin/lark-cli-shim.py—— 真正干活的 Python shim 本体与 launcher 同目录.deerflow-lark-cli-runtime.json—— 运行时标记文件kind字段为shim让运行时校验器知道linux-*真实二进制有意缺席真实二进制在 sidecar 里避免误判为安装失败。这个布局与 Pattern A 的 init 镜像产物完全同构因此沙箱里lark-cli出现在lark_cli_env_overlay(sandbox_pathsTrue)所指向PATH的同一位置上层代码无需为两种模式分叉。2.2 Launcher 与 Shim 分离为什么bin/lark-cli不是 Python 脚本README 强调了一个工程细节PATH上的可执行文件bin/lark-cli是一个/bin/shlauncher它负责解析出 Python 3 解释器后exec同目录下的 shim 本体bin/lark-cli-shim.py。分离的动机在 lark_broker.py 的LARK_CLI_BROKER_LAUNCHER_TEMPLATE常量中写得很清楚shim 本体需要发 HTTP 请求必须是 Python但 Broker 模式是opt-in的如果直接把 shim 做成#!/usr/bin/env python3脚本任何PATH上没有python3的沙箱镜像都会让每次lark-cli调用变成晦涩的 ENOEXEC/exit 127而且这种故障可能在 CI 中漏网、只在运维侧暴露launcher 只用 shell 内建命令command -v逐个探测python3、python即使沙箱PATH为空、仅靠DEERFLOW_LARK_BROKER_PYTHON环境变量钉住解释器路径也能工作找不到解释器时launcher大声失败打印可操作的错误信息提示设置DEERFLOW_LARK_BROKER_PYTHON并以127退出而不是留一个不透明的 ENOEXEClauncher 里 shim 本体的路径是安装时烘焙进去的绝对路径render_launcher_script()把占位符LARK_CLI_BROKER_SHIM_PATH替换为实际路径。因为从PATH上直接运行lark-cli时$0只是裸命令名、没有目录信息靠$0做同目录查找会失败而安装目录是 init 容器与沙箱共享的稳定挂载绝对路径在两侧都有效。标准all-in-one-sandbox镜像自带 Python 3所以默认路径无需任何额外配置。两个脚本模板都以进程内常量LARK_CLI_BROKER_LAUNCHER_TEMPLATE/LARK_CLI_BROKER_SHIM_SCRIPT作为唯一事实来源由 broker 镜像构建时的install-shim生成——镜像里的副本永远不可能与 Gateway 进程内的副本漂移。2.3 Shim 本体的转发语义shim 脚本本身LARK_CLI_BROKER_SHIM_SCRIPTlark_broker.py逻辑非常克制读取DEERFLOW_LARK_BROKER_URL默认http://127.0.0.1:8788把sys.argv[1:]与 base64 编码的 stdin 打包成 JSONPOST /v1/exec把 broker 返回的stdout_b64/stderr_b64原样写回 stdout/stderr并以 broker 返回的exit_code退出任何传输层故障broker 不可达、HTTP 错误、JSON 解析失败都以非零码大声失败保证 broker 宕机永远不会看起来像一次“成功”的lark-cli执行——HTTP 错误还会把 broker 返回的error字段透传出来broker rejected request (HTTP 500: ...)。3. 构建 Broker 镜像构建上下文是仓库根目录broker 模块位于backend/之下构建命令见 READMEdocker build -t deer-flow/lark-cli-broker:v1.0.65 \ --build-arg LARK_CLI_VERSIONv1.0.65 \ -f docker/lark-cli-broker/Dockerfile .镜像 tag 应当编码 lark-cli 版本使其可以独立于上游all-in-one-sandbox镜像单独升级。Dockerfile 的关键事实builder 阶段debian:bookworm-slim复用 Pattern A 的共享脚本 build-runtime.sh 下载官方larksuite/cli的 Linux 发布二进制并做SHA-256 校验后放入/opt/lark-cli——sidecar 拿到的是与 Pattern A 完全同一下载校验路径产出的真实二进制支持APT_MIRROR构建参数用于镜像源替换最终阶段python:3.12-slim拷贝构建好的二进制、单文件模块lark_broker.py与 entrypoint并预置环境变量环境变量默认值作用DEERFLOW_LARK_BROKER_CLI/opt/lark-cli/bin/lark-clibroker 实际调用的二进制路径LARKSUITE_CLI_CONFIG_DIR/var/lark/configsidecar 侧凭据 config 目录LARKSUITE_CLI_DATA_DIR/var/lark/datasidecar 侧 OAuth token 等 data 目录DEERFLOW_LARK_BROKER_HOST/_PORT127.0.0.1/8788loopback 绑定地址LARK_CLI_RUNTIME_DEST/mnt/integrations/lark-cli/runtimeinstall-shim缺省写入位置CI 会以多架构linux/amd64,linux/arm64发布到ghcr.io/owner/deer-flow-lark-cli-broker:lark-cli-version由lark-cli-images.yamlworkflow 触发带lark_cli_version输入运行或推送lark-cli-v*tag。这条流水线与 DeerFlow 主v*发布解耦因为该镜像跟踪的是上游larksuite/cli的版本节奏。4. 接入 ProvisionerOpt-in、容器拓扑与就绪信号Broker 模式是可选开启opt-in的默认关闭。启用方式是发布镜像并在 provisioner 上配置LARK_CLI_BROKER_IMAGE。源码中 app.py 的默认值是空字符串_lark_cli_broker_enabled()要求镜像配置与请求标志同时成立LARK_CLI_BROKER_IMAGE os.environ.get(LARK_CLI_BROKER_IMAGE, ) def _lark_cli_broker_enabled(provision_lark_cli_broker: bool) - bool: return bool(LARK_CLI_BROKER_IMAGE) and provision_lark_cli_broker因此镜像未发布或未配置时就是 no-op——走 Pattern A / 遗留路径行为零变化。当配置生效且 Gateway 在建沙箱请求中携带provision_lark_cli_broker时provisioner 会追加以下资源资源说明lark-cli-runtimeemptyDirinit 容器与沙箱共享沙箱侧为只读挂载lark-cli-shim-initinit 容器运行 broker 镜像的install-shim模式把 shim 预置进共享卷lark-cli-brokersidecar运行serve模式per-user 的config只读/data可写凭据挂载只进 sidecar另有嵌套的locks挂载保持可写沙箱容器只拿到 runtime 卷的只读挂载 DEERFLOW_LARK_BROKER_URL环境变量没有任何config/data挂载几个值得注意的实现细节均可在 app.py 中核对_build_lark_cli_init_containers()L839-L883中broker 启用时返回的是lark-cli-shim-init容器args[install-shim, runtime 路径]privilegedFalse取代Pattern A 的lark-cli-init容器——即“两者同时配置时 Broker 模式优先supersedes”_build_lark_cli_broker_sidecars()L886-L948把config/locks/data三个挂载只挂到 sidecar 的/var/lark/*路径上PVC 模式下带 per-mountsub_path隔离并注入LARKSUITE_CLI_CONFIG_DIR/LARKSUITE_CLI_DATA_DIR指向 sidecar 内路径如果 provisioner 侧配置了DEERFLOW_LARK_BROKER_DENY_SUBCOMMANDS还会把它转发为 sidecar 环境变量沙箱侧的环境覆盖由 lark_cli.py 的lark_cli_env_overlay(user_id, brokerTrue)生成只包含PATH把 runtime 的bin/排到最前和DEERFLOW_LARK_BROKER_URL绝不携带LARKSUITE_CLI_CONFIG_DIR/DATA_DIR——这是“明文凭据路径不出现在沙箱环境里”的代码级保证。4.1 就绪信号/api/capabilities与 Lark 集成状态provisioner 通过GET /api/capabilities上报自身能力app.py{lark_cli_init_image: true, lark_cli_broker_image: true}Gateway 据此把 Lark 集成的沙箱运行时就绪状态暴露为/api/integrations/lark/status中的sandbox_runtime_mode: broker信号。其目的在源码注释里写得很直白让前端 UI 变绿的同时不能掩盖聊天时的command not found——即 UI 状态与运行时真实能力对齐。5. Broker HTTP 契约仅 loopbackBroker 服务只用标准库ThreadingHTTPServer实现绑定 loopback。在 K8s 中沙箱与 sidecar 共享 Pod 网络命名空间所以127.0.0.1能打到 sidecar而Pod 外的任何容器都不可达——这本身是一道网络层隔离。5.1POST /v1/exec请求体{args: [...], stdin_b64: ...}其中args必须是字符串列表broker 侧会做类型校验非法请求返回400 {error: invalid request}响应体{exit_code, stdout_b64, stderr_b64, truncated}执行语义在 lark_broker.py 的run_lark_cli()中args以argv 列表 shellFalse交给subprocess.run所以沙箱侧提供的任何参数不可能被 shell 解释注入第二条命令凭据环境变量由 broker 通过BrokerConfig.credential_env()单方面注入含LARKSUITE_CLI_CONFIG_DIR、LARKSUITE_CLI_DATA_DIR以及LARKSUITE_CLI_NO_UPDATE_NOTIFIER1/NO_SKILLS_NOTIFIER1客户端无法覆盖——沙箱进程不能把lark-cli指向别的 profile超时返回exit_code 124lark-cli: broker timed out二进制缺失返回127其它意外异常OSError等统一收敛为500 {error: broker exec failed}保证 shim 侧总能拿到结构化响应而不是不透明的传输失败。5.2 防御纵深资源限制一览这些常量在 lark_broker.py 中集中定义针对的是“被攻陷沙箱恶意消耗自己 broker”的场景常量值防御目标LARK_BROKER_MAX_REQUEST_BYTES1 MiB超大请求体直接413LARK_BROKER_MAX_OUTPUT_BYTES4 MiBstdout/stderr 截断并置truncated: trueLARK_BROKER_DEFAULT_TIMEOUT_SECONDS120单次lark-cli执行超时可用DEERFLOW_LARK_BROKER_TIMEOUT覆盖LARK_BROKER_MAX_CONCURRENCY8BoundedSemaphore限制并发子进程数打满即503 {error: broker busy}LARK_BROKER_SOCKET_TIMEOUT_SECONDS30防止客户端声明大Content-Length却不发 body、永久挂住 per-connection 线程5.3GET /v1/health返回{ok: true}供 sidecar 就绪探测使用其余路径返回404。6. 使用边界只有命令面没有文件系统桥这是启用 Broker 模式前必须理解的语义收缩broker 在sidecar 自己的工作目录里运行lark-cli看不到沙箱文件系统因此沙箱的 cwd 有意不被转发shim 模块 docstring 亦有说明。后果是依赖“相对沙箱 cwd 的路径”读/写文件的子命令例如上传某个本地文件在 Broker 模式下不受支持绝对路径仍然有效但指向的是sidecar 的文件系统不是沙箱的定位上这是一座纯命令面桥command-surface-only bridge不是文件系统桥。7. 可选子命令拒绝清单DEERFLOW_LARK_BROKER_DENY_SUBCOMMANDSBroker 移除了凭据文件但完整lark-cli命令面仍然可达——任何能打印/导出 token 的子命令如config show、auth token依旧能把凭据“读进 stdout”再外泄。因此 Broker 提供一个硬化开关在 sidecar 上设置DEERFLOW_LARK_BROKER_DENY_SUBCOMMANDSconfig show, auth token实现要点lark_broker.pyparse_deny_subcommands()把逗号分隔串解析为命令前缀元组config show, auth token→((config, show), (auth, token))空白项被丢弃匹配针对leading non-flag tokens先过滤掉所有以-开头的选项及其值因此config --json show同样会被config show规则命中命中后不会 spawn 二进制直接返回退出码126与subcommand config show is disabled in broker mode消息默认值为空行为零变化。测试覆盖见 test_lark_broker.py含解析、匹配与拒绝路径。README 给出的生产建议在启用前先确认所部署lark-cli版本的子命令面中不存在其它“唾手可得”的密钥导出命令provisioner 侧配置了该变量后会自动注入到 sidecar 容器app.py未配置则不注入。8. 小结与延伸阅读Pattern B 用“sidecar 持有凭据 loopback 命令面 沙箱 shim”的组合把凭据暴露面从“沙箱文件系统”收缩到“Pod 内 loopback 显式命令白名单”同时通过单模块单标准库、双模式分发、进程内模板常量三个设计保证了镜像与 Gateway 永不漂移、失败永远可诊断。关键路径速查关注点路径方案说明本文主体docker/lark-cli-broker/README.md镜像定义docker/lark-cli-broker/Dockerfile / docker/lark-cli-broker/entrypoint.shBroker 核心实现标准库单模块backend/packages/harness/deerflow/integrations/lark_broker.py沙箱环境覆盖broker 模式backend/packages/harness/deerflow/integrations/lark_cli.pyK8s 接线init/sidecar/挂载/能力上报docker/provisioner/app.py 与 docker/provisioner/README.md行为验证backend/tests/test_lark_broker.py、backend/tests/test_provisioner_pvc_volumes.py适用前提该方案面向 K8s 多容器 Pod 拓扑依赖沙箱与 sidecar 共享网络命名空间实现 loopback 互通且需要运营方自行构建/发布 broker 镜像并配置LARK_CLI_BROKER_IMAGE未配置时系统自动回退 Pattern A不会改变既有行为。【免费下载链接】deer-flowAn open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of tasks that could take minutes to hours.项目地址: https://gitcode.com/GitHub_Trending/de/deer-flow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门