logo
0
0
WeChat Login

支持动态拉起 Codex、CodeBuddy、DoAgent 运行环境#16

Resolved
created 1 weeks ago
Edit
Edit history

背景

当前 auto-harness 已能基于 CNB WebIDE 配置 Codex 运行环境,但后续需要面向多类智能体运行时统一编排,支持按任务动态拉起 Codex、CodeBuddy、DoAgent 等执行环境。

目标

  • 支持按任务类型动态选择并拉起 Codex、CodeBuddy、DoAgent 运行环境。
  • 支持运行环境的凭证注入、启动探针、任务派发、日志采集和结果回收。
  • 支持 CNB WebIDE / workspace 场景下的远程运行和状态追踪。
  • 为不同智能体运行时提供统一的环境描述、启动接口和健康检查接口。

初步验收

  • 可以通过配置声明至少三类 runner:codex、codebuddy、doagent。
  • 可以拉起指定 runner 并执行一个最小任务探针。
  • 可以收集任务 stdout/stderr、退出码、最终结果文件。
  • 敏感 token 不进入仓库明文,优先通过 CNB 密钥仓库或运行时注入。

备注

本 Issue 先用于设计梳理,后续拆分实现任务。

需求补充

范围调整:当前优先支持 codexdoagent 两类运行环境,codebuddy 暂不作为首期主线,可保留为后续可插拔 runner。

新增凭证需求:支持 type: cnb_api_token 的 CNB API token 配置方式。

设计约束:

  • token 全程对最终用户不可见,不进入仓库明文、不出现在日志、不回显到任务结果。
  • 用户可以通过受控运行环境体验 AI 能力,但不能直接读取或导出 token。
  • token 只在 runner 启动/任务执行时以环境变量、临时文件或平台代理方式注入。
  • 需要明确 token 的作用域、适用 runner、有效期、审计记录和最小权限边界。
  • 对于 CNB 平台代理 AI 调用,需要区分受信场景(如流水线/托管任务)和普通 WebIDE/SSH 场景,避免误以为任意环境都可直接复用 CNB AI 代理。

首期验收建议:

  • codex runner 可使用托管 token 完成最小探针任务。
  • doagent runner 可使用托管 token 或平台授权完成最小探针任务。
  • 用户侧只能看到任务状态、日志摘要和结果,看不到 token 原文。
  • 日志脱敏覆盖环境变量、命令行参数、配置文件内容和错误输出。

初步设计梳理:Codex + DoAgent 动态运行环境

1. 范围

首期只做两类 runner:

  • codex:通过 Codex CLI / exec server / app-server 形态执行任务。
  • doagent:通过 DoAgent 进程或远端服务执行任务。

codebuddy 暂时不进入首期主线,保留 runner 插件位即可。

2. 核心抽象

建议在现有 worker spec 上增加:

runners:
  - id: codex-default
    type: codex
    image: docker.cnb.cool/aiedulab/auto-harness/codex-runtime:latest
    workspace: /workspace
    command:
      argv: ["codex", "exec", "--sandbox", "workspace-write", "-"]
    credentialRefs:
      - codex-token
    health:
      argv: ["codex", "--version"]

  - id: doagent-default
    type: doagent
    image: docker.cnb.cool/aiedulab/auto-harness/doagent-runtime:latest
    workspace: /workspace
    command:
      argv: ["doagent", "run", "--input", "-"]
    credentialRefs:
      - cnb-ai-token
    health:
      argv: ["doagent", "--version"]

credentials:
  - id: cnb-ai-token
    type: cnb_api_token
    source: managed
    expose: never
    env:
      target: CNB_TOKEN
    scopes:
      - ai:invoke
      - repo:read

3. cnb_api_token 凭证模式

type: cnb_api_token 表示 token 由平台/托管侧持有,用户不可见。

约束:

  • 不写入仓库。
  • 不回显给用户。
  • 不直接进入普通任务日志。
  • 只在 runner 启动时注入到白名单环境变量或临时文件。
  • 所有日志经过 token scrubber。
  • 每次任务记录 credential id、runner id、用途和审计摘要,但不记录 token 值。

