Architecture

September 6, 2026 · View on GitHub

唯一事实来源是 docs/plan.md(Wave 7 最小化 Root-Orchestrator 架构)。本文是其在代码层的落地视图。

心智模型

User ⇄ DSH Root Session(pentester persona preset)
         │  Root 工具面(per-Root tools.restrict 移除 container_exec;
         │  通用 subagent/job/bash/fs 工具由组合排除 —— 本 preset 不带那些行):
         │  pentester_delegate({assignments:[{agent,objective,task_prompt}]})
         │  pentester_cancel_delegation({delegation_id})
         │  pentester_advance_stage({summary, run?})   # run 仅首次 bootstrap
         │  pentester_rollback_stage({stage, reason})

   dsh-pentester Host(只管理 PTES 领域状态;ctx.provide('pentester'))
         │  ctx.subagents.startContinuable('spawn')

   DSH 官方 Continuable Subagent Runtime(durable child Session:followup / interrupt)
         │  Worker persona = WORKER_BASELINE + AgentProfile.systemPrompt
         │  Worker prompt  = standalone task context + task_prompt
         │  toolFilter     = { allow: [container_exec + skill + profile.mcpIds 的 mcp__* 工具] }

   pentester_container_exec(dockerode;只对 Worker child 可见)
   skill(官方 @deepseek-ai/dsh-tool-skill;child-scoped frozen provider 供 catalog)

   workspace/stages/<NN>-<stage>/delegations/D-00N/   ← Deliverable 就是文件

   Worker 是 durable continuable child:写/更新 result.md 作为当前正式可交付结果,
   可接收 Root 的 send_message 后续;Root 自行判断是否 advance / cancel / 再派

Pentester 不直接创建 Worker Agent(docs/plan.md §86)。Worker 执行使用 DeepSeek Harness 官方 subagent seam:ctx.subagents → provider spawn (dsh-base 注册的 @deepseek-ai/dsh-subagent-spawn-in-process)→ durable child Session(跨 Activation 持续,FIFO inbox,官方管理 cold resume)。Worker 可经 send_message 收到后续消息、经 interrupt_agent 被中断;不再使用 ctx.jobs,不再存在 自定义 ctx.agents.create() / agent.whenIdle() Worker lifecycle。

工具注册位置 = pentester preset scope(docs/plan.md §7):宿主 apply() 不再向 global registry 注册任何 pentester 工具,只 ctx.provide('pentester') 发布实现;preset 行 run-state.mjs 把 5 个 Root 工具 + pentester_container_exec 注册进 pentester 的 standing scope,官方 tool-skill 行注册 native skill tool。结果:standard Session 完全看不到 pentester_*;Root 看到 4 个领域工具 + 原生 ask_user;Worker child 的 官方 toolFilter 是正向 allow-listworkerToolFilter(mcpToolNames)pentester_container_exec + 官方 skill + 该 profile mcps 分配的 mcp__<serverName>__* 工具名),看不到 Root 编排、ask_user_question、 其他 AgentProfile 的 MCP 工具(详见 docs/mcp.md)。

Root 的收敛 = 组合排除 + 正向 per-Root allow-list(docs/plan.md Wave 9 / docs/mcp.md):

  • 组合排除:本 deployment 的通用工具(bash / fs / subagent / job / skill / workflow / goal 等)是 per-preset 行(standard 等 preset 自带 tool-bash / tool-fs / tool-subagent / tool-jobs;web-app bundle 把 base 宿主层的同名 全局行 disabled)。pentester preset 的组合里没有这些行 → Root 天然看不到。
  • per-Root 正向 allow-list(ROOT_ALLOW):MCP 工具是 global 工具 (官方 dsh-mcp-client 经 Host 注册),deny-list 无法防住未来新增的 global MCP 工具(list_changed / 新 server)自动进入 Root —— 这是 Wave 9 从 deny-list 迁移到 allow-list 的核心理由。run-state.mjs 监听 agent/created,在 Root agent 自己的 layer 上 restrict { allow: ROOT_ALLOW }(orchestration + ask_user + subagent control; Worker child:origin=subagent / delegationDepth≥1,跳过)。allow-list 未 列出的工具(container_exec、skill、全部 mcp__*、未来新增 global tools) 一律不可见。
  • Worker 的收敛 = per-Delegation toolFilter allow-list:dispatch 阶段经 Mandatory Gate(McpRuntime.requiredToolNames)resolve 出该 profile 的 MCP 工具名,与 base Worker tools 合成 workerToolFilter() 传入 startContinuable。frozen allow-list 不随服务器 tool list 变化扩大 (权限不扩大不变量);官方 continuable descriptor 持久化 toolFilter, cold resume 重建相同可见性。

