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-list(workerToolFilter(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 已存在 →alreadyInitializedno-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 →alreadyStoppedno-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 丢失重试), 返回alreadyRestartedno-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/targetlabels 为 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
labels(dsh.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.ts | cordis 入口:apply / Config / inject;preset 发布;ctx.provide('pentester') 发布工具实现(不注册进 global registry)+ Settings RPC |
src/model.ts | 6 概念类型 + Delegation status + StageDefinition + StageStatus + stageDir + Run Instance Identity(instanceId 生成/碰撞检查/legacy 推导) |
src/stages.ts | 7 个 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.ts | run.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.ts | GitCheckpointService: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.ts | Delegation 生命周期:三阶段批处理(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.ts | 5 个 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.ts | pentester_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.ts | deterministic 检索(alias + structured + field-weighted BM25 + relation/family-aware MMR)+ recommendedLoad |
src/skill-runtime.ts | frozen grant snapshot 读取(agent-profile.json,V4 供 native mount 用)+ legacy per-delegation skill runtime state 读写(V2 search/load 退役后仅存兼容读取) |
src/query-normalizer.ts | Query normalization:alias 展开 + 单复数归一化 + tool/technique 检测 |
src/docker/host.ts | docker host 解析(本地优先回退链)+ 连接探测 + 引擎构造 |
src/docker/connection.ts | Settings 测试连接:连接字符串解析(URL 类)、高级参数(TLS/SSH)→ dockerode 构造、version 探测 + 超时 |
src/docker/images.ts | Toolbox 白名单、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.ts、src/settings-store.ts | Docker 设置的 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.ts | manual/custom 阶段推进人工确认的 Host validator(stage-scoped question id + fresh event-time fence;错误码 missing / declined / stale) |
src/pentest/mcp-audit.ts | Native 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 执行只用官方 seam:
ctx.subagents.startContinuable()(providerspawn,durable child Session) 生产代码不存在ctx.agents.create()/agent.whenIdle()Worker 路径,也不使用ctx.jobs/ one-shotSubagentRun.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(workerToolFilter:pentester_container_exec+ 官方skill+ 该 profilemcps分配的 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.pentesterDockertyped 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 白名单顶层目录逐目录 bind
(
target/ assets/ findings/ stages/ report/ traffic/→/workspace/<dir>, 创建时已存在的目录才挂载;.git/与.dsh-pentester/物理上不在容器内 —— 不可读、不可写,Persona 的声明与事实一致); 远程 Docker → per-run named volume(dsh-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/instancelabels(权威身份)。 - 同步(仅远程):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+ 初始 commitworkspace: initialize <target>;每阶段完成时git add -A→ commitptes(<NN>-<stage>): complete→ tagptes/<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 gate:
stageAdvancePolicy.mode为 manual / custom (custom 保守 fallback 同 manual)时,advance 在任何 side effect 之前验证 Root Session 内存在针对当前 Stage 的人工推进确认(ask_user_question idstage_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 协作、实现简单和可维护性。