这类 token 的价值是:用户可以体验 AI 能力,但不能拿到 token 本身。

4. 运行生命周期

统一 runner 生命周期:

  1. resolve:解析 runner 类型、镜像、workspace、credentialRefs。
  2. provision:动态创建运行环境,可是本地进程、Docker Compose、CNB workspace、K8s pod。
  3. inject:注入凭证和运行配置。
  4. probe:运行健康检查,例如 codex --versiondoagent --version
  5. dispatch:投递 prompt / task,写入任务输入文件,启动进程。
  6. observe:采集 stdout、stderr、退出码、结果文件、心跳。
  7. collect:归档最终结果和 artifacts。
  8. cleanup:按策略保留或销毁环境。

5. 需要新增的 Go 模块

建议拆分:

  • internal/runtime:Runner 接口和生命周期编排。
  • internal/runtime/codex:Codex runner。
  • internal/runtime/doagent:DoAgent runner。
  • internal/credentials:凭证解析、注入、脱敏。
  • internal/provision:本地 / compose / cnb workspace / k8s provisioner。
  • internal/tasks:任务记录、日志路径、结果文件约定。

6. Runner 接口草案

type Runner interface {
    Type() string
    Probe(ctx context.Context, env RuntimeEnv) error
    Run(ctx context.Context, task Task, env RuntimeEnv) (Result, error)
}

RuntimeEnv 里只携带脱敏后的元信息;真实 secret 通过 CredentialInjector 在最后一刻注入。

7. 首期验收

  • spec 可声明 codexdoagent runner。
  • validate 能校验 runner / credential 引用关系。
  • render 能生成对应 compose 或 workspace 配置。
  • servechat/sendMessage 可按 model/runner 路由到 Codex 或 DoAgent。
  • Codex runner 完成最小探针任务。
  • DoAgent runner 完成最小探针任务。
  • token 不出现在 stdout/stderr/API response/build artifact 中。

8. 风险点

  • CNB API token 是否允许 AI 调用取决于运行场景和平台授权,不应假设普通 WebIDE/SSH 场景都可直接复用。
  • DoAgent 的最小运行协议需要进一步确认:CLI、HTTP、ACP 还是 doops/远端服务。
  • 需要先做日志脱敏,再开放用户可见任务日志。

新需求规划:长驻实例、一次性任务、上下文保存与资源回收

1. 运行实例类型

需要支持两类实例生命周期:

长时间运行实例(persistent instance)

适用于:

  • 持续对话型 Codex / DoAgent worker。
  • 需要保持 workspace、上下文、会话状态的智能体。
  • 后续可能恢复继续执行的任务。

特点:

  • 实例不会在单次任务结束后立即销毁。
  • 通过心跳判断用户是否仍在使用。
  • 空闲超时后进入 pausedsuspended 状态。
  • 支持从保存的上下文恢复运行。

一次性任务实例(ephemeral task instance)

适用于:

  • 单次代码生成、单次评审、单次脚本执行。
  • 不需要交互式上下文保留的任务。

特点:

  • 任务完成后自动收集结果。
  • 上下文和日志持久化后销毁资源。
  • 失败时仍保留可追溯信息。

2. 实例状态机

建议状态:

created -> provisioning -> running -> idle -> paused -> resuming -> running
                                  \-> completed -> archived -> destroyed
                                  \-> failed -> archived -> destroyed
                                  \-> expired -> archived -> destroyed

说明:

  • running:正在执行或可交互。
  • idle:无任务执行但仍可快速恢复。
  • paused:资源暂停或缩容,保留上下文。
  • completed:一次性任务正常结束。
  • archived:上下文、日志、artifact 已保存。
  • destroyed:底层资源已释放。

3. 上下文与执行过程持久化