Root 没有 bash / 文件工具:信息经注入的 run state、worker report 与用户 获得,需要更多事实就再 delegate。

TypeScript 不判断"信息是否足够"。这个判断属于 Root Agent 的 system prompt。

Pre-engagement / Bootstrap

Pre-engagement 不创建 Worker:由 User + Root + 原生 ask_user_question 固定问卷完成(target_confirm / scope_confirm / exclusions_confirm / roe_confirm / language_confirm / stage_advance_policy / final_confirm, 一次调用;不封装 pentester_ask_user)。第 6 题 stage_advance_policy 的答案由 proof 派生成 StageAdvancePolicy(automatic / manual / custom+instruction), 持久化到 run.json 的 stageAdvancePolicy 字段(旧 Run 缺省 = automatic)并 写入 target/inputs/pre-engagement.md(审计);模型参数不可绕过用户答案。 首次初始化在 pentester_run({action:"start"}) 承担:pre-engagement 批次校验通过后创建 workspace 骨架、 run.json(status: active,currentStage=pre-engagement)、target/ 文件, git init + 初始 commit(workspace: initialize <target>);随后同一调用完成 pre-engagement checkpoint(ptes(01-pre-engagement): complete)并激活 intelligence-gathering。pentester_delegate 不 bootstrap:无 Run 时明确 报错(run_not_initialized)。

确认判定是 semantic 而非 display-label 全等ask_user_question 携带的 是 UI display label(模型可能附加 "(Recommended)" / "(推荐)" 等 cosmetic 后缀),normalizePreEngagementChoice() 把 label 归一成 authorization semantic(roe: standard / non-invasive / custom;language: zh-CN / en; stage_advance_policy: automatic / manual / custom —— 三者都算 confirmed,选 "其他" 不等于拒绝授权;官方 UI 在用户填写 freeform 文本时以 custom 字段 携带指令);final: confirmed / rejected;exclusions: none / custom;target/scope 的明确 negative option),只有接受语义才算 confirmed。归一保守:canonical label + 有限明确的 cosmetic 后缀集合,任意未知字符串一律 undefined(拒绝)。 同时 selected 必须存在于该问题 invocation 的 options[].label 中 (不信任 result payload 里凭空出现的 label)。pre_engagement_not_confirmed 错误会列出未确认的 question id,Root 据此向用户说明,而不是盲目重问七题。