容器销毁前必须自动保存:

  • runner 类型、版本、镜像、启动参数。
  • workspace git 信息:repo、branch、commit、dirty diff 摘要。
  • 输入 prompt / task spec。
  • stdout / stderr 原始日志和脱敏日志。
  • exit code、开始时间、结束时间、耗时。
  • artifacts:结果文件、patch、报告、会话摘要。
  • agent session 信息:例如 Codex rollout / session id,DoAgent session id。
  • credential 使用摘要:credential id、类型、用途、注入方式,不能保存 token 原文。

建议引入:

persistence:
  enabled: true
  backend: local | cnb-artifact | cos | s3
  retentionDays: 30
  saveOn:
    - completed
    - failed
    - paused
    - destroyed
  redactSecrets: true

4. 恢复运行

长驻实例需要支持恢复:

  • 根据 instanceId 查找最近一次 checkpoint。
  • 重新 provision 容器或远端 workspace。
  • 恢复 workspace 文件、agent 会话元数据、任务队列。
  • 重新注入凭证,但不从 checkpoint 中读取 token。
  • 恢复后先执行 probe,再允许继续任务。

恢复接口建议:

POST /instances/{id}/resume
GET  /instances/{id}/snapshots
POST /instances/{id}/tasks

5. 心跳与资源回收

需要资源管理器定期扫描实例:

resourcePolicy:
  heartbeatTimeoutSeconds: 300
  idlePauseAfterSeconds: 900
  destroyEphemeralAfterSeconds: 60
  destroyPausedAfterSeconds: 86400
  maxRuntimeSeconds: 64800
  cleanupIntervalSeconds: 300

策略:

  • 用户持续心跳:保持 running。
  • 无心跳且无任务:进入 idle。
  • idle 超时:长驻实例 pause,一次性实例 destroy。
  • pause 前必须保存 checkpoint。
  • destroy 前必须 archive。
  • 超过最大运行时长强制归档并回收。

6. 一次性任务销毁策略

一次性任务完成后流程:

  1. 收集结果。
  2. 生成 task summary。
  3. 脱敏日志。
  4. 保存上下文包。
  5. 标记 completed / failed。
  6. 销毁容器 / workspace / pod。
  7. 仅保留可审计记录和 artifacts。

7. 周期性任务执行

需要支持周期任务,不依赖长驻实例永久运行。

建议 spec:

schedules:
  - name: nightly-maintenance
    runnerRef: codex-default
    cron: "0 2 * * *"
    mode: ephemeral
    prompt: "检查项目健康并生成报告"
    timeoutSeconds: 1800
    persistence:
      retentionDays: 14

执行策略:

  • 到点创建一次性 task instance。
  • 执行完成后归档并销毁。
  • 支持 missed run 策略:skip / catch_up_once。
  • 支持并发策略:forbid / replace / allow。

8. 建议新增模块

  • internal/instances:实例状态机、实例元数据。
  • internal/checkpoint:上下文保存、恢复、归档。
  • internal/resource:心跳、空闲检测、暂停、销毁。
  • internal/scheduler:周期任务调度。
  • internal/tasks:一次性任务和长驻实例任务统一记录。

9. 首期验收建议

  • 支持创建 persistentephemeral 两类实例。
  • ephemeral 任务完成后自动归档并销毁。
  • persistent 实例无心跳后自动 pause。
  • pause / destroy 前生成 checkpoint。
  • 可以查看实例执行历史和脱敏日志。
  • 可以从 checkpoint 恢复 persistent 实例。
  • 支持至少一个 cron 周期任务,执行后自动回收资源。
  • 所有 credential 原文不出现在 checkpoint、日志和 API response 中。

新需求补充:执行过程中支持向对话通道实时回复方案 / 评审条目

这是可行的,而且建议作为一等能力设计,而不是只依赖任务完成后的最终结果文件。

目标

AI runner 在执行过程中,可以把阶段性方案、评审结论、风险提示、澄清问题直接回复到用户当前对话里。