PentestRun Lifecycle V2(pentester_run

pentester_run 是唯一的 PentestRun 生命周期工具(一个 Domain Aggregate, 五个 action;不把 delegate/advance/rollback 塞进来):

              start
  NONE ─────────────────► ACTIVE
                            │ stop          resume
                            ▼                 │
                         STOPPED ◄────────────┘

  ACTIVE ──PTES final──► COMPLETED

  ACTIVE / STOPPED / COMPLETED ──restart──► NEW ACTIVE RUN(新 UUID + 新 instanceId)
  • status 枚举(run.json status,缺省 active):active | stopped | completed。schemaVersion 保持 3 —— 旧 run.json(active/completed)正常 读取,无格式迁移。
  • action=start:要求 pre-engagement proof(七题确认,含 Stage Advance Policy),任何 side effect 之前校验。Target 已存在时返回 run_already_exists(绝不偷偷覆盖)。 幂等:同 Root Session 绑定同 target 且 run 已存在 → alreadyInitialized no-op(同 run.id / instanceId,不 wipe workspace、不重 git init、不动 Docker)。Docker 按需:start 不创建容器(delegate/resume 路径才 ensure)。
  • action=stop:可逆暂停。取消 active/starting/cancelling Delegations (cancel 携带经验证的 interrupt authority;interrupt 准许后先进入 cancelling 并排空该 Delegation 已准入的 in-flight command,再落 closed) → best-effort sync → stopRunRuntime()(删容器,保留 remote persistent volume;只有 404/no-such 视为幂等 already-gone,其它错误 fail loud;删除后重新 list 确认本 run 的 managed 容器确实消失)→ status=stopped(经 serial mutation boundary + revision)。run.id / instanceId / currentStage / workspace / findings / git history 全部保留。 幂等(重复 stop → alreadyStopped no-op)。completed 拒绝 (run_completed)。stop/resume/status 不要求 pre-engagement proof。
  • action=resume:只适用于 stopped。resumeRunRuntime() 以同 instanceId 容器 + 原 volume 恢复(volume 丢失走既有 recovery 从 local mirror 恢复),status=active。同 run.id / instanceId / currentStage / createdAt —— 不产生新 Run。幂等(active 时 → alreadyActive)。 不自动复活被 stop 取消的 Worker(避免重复 Tool Call / 重复攻击行为); Root 按当前 Stage 重新 delegate。
  • action=restart:destructive。顺序:validate proof → resolve target/ run → validate expected_target_id → 取消 active/starting/cancelling Delegations(排空 in-flight command 后 quiescent)→ sync → cleanupRunResources()(容器 + remote volume 都删)→ 删旧 workspace → 新 Run(新 UUID + 新 instanceId + restartedFrom 前身记录)→ save → init workspace/git → pre-engagement checkpoint。fresh confirmation: restart 必须由一次新的七题确认驱动 —— run.json 记录 preEngagementCallId(创建该 run 消费的确认 callId);同 proof 再次 restart 视为对已成功初始化 run 的重复调用(result delivery 丢失重试), 返回 alreadyRestarted no-op,绝不二次销毁。
  • action=status:纯 read-only(无 proof / Docker / workspace / Git / Run 写)。未绑定 → {bound:false};绑定 → {bound, runId, instanceId, status, currentStage, activeDelegations, ...}
  • Guards:stopped 时 pentester_delegate / pentester_advance_stage / pentester_rollback_stage 一律 run_stopped(暂停时不修改 timeline); completed 时一律 run_completed(终态只能 status / restart)。稳定错误 码前缀:run_not_initialized / run_already_exists / run_stopped / run_completed / target_mismatch
  • 事件(events.jsonl):run.started / run.stopped(含 reason)/ run.resumed / run.restarted(previousRunId + previousInstanceId,写在新 Run 起点 —— 旧 workspace 连同旧 event log 一并销毁)/ run.completed
  • Docker API 三层ensureRunContainer(恢复/确保 runtime)/ stopRunRuntime(停容器、保 volume,stop 用)/ cleanupRunResources(容器 + volume 都删,restart/destructive 用)。 资源操作一律以 dsh.pentester.managed/run/target labels 为 authority。
  • 未绑定 session 的既有 target 仍走 Continue / Restart 语义;stopped run 的恢复入口是 resume(Root 询问用户后调用,不自动 resume)。

pentester_run 幂等保护:start 的重复调用(transport retry / result delivery 丢失)返回确定性 no-op;restart 靠 preEngagementCallId 区分 「新确认驱动的真正 restart」与「对已成功初始化 run 的重复调用」。

PentestRun Instance Identity(run.json schemaVersion 3):每个 PentestRun 持有 id(内部 UUID,durable identity)与 instanceId (7 位 lowercase hex,人类可读 Run Instance Identity,如 abcd123)。 UI 在 Target 下方显示 #abcd123(灰色弱视觉 / monospace)与状态标签 (Active / Stopped / Completed,Harness semantic tokens;stopped 状态下 Trace/Output 数据照常可浏览);Docker 容器名 = dsh-pentester-<instanceId> —— 容器名不再由 target 文本生成(target 可含 敏感信息且跨 restart 稳定,正是旧版 restart 409 name-conflict 的根因)。 createRunInstanceId()(crypto random,generate → check → retry,上限 32 次)对 Project 内已有 run 的 instanceId 做碰撞检查。身份权威是 Docker labelsdsh.pentester.managed / .run(完整 UUID)/ .target / .instance);容器名只是 presentation / operational convenience,绝不解析 容器名反推 run identity。Legacy 兼容:V1/V2 run.json 没有 instanceId —— 加载时从 run.id 稳定推导(UUID 去 - 取前 7 hex,非 UUID 形态用 sha1 前 7 hex;同一 id 永远推导出同一值,read path 绝不 random),内存中即持有 instanceId / schemaVersion 3,下一次正式 saveRun() 落盘 V3;纯 read 不写盘。

Restart 的 Docker 生命周期mode=restart = 新 PentestRun 实例 (新 UUID + 新 instanceId,绝不复用旧 instanceId)。顺序:resolve 旧 run → cleanupRunRuntime(oldRunId, targetId, instanceId)DockerRuntime.cleanupRunResources: 按 label 找到旧 run 的 managed 容器,删除前经统一身份复核(managed/run/ target + instance 双方都有时必须一致)后 force remove;删除后重新 list 确认本 run 的 managed 容器确实消失(remove 撒谎 / 并发重建 → fail loud); 只有 404 / no-such 视为幂等 already-gone,403/500/daemon 不可达一律上抛 —— stop/restart 绝不误报成功;remote 模式同时移除旧 per-run volume)→ 删旧 workspace → 重建 → 新 run 保存。cleanup 失败 fail loud —— 旧数据 原样保留(workspace 绝不在容器状态未知时被删除)。ensure 侧自愈:同名 foreign 容器(非 managed)→ container_name_conflict;同名 managed 容器 但属于其它 run → container_instance_conflict(都不自动删除、不暴露 Docker 原始 409);同 run 孤儿容器(DSH 崩溃后存活)→ 复用,绝不建第二个。