典型场景:

  • 用户提交一个需求条目,AI 先输出设计评审意见。
  • AI 在长任务执行中持续汇报阶段性方案。
  • AI 发现需求不清晰时,直接在对话里提出澄清问题。
  • AI 在执行结束前先给出可读方案,再继续生成代码或 artifacts。

建议抽象:Conversation Sink / Event Sink

Runner 执行不应只返回最终 Result,而是持续产生事件:

type RunnerEvent struct {
    InstanceID string
    TaskID     string
    Type       string // message | plan | review | progress | question | artifact | final | error
    Visibility string // user | internal | audit
    Body       string
    Metadata   map[string]string
    CreatedAt  time.Time
}

系统提供一个 ConversationSink

type ConversationSink interface {
    Emit(ctx context.Context, event RunnerEvent) error
}

不同部署场景可绑定不同 sink:

  • AgentForge 对话:转成 session/update SSE 事件。
  • CNB Issue:转成 issue comment 或 work-mode comment。
  • CNB PR:转成 PR review/comment。
  • 本地 CLI:写 stdout / JSONL。
  • 审计归档:写 checkpoint event log。

和现有 ACP server 的关系

当前 chat/sendMessage 已经会通过 /events?sid=<sessionId> 推送:

  • agent_message_chunk
  • agent_message
  • turn_finished

后续可以把 runner 事件映射为更细的对话事件:

  • plan -> agent_message_chunkagent_plan
  • review -> agent_review_item
  • question -> agent_question
  • progress -> agent_progress
  • final -> agent_message + turn_finished

对“条目评审”的支持方式

建议增加 task 类型:

tasks:
  - type: review_item
    runnerRef: codex-default
    input:
      itemType: issue_requirement
      source: cnb_issue
      issue: 16
    output:
      sink: conversation
      alsoArchive: true

执行流程:

  1. 读取需求条目或 Issue 内容。
  2. AI 生成结构化评审:可行性、风险、缺口、建议拆分。
  3. 立即通过 conversation sink 回复给用户。
  4. 同步写入 checkpoint,便于追溯。
  5. 如需沉淀,再补充写入 Issue 评论或设计文档。

安全和可追溯要求

  • 对话输出必须经过 secret scrubber,不能泄露 token、环境变量、临时凭证。
  • 内部执行日志和用户可见回复分离。
  • 每条对话事件都要记录 task id、runner id、instance id。
  • 支持 replay:实例恢复后可以重放关键对话事件和执行过程。

结论

支持“AI 在执行过程中直接将方案回复到对话里”是可行且必要的。建议把它设计成 runner event stream + conversation sink,而不是让各 runner 自己直接操作 CNB Issue 或 AgentForge 对话。这样 Codex 和 DoAgent 都能复用同一套输出通道。

Issue #16 实现提交

已提交并推送动态 runner 首期实现。

  • Repo: aiedulab/auto-harness
  • Branch: codex/issue-16-dynamic-runners
  • Commit: 10ff8e17dc71e53afed5da8de24cbdda2ceaadf3
  • Commit message: feat: support dynamic agent runners
  • 代码路径: /Users/wwyz/Documents/code/AgentForge/auto-harness

已实现范围

  • defaultRunner / runners 配置模型。
  • 按 runner id、runner type、task type、model/default runner 选择运行环境。
  • probe CLI,可执行指定 runner 的最小探针。
  • stdout、stderr、exit code、声明结果文件收集。
  • credentials 元数据与 type: cnb_api_token 配置,支持 runner credentialRefs
  • 用户可见 stdout/stderr/resultFiles/ACP response 的 token 脱敏。
  • 示例配置首期聚焦 codex + doagentcodebuddy 作为后续可插拔 runner 保留在设计边界。
  • README 与 specs/issue-16-dynamic-runners.md 已更新。