Root 动态状态注入

每次 prompt assembly(创建 / 恢复 / compaction 后继续 / Worker wakeup 后继续), preset 的 run-state.mjs 行通过 systemPrompt.context 注入 compact 状态: target、当前 git branch、run 状态、current stage、各 stage 状态、当前 stage 的 Available AgentProfiles(由 Settings stageAgents[currentStage] ∩ AgentLibrary 生成,run-state.mjs 经 ctx.pentester.listAgentsForStage() 同步内存读取, 条目含 id/name/skills/source)、delegations 摘要 —— 从 run.json + git branch --show-current + AgentLibraryManager 生成, 不注入完整 run.json。Root 因此无需 status 工具。

源码布局

文件职责
src/index.tscordis 入口:apply / Config / inject;preset 发布;ctx.provide('pentester') 发布工具实现(不注册进 global registry)+ Settings RPC
src/model.ts6 概念类型 + Delegation status + StageDefinition + StageStatus + stageDir + Run Instance Identity(instanceId 生成/碰撞检查/legacy 推导)
src/stages.ts7 个 PTES StageDefinition(goal/exitCriteria/deliverables)+ DEFAULT_STAGE_AGENTS
src/agent-library/AgentLibrary:Agent/Skill 注册表(builtin 19 Agents:Topology V3,见 docs/agent-skill-allocation.md + custom)、profile 单继承解析、skill bundle 扫描、watcher(fs.watch+debounce+last-known-good)、manager(Settings+Library 组合根;create() 时对持久化 stageAgents 做一次通用 dangling reconciliation:全部可解析不动 / 部分 dangling 移除并 warning / 全部 dangling 恢复 DEFAULT_STAGE_AGENTS,修复 authoritative settings 而非 UI 过滤)、Typert RPC
src/store.tsrun.json 原子读写(target-scoped run.json,schemaVersion 1/2/3 兼容 + V3 instanceId hydration)+ serial mutation boundary(updateRun:authoritative 重读 + 单调 revision)+ saveRun stale-snapshot revision fence + stageStatuses
src/workspace.ts工作区骨架初始化、target/scope/roe 落盘、events.jsonl、stage summary、canonical promotion
src/git.tsGitCheckpointService:workspace git 仓库 + init/checkpoint/rollback timeline(injectable runner)
src/dsh.ts官方 Continuable Subagent seam 适配器:ctx.subagents.startContinuable('spawn')(durable child:followup/interrupt)+ trusted caller 推导
src/delegations.tsDelegation 生命周期:三阶段批处理(pure preflight → reservation → dispatch,0-side-effect preflight)+ cancel(经验证 interrupt authority:ancestor live Root agent / user=真实 direct parent;UNAUTHORIZED fail loud;active→cancelling→closed + in-flight 排空)+ task_prompt snapshot + 目录骨架(input/work/artifacts/evidence)+ result.md = 当前正式可交付结果
src/tools.ts5 个 Root 工具注册(经 pentester service,preset scope)+ caller=root 校验 + pentester_run 生命周期状态机(start/stop/resume/restart/status)+ advance(summary→transition→checkpoint)+ rollback + stopped/completed guards
src/worker-tools.tspentester_container_exec 注册(经 pentester service,preset scope)+ caller=worker-only 校验(childSessionId↔Delegation 绑定)+ 执行准入(F01:任何 Docker 副作用前校验 run/delegation 状态 + pre-exec authoritative recheck)+ 真实 exec.signal 透传 + ExecTracker(in-flight 跟踪,cancel/stop 排空)+ 结构化结果兼容投影(status/exitCode/timedOut/truncated)+ 默认 cwd = delegation work/
src/skill-search.tsdeterministic 检索(alias + structured + field-weighted BM25 + relation/family-aware MMR)+ recommendedLoad
src/skill-runtime.tsfrozen grant snapshot 读取(agent-profile.json,V4 供 native mount 用)+ legacy per-delegation skill runtime state 读写(V2 search/load 退役后仅存兼容读取)
src/query-normalizer.tsQuery normalization:alias 展开 + 单复数归一化 + tool/technique 检测
src/docker/host.tsdocker host 解析(本地优先回退链)+ 连接探测 + 引擎构造
src/docker/connection.tsSettings 测试连接:连接字符串解析(URL 类)、高级参数(TLS/SSH)→ dockerode 构造、version 探测 + 超时
src/docker/images.tsToolbox 白名单、inspect 原语
src/docker/runtime.ts硬化(HostConfig Binds+Mounts 双语法校验)、容器/卷生命周期(instance-based 容器名 + labels 权威 + 统一身份复核 + cleanup 404-only 幂等 + 删除后真实确认 + catalog 缓存失效)、exec(官方 docker-modem demuxStream + discriminated 结果 + 绝对 deadline + supervision guard 命令级终止 + 有界输出)、PathPolicy(HOST_CONTROL_PATHS)+ 流式 mirror 同步(原子发布 / symlink 不跟随 / 资源上限)、本地按白名单目录 bind / 远程 volume、recovery
src/rpc.tssrc/settings-store.tsDocker 设置的 Typert RPC、全局设置存储(V4:dockerHost + toolboxImages + stageAgents + stageAgentsDefaultsVersion + mcpServers;V3→V4 按 Stage 迁移默认拓扑,用户改过的 Stage 原样保留)
src/pentest/pre-engagement.ts固定七题问卷定义(含 stage_advance_policy)+ display label → authorization semantic 归一(normalizePreEngagementChoice)+ proof → StageAdvancePolicy 派生 + Host 侧 session event 批次校验
src/pentest/stage-advance-approval.tsmanual/custom 阶段推进人工确认的 Host validator(stage-scoped question id + fresh event-time fence;错误码 missing / declined / stale)
src/pentest/mcp-audit.tsNative MCP Audit Projection:child Session durable mcp__* tool/call + tool/result → per-server 聚合(Trace 数据面;无 runtime state)
src/ui/client/Settings:Overview / Agent Library / Docker 顶层 Tab(Docker 最右)+ Agent Library 内层(Agents / Skills / MCP / Stage Assignment)+ MCP Form/JSON 双编辑模式(mcp-json.ts:主流 mcpServers JSON import/export 纯函数)+ Overview MCP 摘要;Output 的 Markdown 预览经 pentester/MermaidMarkdown.tsx(普通 Markdown 仍走 Harness MarkdownText;```mermaid fence → MermaidDiagram:securityLevel 'strict'、dark/light 主题重渲、失败 fallback 保留原文;mermaid runtime 仅在存在 fence 时动态 import 执行 —— 当前 client build 单文件 CJS 无 code splitting,mermaid 代码随 bundle 分发但初始化延迟)
src/ui/client/pentester/trace/Pentester 面板 Trace 拓扑:Center Anchor(SpineAnchor left:50% + translateX(panX) 水平平移,scale 只作用于 TopologyWorld;Timeline 轨道不参与 pan)+ Skill/MCP 共享 ResourceChip(同尺寸同底色同边框,仅 dot/connector 类型色不同:blue / amber;统计只进 tooltip)+ Agent 下方 Deliverable 节点(Host 轻量摘要 DelegationView.deliveries:result/evidence/artifact/asset 按类别聚合 count + ≤5 sample names + navPath;Findings 走 canonical snapshot.findings;aggregation.ts 只消费 Host 投影,Client 不重新扫描 filesystem,full output tree 仍走 listOutput 懒加载)+ 横向 pan(pan.ts 纯数学:clamp / zoom-preserving / wheel 分类;背景拖拽 pan 且节点点击不劫持;普通滚轮永远交给 Harness 页面滚动,Ctrl/Cmd 滚轮缩放,Shift/触控板横向滚动 pan)
presets/pentester/Root persona + ask_user + run-state.mjs(preset scope 工具注册 + per-Root allow-list + 动态状态注入)

不变量

  • Delegation 的 task_prompt / objective 是不可变 snapshot;steer/followup 不改写。
  • 编排树只有 Root → Workers 一层;Worker 无 delegate 权限。
  • Worker 执行只用官方 seamctx.subagents.startContinuable()(provider spawn,durable child Session) 生产代码不存在 ctx.agents.create() / agent.whenIdle() Worker 路径,也不使用 ctx.jobs / one-shot SubagentRun.result/dispose;基础设施缺失时 fail loud,不 fallback。
  • Root 工具面 = 5 个 pentester 工具 + 原生 ask_user + subagent control。 收敛方式:(a) 组合排除通用工具(bash / fs / subagent / job / skill / workflow / goal 等是 per-preset 行,pentester 组合不带它们); (b) per-Root-agent 正向 tools.restrict({ allow: ROOT_ALLOW })(由 run-state.mjs 在 agent/created 时落在 Root 自己的 layer,Worker child 不受影响)—— allow-list 未列出的工具(container_exec、skill、全部 mcp__*、未来新增 global tools)一律不可见;Worker 工具面 = 官方 toolFilter allow-list(workerToolFilterpentester_container_exec + 官方 skill + 该 profile mcps 分配的 MCP 工具名)。
  • pentester 工具注册在 preset scope:standard Session 看不到 pentester_*;只有 pentester preset 的 standing mount 注册了它们。
  • container_exec 运行时授权(defense-in-depth):调用者必须是真实 subagent child 且其 SessionId 已绑定到一个 Delegation(宿主在 spawn 发布后立即写入 run.json);Root / 普通 Session / 未绑定 Session 一律拒绝。
  • container_exec 执行准入(F01):任何 Docker 副作用(ensureImage / 容器 / exec)之前校验 run.status === active 且 delegation 处于 active/starting;容器就绪后、命令启动前重读 authoritative run.json (同 run.id / instanceId / 状态 / delegation 状态)作 race fence —— stop/restart/cancel 竞态一律以稳定错误码拒绝 (run_stopped / run_completed / run_replaced / delegation_closed / delegation_failed / delegation_not_active)。
  • exec 结果结构化(F09/F10):multiplexed stream 用官方 docker-modem demuxStream 解码(全分片 property 测试保证任意合法切分等价);结果为 discriminated status(completed / timed_out / cancelled / runtime_error),未知退出状态绝不报 exitCode=0;绝对 deadline 覆盖 guard 安装 → create → start → 流消费 → inspect;timeout/AbortSignal (真实 exec.signal)经 host-installed supervision guard(/pentester/bin/ dsh-exec-guard:setsid 独立进程组 + PID 文件,argv 语义不变)以 TERM → grace → KILL 真正终止容器内命令 —— 只关网络流绝不冒充终止; stdout/stderr 每流有界(256KiB BoundedOutputSink + truncated 标记, 需要完整输出由 Worker 重定向到 workspace 文件)。
  • container_exec 只允许项目 Dockerfile 构建的 Toolbox 镜像;HostConfig 硬化(禁 privileged/host net/pid/ipc、capAdd 白名单、禁 docker.sock 挂载;Binds 与 Mounts 双语法都验证 —— bind source 禁 //docker.sock/DSH 控制根/.git/.dsh-pentester,target 必须在 /workspace 下且不得命中控制态)。
  • MCP 全部经官方 @deepseek-ai/dsh-mcp-client(Host 加载;连接 lifecycle = Host 共享实例,visibility = AgentProfile/Delegation 的 toolFilter allow-list)。不实现 MCP 协议 / spawn / reconnect。
  • MCP Mandatory Gate:profile.mcpIds 任一 unknown / disabled / error / 无工具 → pentester_delegate 纯 preflight 阶段整批 fail loud(确定性 mcp_* 错误码;0 side effects —— 无 Delegation 记录 / 目录 / event / child / Docker)。
  • MCP secrets 只存在于 Host 内存:env/headers ${ENV_NAME} 模板在 mount 前展开;resolved 值不落盘、不回传 UI、不进日志。
  • MCP 权限不扩大不变量:Worker 的 frozen toolFilter allow-list 不随 服务器 tool list 变化扩大;新 Delegation 按新 tool generation 创建。
  • 全局单一 docker host,支持 unix socket 与 http(s) 远程 daemon。优先级:settings.json(Settings UI "Docker Engine" Tab 可编辑,remote.pentesterDocker typed RPC,保存后热切换)> 插件 dockerHost 配置 > 本地 socket(自动发现,"未指定优先使用本地的")> DOCKER_HOST 环境变量 > 默认 socket。
  • 输出语言跟随:Root Agent 依据用户最近消息的语言生成一切输出(回复、task_prompt、报告);Worker 的最终回复与生成的文件内容(含报告)跟随 task_prompt 的语言。
  • Settings “Docker” Tab:DOCKER HOST(来源对齐 + 测试连接)、Toolbox Image(自定义镜像名 + ready 状态 + 刷新)。镜像为预构建模式(fb0sh/dsh-pentester-kali),不在 UI 内 pull/build。
  • 工具目录:镜像构建期探测生成 /pentester/tools.json(装漏的工具不会进目录);Worker 可自助 tool-info <name> 查询(宿主不在 prompt 里注入目录)。
  • 全流量记录(只记录,不分析):run 容器启动即执行 record-traffic.sh —— tcpdump 全网卡 pcap + mitmdump :8080 明文流;全部落在共享 volume 的 traffic/ 目录。
  • Workspace 挂载:本地 Docker → 按 Worker 白名单顶层目录逐目录 bindtarget/ assets/ findings/ stages/ report/ traffic//workspace/<dir>, 创建时已存在的目录才挂载;.git/.dsh-pentester/ 物理上不在容器内 —— 不可读、不可写,Persona 的声明与事实一致); 远程 Docker → per-run named volumedsh-pentester-<sha1(targetId:runId) 前 12 hex>, 1 PentestRun = 1 复用容器 = 1 volume;restart 清理旧 volume)。Run 容器名 dsh-pentester-<instanceId>(7 hex,target 文本绝不进容器名);容器与 volume 均携带 dsh.pentester.managed/run/target/instance labels(权威身份)。
  • 同步(仅远程):push(派发/advance/rollback 后,putArchive)与 pull(exec 后)均为 mirror 语义(create/update/delete)。集中 PathPolicy (HOST_CONTROL_PATHS = .git + .dsh-pentester):push 不送控制态进 容器;pull 在任何 mkdir/writeFile 之前拒绝控制态条目(容器侧归档绝不能 覆盖宿主 run.json / .git)。transport 全程 lstat、不跟随 symlink(根外 文件不可能进 archive,deleteMissing 不沿链接递归);普通文件 temp+rename 原子发布;资源上限(条目数 / 单文件 / 总量 / 路径长度)。
  • Git checkpoint(GitCheckpointService,src/git.ts):workspace 初始化时 git init -b main + 初始 commit workspace: initialize <target>;每阶段完成时 git add -A → commit ptes(<NN>-<stage>): complete → tag ptes/<NN>-<stage>。 commit message 与 tag 格式固定(约定式提交),不随语言变化。run.json 在 workspace git 树内,checkpoint commit 记录 transition 后的机器状态。 全 Workspace git 跟踪:不忽略 work/traffic/*.pcap*.mitm.gitignore 只保留 tmp/ cache/ *.log)。
  • Stage Advance Policy gatestageAdvancePolicy.mode 为 manual / custom (custom 保守 fallback 同 manual)时,advance 在任何 side effect 之前验证 Root Session 内存在针对当前 Stage 的人工推进确认(ask_user_question id stage_advance_confirm:<stageId>,选项 推进/暂不推进 —— reporting 为 确认完成/暂不完成;确认结果事件 time 必须晚于该 Stage 当前 entry 的 enteredAt,跨 Stage / 过期批准一律拒绝)。缺失/拒绝 → 稳定错误 stage_advance_confirmation_required;validator 见 src/pentest/stage-advance-approval.ts(与 pre-engagement 同一 durable event 事实源)。
  • advance 流程:mechanical validation(无 active/starting/cancelling delegation)→ 远程先 remote→local sync(本地模式 no-op)→ promotion (canonical 产物提升)→ 写 summary.md(Root 提供,Host 不解析)→ run transition(completed → active,经 serial mutation boundary + revision)→ git checkpoint → pushWorkspace(响应用 authoritative 重读)。首次 advance(无 run.json)额外承担 bootstrap(见上节)。
  • Root 工具响应(delegate / cancel / advance 后的 compact state)都让 Root 获得:current stage、allowed AgentProfiles、active delegations 摘要、最新 completion delta(§28)。
  • rollback 流程(pentester_rollback_stage):cancel 当前 workers(含 cancelling,逐个排空 in-flight command)→ dirty 则建 backup branch + WIP commit → first-parent 历史找目标 stage checkpoint → 从 checkpoint 建 rework/<next-stage>-<n> 并 checkout (run.json 随 checkout 还原)→ pushWorkspace。不重写历史、不自动 merge。
  • Worker prompt 的 trusted metadata(delegationId/stage/workspaceDir)由 host 注入, Root 不可伪造;Worker 默认 cwd 是自己的 delegation work/
  • result.md = 当前正式可交付结果:Worker 写/更新 result.md 作为当前 Delegation 的正式产出(不是 Session 终止标志)。continuable child 跨轮 持续存在;宿主不做自动 settle/完成检测。Worker 每轮最终回复总结所做、 所发现、所写文件与下一步建议。

已知安全让步(deferred)

无程序化 approval/scope 参数级检查;RoE/Scope 作为数据注入 prompt + 开始前 ask_user 预授权门。

Worker 文件写入边界是协作约束,不是机制级隔离(owner 决策):Worker 默认 只写自己的 Delegation 目录,但整个 /workspace 全局可读,且技术上同一共享 Container 内 Worker 仍可能写入其他 Workspace 路径。有意不实现 UID 隔离 / ACL / filesystem jail / 独立 container / mount namespace,也不把该约束宣传为 security boundary —— 多个 Worker 属于同一个授权 Pentest Project、同一个信任域, 当前架构优先保证共享 Workspace、多 Agent 协作、实现简单和可维护性。

docs/adr/0007