验证结果

  • go test ./... 通过。
  • go run ./cmd/auto-harness validate -f examples/codex-worker.yaml 通过。
  • go run ./cmd/auto-harness probe -f examples/codex-worker.yaml --runner codex --cwd /tmp 通过。
  • CNB_TOKEN=visible-secret-value go run ./cmd/auto-harness probe -f examples/codex-worker.yaml --runner remote --cwd /tmp 通过,输出未回显 token。
  • git diff --cached --check 通过。

Token 安全说明

本次提交没有提交任何 token 原文;示例只声明 cnb_api_token 的元数据、env 名称、scope 与 runner 适用范围。runner 输出、错误输出和结果文件内容会经过 scrubber 后再返回用户。

开发流程说明

检查 .cnb.yml 后确认:推送功能分支不会自动集成到 devdev.push 只在直接推送/合并到 dev 后触发 dev CI 和 dev 镜像发布;功能分支需要后续 PR 或显式合并到 dev 才进入 dev 集成。

@npc/DeepSeek-TUI(DeepSeek-TUI-pro) 你来做一次代码评审

DeepSeek-TUI-pro

NPC
1
285,344 tokens, 8.930 credits, ¥0.447

DeepSeek-TUI-pro

NPC

@wwyz(帷幄庸者) 代码评审完成,以下是详细结果。


代码评审报告 — auto-harness (Issue #16)

一、整体印象

项目结构清晰,遵循 Go 社区约定:cmd/ 入口、internal/ 核心逻辑、examples/ 示例、specs/ 设计文档。依赖极少(仅 uuid + yaml.v3),编译产物干净。JSON-RPC over HTTP 的 ACP Server 实现规范,K8s Pod 渲染是 779243d 提交的高价值增量。


二、严重问题

1. 路径遍历漏洞 — fsRead / fsReadText / attachmentUpload

internal/acp/server.go L369、L384 直接使用用户传入的 params.Path 调用 os.ReadFile,未做任何沙箱限制。攻击者可通过 /rpc 端点读取任意文件:

POST /rpc
{"method":"fs/read_text_file","params":{"path":"/etc/passwd"}}

建议: 校验路径必须在 spec.Workspace.Cwd 子树内(filepath.Rel + 检查不包含 ..),拒绝符号链接逃逸。attachmentUpload L314 同理,filepath.Base 不足以防御 ../../ 攻击。

2. 目录遍历 — fsSearch

L341 filepath.WalkDir(root, ...) 的 root 来自 params.WorkspacePath(L332),未限制在工作区范围内。恶意请求可遍历整个文件系统。

建议: 将 root 限制为 spec.Workspace.Cwd 的子目录,拒绝越界。


三、中等问题

3. 错误静默吞没

多处在核心路径中忽略 json.Unmarshal / json.Marshal 返回的错误:

  • sessionNew L151: _ = json.Unmarshal(raw, &params) — 参数解析失败被静默忽略
  • workspaceOpen L290: 同上
  • fsSearch L331: 同上
  • events L276: data, _ := json.Marshal(event) — 序列化失败时发送空数据
  • fsSearch L348: rel, _ := filepath.Rel(root, path) — 路径计算失败被忽略

建议: 至少 slog.Warn 记录,关键路径应返回错误。

4. Session 内存泄漏

Server.sessions map 只有新增(sessionNew),永不移除。长时间运行的服务会无限增长。

建议: 增加 TTL 过期机制 + 定时清理 goroutine;或设置最大 session 数上限。

5. Runner 环境变量继承

internal/runner/runner.go L36:cmd.Env = os.Environ() 会将宿主全部环境变量注入子进程,包括可能泄露的 token/密钥。

建议: 从零开始构建 cmd.Env,只传递显式声明的变量(spec.Env + 必需的 AUTO_HARNESS_*)。

6. Kubernetes Pod 配置缺失

internal/k8s/pod.go 的 Pod 模板缺少关键字段:

  • 无 resources.requests/limits — 无资源约束的 Pod 可能耗尽节点
  • 无 securityContext — 容器以 root 运行
  • 无 secretKeyRef / configMapKeyRef 支持的 env 注入 — 与 Issue #16 要求的「凭证注入」直接相关
  • hostPath + DirectoryOrCreate 类型可能在节点上创建意外目录

四、轻微问题

7. 测试覆盖不足

当前仅 internal/k8s/pod_test.go 有测试。核心模块缺失:

  • config/spec.go — Load / Validate / ApplyDefaults 无测试
  • compose/render.go — Render / WriteBuild 无测试
  • acp/server.go — dispatch / handlers 无测试
  • runner/runner.go — RunCommand 无测试

建议: 优先补 config 和 acp 的 table-driven tests。

8. Compose healthcheck 依赖 wget

internal/compose/render.go L37:healthcheck 使用 wget,但 Dockerfile 基于 Alpine 且未安装 wget。容器启动后会一直 unhealthy。

建议: 改用 curl(Alpine 常见)或添加 RUN apk add wget。

9. Config 校验边界

  • command.timeoutSeconds 无上限(可设为极大值)
  • schedule.cron 仅校验字段数(5),不做语义校验
  • namePattern 要求首字符为字母数字但允许 _ 在第二位置,与其他字段校验不一致

10. k8s-pod 对 env value 的双重引号

internal/k8s/pod.go L105:strconv.Quote(value) 在 YAML 模板中输出带引号的值。渲染结果为 value: ""yes"",K8s 会将其解读为字面量引号字符串而非 yes。测试断言(L46)也验证了这个 bug。

建议: K8s env value 在 YAML 中不需要引号包裹,直接输出原始值即可。


五、与 Issue #16 目标的对齐分析

Issue #16 要求支持「按任务类型动态选择并拉起 Codex、CodeBuddy、DoAgent 运行环境」。当前代码的 gap:

目标现状
多 runner 类型声明config.Spec 无 type/runtime 字段,无法区分 Codex/CodeBuddy/DoAgent
动态拉起 + 启动探针仅静态渲染 compose/k8s,无运行时 launch API
凭证注入env map 仅支持明文,无 secret 引用
任务派发 + 结果回收ACP server 仅支持单 worker,无任务路由
日志采集仅 stdout/stderr 合并,无结构化日志

建议: 将 Issue #16 拆分为多个子任务:

  1. Config 扩展 — 增加 type、runtime、credentials 字段
  2. Runner 抽象 — 定义 Runner 接口,Codex/CodeBuddy/DoAgent 各自实现
  3. 动态启动 — 增加 /runner/launch RPC 方法 + 健康探针
  4. K8s 凭证注入 — secretKeyRef / envFrom 支持

六、总评

代码质量整体良好,Go 惯用法使用得当,模板渲染和 JSON-RPC 实现是亮点。但存在 2 个安全漏洞需立即修复(路径遍历),以及 K8s env value 的双重引号 bug 会导致实际部署异常。建议在继续 Issue #16 功能开发前,先完成上述安全修复和核心模块的测试补充。


审查范围:8 个源文件 + 1 个测试文件 + Dockerfile + 示例配置,按 go vet / gosec / OWASP Top 10 标准

评审问题修复提交

已根据上一轮评审修复阻塞安全问题并推送。

  • Branch: codex/issue-16-dynamic-runners
  • Commit: 997860b650cfa5e909b53d4e5625a0c3199b9e14
  • Commit message: fix: harden runner and workspace access

修复内容

  • ACP 文件接口增加 workspace 路径沙箱:fs/read_text_filefs/readfs/searchworkspace/open 均限制在 workspace.cwd 内。
  • 增加 symlink escape 检查,拒绝通过符号链接读取 workspace 外文件。
  • attachment/upload 拒绝覆盖 symlink 目标,避免写入逃逸。
  • 修复 session/newworkspace/openfs/search 的 JSON 参数解析静默吞错。
  • SSE 事件 marshal 失败会记录 warn,不再静默发送异常数据。
  • Runner 子进程不再继承完整宿主环境,改为安全基础 env allowlist + spec/runner 显式 env + credentialRefs 指定 env + AUTO_HARNESS_*。
  • 增加 ACP 路径沙箱测试、symlink 逃逸测试、runner env 白名单测试和 credential env 注入测试。

验证

  • go test ./... 通过。
  • go run ./cmd/auto-harness validate -f examples/codex-worker.yaml 通过。
  • go run ./cmd/auto-harness probe -f examples/codex-worker.yaml --runner codex --cwd /tmp 通过。
  • CNB_TOKEN=visible-secret-value go run ./cmd/auto-harness probe -f examples/codex-worker.yaml --runner remote --cwd /tmp 通过。
  • git diff --cached --check 通过。

@npc/DeepSeek-TUI(DeepSeek-TUI-pro) 你来做一次代码评审,核对代码改动是否完整实现需求

DeepSeek-TUI-pro

NPC
1
798,259 tokens, 12.400 credits, ¥0.620

DeepSeek-TUI-pro

NPC

@wwyz(帷幄庸者) 代码评审完成,以下是针对 commit 997860b(分支 codex/issue-16-dynamic-runners)的评审结果。


评审概览

维度结论
需求覆盖✅ 6 项声称修复全部有对应代码变更
安全性✅ workspace 沙箱、symlink 逃逸、env 白名单均实现正确
测试覆盖✅ 新增 13 个测试覆盖安全关键路径
代码质量⚠️ 有三处细节建议改进

一、逐项核对

1. ACP 文件接口 workspace 沙箱 ✅

fsReadTextfsReadfsSearchworkspaceOpen 均通过新增的 resolveWorkspacePath 解析输入路径。该函数做了两层校验:

  • 直接路径校验filepath.Abs + filepath.Clean 后用 pathWithin 检查是否在 workspace.cwd 内;
  • 符号链接解析后校验:对 root 和 candidate 分别做 EvalSymlinks,确保解析后仍在 workspace 边界内。

双重校验设计合理,能同时防御 ../ 穿越和 symlink 逃逸。

2. Symlink escape 拒绝 ✅

resolveWorkspacePathmustExist=true 时会 EvalSymlinks 候选路径,然后用 rootEval(workspace 根的真实路径)做 pathWithin 检查。测试 TestFSReadRejectsSymlinkEscape 验证了:workspace 内 symlink 指向 workspace 外文件时会被拒绝。

3. attachment/upload 拒绝覆盖 symlink ✅

attachmentUpload 改为:

  1. filepath.Base 去除路径分隔符,防止路径注入;
  2. os.Lstat(不跟随 symlink)检查目标是否为 symlink,是则拒绝;
  3. 测试 TestAttachmentUploadRejectsSymlinkTarget 覆盖此场景。

⚠️ TOCTOU 竞态LstatWriteFile 之间理论上存在原子性问题(在 Lstat 返回非 symlink 后、WriteFile 执行前,其他进程创建 symlink)。在单租户场景下风险极低,但若后续支持多租户可考虑用 O_NOFOLLOW 写入。

4. JSON 参数解析静默吞错修复 ✅

sessionNewworkspaceOpenfsSearch 三处均由 _ = json.Unmarshal(...) 改为 if err := json.Unmarshal(...); err != nil { return nil, err }。不再吞错。

5. SSE marshal 失败 warn 日志 ✅

events handler 中 json.Marshal(event) 的错误从静默丢弃改为 slog.Warn("failed to marshal session event", "err", err) + continue。错误不再被吞。

6. Runner 子进程环境隔离 ✅

commandEnv 替代了原有的 os.Environ(),当前环境白名单为:

HOME, LOGNAME, PATH, TMPDIR, USER

然后按优先级叠加:spec.Envrunner.EnvcredentialRefs env → runner.Credentials env → AUTO_HARNESS_* 系列变量。测试 TestRunCommandDoesNotInheritHostEnvironment 验证了非白名单变量不会泄漏到子进程。

7. 测试覆盖 ✅

新增测试:

文件测试数覆盖场景
internal/acp/server_test.go4fs/read_text_file 沙箱、fs/read symlink 逃逸、fs/search 沙箱、attachment/upload symlink
internal/runner/runner_test.go5Probe 结果收集、credential 缺失报错、credential 值脱敏、env 白名单、credentialRefs 注入
internal/config/spec_test.go4legacy runner 自动创建、多维度 runner 选择、defaultRunner 校验、cnb_api_token 默认值

二、潜在改进建议

建议 1:fsSearch 对 workspace 内 symlink 条目的盲点(中)

当前 fsSearchresolveWorkspacePath 只校验了搜索根目录不逃逸 workspace,但 filepath.WalkDir 遍历时会跳过 symlink 目录(安全行为,不跟随)。然而,workspace 内指向外部的 symlink 文件会作为条目出现在搜索结果中,暴露外部路径名。

建议:在 WalkDir 回调中对每个 entry 检查是否为 symlink(d.Type()&os.ModeSymlink != 0),若是则跳过该条目。

建议 2:safeHostEnvKeys 白名单可配置(低)

当前白名单硬编码为 5 个 key。对于需要特定环境变量(如 LANGLC_ALLTZ)的 runner,可能不够灵活。非阻塞性问题,可在后续迭代中考虑将白名单纳入 spec 配置。

建议 3:resolveWorkspacePath 对必须存在路径的 EvalSymlinks 失败处理(低)

mustExist=trueEvalSymlinks 失败时函数返回错误。但如果 candidate 本身不是 symlink 且确实存在,EvalSymlinks 不应失败。当前路径经过 filepath.Abs + filepath.Clean 后应是合法路径,此场景极少触发。不影响安全性。


三、结论

6 项声称修复均已落地,代码变更与声明一致。workspace 沙箱和 env 隔离的实现质量较高,双重路径校验 + symlink eval 的设计在同类 Go 项目中属于较为完备的方案。新增 13 个测试覆盖了安全关键路径。建议 1(fsSearch symlink 条目过滤)建议在后续迭代中跟进;其余无阻塞性问题。

整体评审:通过

下一步:进入 dev 集成 PR

评审通过后已创建面向 dev 的 PR:

  • PR: #27 feat: support dynamic agent runners
  • Source: codex/issue-16-dynamic-runners
  • Target: dev
  • Latest fix commit: 997860b650cfa5e909b53d4e5625a0c3199b9e14

说明:功能分支不会自动集成到 dev;需要 PR 合并到 dev 后才会触发 .cnb.ymldev.push 流水线和 dev 镜像发布。

Merge pull request #27: feat: support dynamic agent runners

已合并并发布到 main:Codex NPC/openbox runtime 与动态 runner 基础能力已随 main 提交 62efdd6 和发布 PR #26 进入主线。main 构建 cnb-6u8-1jojcvdm4 成功。

Resolved ISSUE

已合并到 dev 并完成集成验证

PR #27 已合并到 dev

  • PR: #27 feat: support dynamic agent runners
  • Merge commit: 56b150a4d815d4d8df91643c45eb1293d484ba00
  • Source branch latest commit: 849b95f8263275da958431f16d1aa492c6eaad6a
  • Target branch: dev

验证结果

  • PR pipeline: success (cnb-oc8-1jojcoisu)
  • dev push pipeline: success (cnb-2m8-1jojcvst3)
  • 远端可见性:origin/dev 包含 merge commit 56b150a4d815d4d8df91643c45eb1293d484ba00

Dev 制品

  • docker.cnb.cool/aiedulab/auto-harness:dev
  • docker.cnb.cool/aiedulab/auto-harness:dev-56b150a4
  • Digest: sha256:1005c3144ecda2d06eac13eb8332fc4005d1694ab5e416afe7cebd425f757834

当前状态:Issue #16 已完成开发阶段并集成到 dev。未执行发布到 main、未创建 Release、未关闭 Issue,等待发布确认。

Assignee
None yet
Label
None yet
Priority
None yet
Time period
-
Property
Add custom properties to record and label key information
Participant