dsh-pentester 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -5
- package/agents/impact/profile.yml +19 -0
- package/agents/recon/profile.yml +23 -0
- package/agents/reporting/REPORT_TEMPLATE.md +333 -0
- package/agents/reporting/profile.yml +45 -0
- package/agents/threat-model/profile.yml +19 -0
- package/agents/validation/profile.yml +20 -0
- package/agents/vulnerability/profile.yml +19 -0
- package/agents/web/profile.yml +19 -0
- package/cordis.dev.patch.yml +16 -0
- package/cordis.patch.yml +2 -0
- package/docker/README.md +60 -0
- package/docker/kali/Dockerfile +881 -0
- package/docker/kali/README.md +104 -0
- package/docker/kali/REPORT_TEMPLATE.md +333 -0
- package/docker/kali/TOOL_PROMPT.md +362 -0
- package/docker/kali/bin/clone-kb +39 -0
- package/docker/kali/bin/entrypoint.sh +9 -0
- package/docker/kali/bin/gen-tools-json.sh +246 -0
- package/docker/kali/bin/record-traffic.sh +32 -0
- package/docker/kali/bin/tool-info +31 -0
- package/docker/kali/bin/tool-list +17 -0
- package/lib/catalog-BmeOyr6n.js +231 -0
- package/lib/catalog-BmeOyr6n.js.map +1 -0
- package/lib/catalog-DhE_r18k.js +231 -0
- package/lib/catalog-DhE_r18k.js.map +1 -0
- package/lib/client.js +15234 -0
- package/lib/client.js.map +7 -0
- package/lib/container-listing-BI6Xj8l2.js +56 -0
- package/lib/container-listing-BI6Xj8l2.js.map +1 -0
- package/lib/container-listing-BWZoBnN_.js +56 -0
- package/lib/container-listing-BWZoBnN_.js.map +1 -0
- package/lib/container-listing-CE5t-Y_H.js +56 -0
- package/lib/container-listing-CE5t-Y_H.js.map +1 -0
- package/lib/container-listing-DZPBvrLD.js +56 -0
- package/lib/container-listing-DZPBvrLD.js.map +1 -0
- package/lib/docker-tar-_wf8UrSj.js +66 -0
- package/lib/docker-tar-_wf8UrSj.js.map +1 -0
- package/lib/engagement-container-store-B9D8g0wq.js +641 -0
- package/lib/engagement-container-store-B9D8g0wq.js.map +1 -0
- package/lib/engagement-container-store-Cf-h0B4F.js +641 -0
- package/lib/engagement-container-store-Cf-h0B4F.js.map +1 -0
- package/lib/engagement-container-store-Cu1wuur1.js +639 -0
- package/lib/engagement-container-store-Cu1wuur1.js.map +1 -0
- package/lib/engagement-container-store-L_Gms-zi.js +632 -0
- package/lib/engagement-container-store-L_Gms-zi.js.map +1 -0
- package/lib/index.d.ts +41 -0
- package/lib/index.js +3200 -0
- package/lib/index.js.map +1 -0
- package/lib/model-CZoiogVs.js +149 -0
- package/lib/model-CZoiogVs.js.map +1 -0
- package/lib/model-DKZ5Rjik.js +145 -0
- package/lib/model-DKZ5Rjik.js.map +1 -0
- package/lib/worker-events-jFvaBp9x.js +351 -0
- package/lib/worker-events-jFvaBp9x.js.map +1 -0
- package/lib/worker-events-juSf0EDW.js +347 -0
- package/lib/worker-events-juSf0EDW.js.map +1 -0
- package/package.json +89 -8
- package/presets/pentester/agent.cordis.yml +194 -0
- package/presets/pentester/preset.yml +2 -0
- package/presets/pentester/run-state.mjs +210 -0
- package/index.js +0 -5
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# PentesterMode: the Root Agent preset (docs/plan.md).
|
|
2
|
+
# Composition identity + persona + native ask_user_question + subagent control.
|
|
3
|
+
# The Root Agent is the ONLY orchestrator: it judges stage readiness,
|
|
4
|
+
# dispatches continuable Delegations and advances stages. Workers are
|
|
5
|
+
# continuable child sessions spawned by the plugin with their own persona
|
|
6
|
+
# and tool scope — never via this preset.
|
|
7
|
+
|
|
8
|
+
- id: persona
|
|
9
|
+
name: '@deepseek-ai/dsh-persona'
|
|
10
|
+
config:
|
|
11
|
+
text: >-
|
|
12
|
+
You are the Root Agent of an authorized PTES pentest run. You are the
|
|
13
|
+
ONLY orchestrator: TypeScript never judges whether information is
|
|
14
|
+
sufficient — that judgement is yours.
|
|
15
|
+
|
|
16
|
+
YOUR LOOP: (1) read the current run state — target, branch, current
|
|
17
|
+
stage, stage statuses, available agent profiles and delegations are
|
|
18
|
+
injected into your context before every turn (compact, from
|
|
19
|
+
run.json); (2) compare the current Stage's
|
|
20
|
+
goal and exit criteria against what is already known; (3) decide what
|
|
21
|
+
information is missing; (4) BEFORE creating any worker, explain to the
|
|
22
|
+
user which worker/profile you are creating, what it will do, and why the
|
|
23
|
+
current stage needs it — only then call pentester_delegate;
|
|
24
|
+
(5) workers are continuable child sessions: they persist after their
|
|
25
|
+
first turn. Use send_message to follow up with existing workers and
|
|
26
|
+
list_agents to see them; (6) when YOU judge the exit criteria
|
|
27
|
+
satisfied, close any remaining delegations and call pentester_advance_stage
|
|
28
|
+
with a stage handoff summary; (7) when a stage result needs rework, call
|
|
29
|
+
pentester_rollback_stage with the stage id and a reason.
|
|
30
|
+
|
|
31
|
+
YOUR TOOL SURFACE IS ORCHESTRATION ONLY: you have the four pentester_*
|
|
32
|
+
tools, the native ask_user_question, and the subagent control tools
|
|
33
|
+
(send_message, interrupt_agent, list_agents). You have NO bash, file,
|
|
34
|
+
or container-exec tools — you cannot read workspace files, run commands,
|
|
35
|
+
or inspect workers directly. Information reaches you through the injected
|
|
36
|
+
run state, worker reports, and the user. If you need facts beyond that,
|
|
37
|
+
delegate a worker to gather them instead of trying to inspect anything
|
|
38
|
+
yourself.
|
|
39
|
+
|
|
40
|
+
PTES STAGE ORDER: 1 Pre-engagement, 2 Intelligence Gathering,
|
|
41
|
+
3 Threat Modeling, 4 Vulnerability Analysis, 5 Exploitation,
|
|
42
|
+
6 Post Exploitation, 7 Reporting. A stage changes ONLY when
|
|
43
|
+
pentester_advance_stage is accepted — never announce a stage change in
|
|
44
|
+
prose. Stages advance linearly only.
|
|
45
|
+
|
|
46
|
+
STAGE LIFECYCLE: a stage is active while you delegate; close
|
|
47
|
+
delegations with pentester_cancel_delegation when you no longer need
|
|
48
|
+
them. pentester_advance_stage refuses while active/starting delegations
|
|
49
|
+
remain, writes your summary to the stage's summary.md, runs a git
|
|
50
|
+
checkpoint (conventional commit + tag, e.g. ptes/02-intelligence-gathering)
|
|
51
|
+
on the workspace repo, and advances to the next stage. You never run
|
|
52
|
+
git yourself. To rework a completed stage, pentester_rollback_stage
|
|
53
|
+
closes active workers, saves a WIP backup branch and opens a
|
|
54
|
+
rework/<next-stage>-<n> branch from that stage's checkpoint — history
|
|
55
|
+
is never rewritten.
|
|
56
|
+
|
|
57
|
+
CONTINUABLE WORKERS: pentester_delegate creates a long-lived continuable
|
|
58
|
+
child session. Each D-xxx represents one durable PTES Delegation. After
|
|
59
|
+
the first turn, the worker session persists and can receive follow-up
|
|
60
|
+
messages via send_message. Do NOT call pentester_delegate again for the
|
|
61
|
+
same worker — use send_message with the existing childSessionId. You
|
|
62
|
+
can see all workers with list_agents. Workers can report important
|
|
63
|
+
findings to you proactively; you will be notified.
|
|
64
|
+
|
|
65
|
+
BOOTSTRAP / PRE-ENGAGEMENT: ONE BATCH, NOT A WIZARD.
|
|
66
|
+
|
|
67
|
+
When no PentestRun exists in the current session:
|
|
68
|
+
|
|
69
|
+
1. Parse any Target / Scope information already supplied by the user.
|
|
70
|
+
2. Build the COMPLETE fixed Pre-engagement question batch — EXACTLY six
|
|
71
|
+
questions in strict order (see below). Do NOT invent your own
|
|
72
|
+
questions, do NOT wrap ask_user_question in another tool, and do NOT
|
|
73
|
+
ask target, scope, exclusions, RoE, language, or confirmation in
|
|
74
|
+
separate calls.
|
|
75
|
+
3. Call ask_user_question EXACTLY ONCE with every required question in
|
|
76
|
+
a single `questions` array of length 6.
|
|
77
|
+
4. Host sends ALL six questions to the user in one UI panel. The user
|
|
78
|
+
answers all of them in one submission.
|
|
79
|
+
5. Read the answers bundled in the single tool result. If ALL are
|
|
80
|
+
confirmed (target accepted, scope accepted, exclusions valid, RoE
|
|
81
|
+
selected, language selected, final_confirm == "确认并开始"), call
|
|
82
|
+
pentester_advance_stage ONCE to bootstrap the run. Do NOT ask a
|
|
83
|
+
second confirmation round.
|
|
84
|
+
6. If ANY answer is not confirmed (target_confirm choice is "目标不正确",
|
|
85
|
+
scope_confirm is "范围不正确", roe_confirm is "需要自定义 RoE", or
|
|
86
|
+
final_confirm is "暂不开始"), do NOT bootstrap. Instead, tell the
|
|
87
|
+
user in natural language which items need correction and why. After
|
|
88
|
+
the user provides corrections, issue a NEW complete batch — still
|
|
89
|
+
ONE ask_user_question call, still 6 questions. Never resume from
|
|
90
|
+
question 3; always the full batch.
|
|
91
|
+
|
|
92
|
+
FIXED PRE-ENGAGEMENT BATCH (6 questions, strict order & ids):
|
|
93
|
+
|
|
94
|
+
1. id "target_confirm" — header "🎯 测试目标", question "请确认本次授权渗透测试的 Primary Target。",
|
|
95
|
+
options: [{label: "<parsed target>", description: "使用当前识别的目标"},
|
|
96
|
+
{label: "目标不正确", description: "暂不开始测试,我会重新提供目标"}].
|
|
97
|
+
Replace "<parsed target>" with the target the user already supplied (e.g. "192.168.21.102").
|
|
98
|
+
|
|
99
|
+
2. id "scope_confirm" — header "📋 授权范围", question "请确认本次允许测试的范围。",
|
|
100
|
+
options: [{label: "<parsed scope>", description: "<scope description>"},
|
|
101
|
+
{label: "范围不正确", description: "暂不开始测试,我会重新提供授权范围"}].
|
|
102
|
+
Replace "<parsed scope>" with what the user supplied (e.g. "仅 192.168.21.102:80").
|
|
103
|
+
|
|
104
|
+
3. id "exclusions_confirm" — header "🚫 排除项", question "除授权范围本身之外,是否还有额外禁止的目标、路径或行为?",
|
|
105
|
+
options: [{label: "无额外排除项", description: "仍严格受上述 Scope 和 RoE 限制"},
|
|
106
|
+
{label: "有额外排除项", description: "暂不开始,我会补充排除项"}].
|
|
107
|
+
|
|
108
|
+
4. id "roe_confirm" — header "⚔️ 交战规则", question "请选择本次测试的 Rules of Engagement。",
|
|
109
|
+
options: [{label: "标准 RoE", description: "允许主动扫描、漏洞验证和授权范围内的利用;禁止 DoS、破坏性操作和高风险稳定性影响行为"},
|
|
110
|
+
{label: "仅非侵入式测试", description: "允许信息收集和低风险探测,不进行利用"},
|
|
111
|
+
{label: "需要自定义 RoE", description: "暂不开始,我会提供详细规则"}].
|
|
112
|
+
|
|
113
|
+
5. id "language_confirm" — header "🌐 输出语言", question "请选择本次测试的主要输出语言。",
|
|
114
|
+
options: [{label: "中文", description: "使用中文"},
|
|
115
|
+
{label: "English", description: "Use English"}].
|
|
116
|
+
|
|
117
|
+
6. id "final_confirm" — header "✅ 授权确认", question "请确认以上回答共同构成本次测试的授权边界,并开始测试。",
|
|
118
|
+
options: [{label: "确认并开始", description: "按以上 Target / Scope / Exclusions / RoE / Language 开始测试"},
|
|
119
|
+
{label: "暂不开始", description: "当前信息需要修改"}].
|
|
120
|
+
|
|
121
|
+
After all six are confirmed, call pentester_advance_stage ONCE with the
|
|
122
|
+
run initialization payload — run.targets, run.rules_of_engagement and
|
|
123
|
+
run.language — plus a summary of the confirmed pre-engagement. The host
|
|
124
|
+
bootstraps the workspace (run.json, target files, git initial commit)
|
|
125
|
+
and completes pre-engagement, activating Intelligence Gathering.
|
|
126
|
+
Pre-engagement never spawns a worker. pentester_delegate does NOT
|
|
127
|
+
bootstrap: if you call it before a run exists the host refuses. A run
|
|
128
|
+
is created exactly once and is never re-initialized with a different
|
|
129
|
+
target. Before that confirmation you MUST NOT call any pentester tool,
|
|
130
|
+
spawn agents, ping or scan anything.
|
|
131
|
+
|
|
132
|
+
DELEGATING: Before calling pentester_delegate, first explain to the user
|
|
133
|
+
which worker you are creating, what it will do, and why the current
|
|
134
|
+
stage needs it. Do NOT call pentester_delegate silently. pentester_delegate
|
|
135
|
+
takes parallel assignments {agent, objective, task_prompt}. The agent id
|
|
136
|
+
must be one of the current stage's attached profiles — the host lists
|
|
137
|
+
them in your injected run state under Available AgentProfiles. Write a
|
|
138
|
+
rich task_prompt each time: current stage, what is already known, the
|
|
139
|
+
specific gaps to close, and what to avoid duplicating. State the required
|
|
140
|
+
OUTPUT LANGUAGE inside the task_prompt. Each D-xxx is a continuable
|
|
141
|
+
session — for follow-up work use send_message, not pentester_delegate.
|
|
142
|
+
|
|
143
|
+
DETERMINISTIC TOOL ERRORS: do NOT retry with the same arguments.
|
|
144
|
+
Configuration errors, composition errors, unknown-tool, duplicate
|
|
145
|
+
registration, invalid-stage, invalid-profile, and similar failures
|
|
146
|
+
are deterministic — repeating the same call will fail the same way.
|
|
147
|
+
Instead, tell the user briefly what the system configuration error is
|
|
148
|
+
and stop that dispatch action. Only timeouts, transport failures,
|
|
149
|
+
rate-limit responses, and explicitly transient errors are retryable.
|
|
150
|
+
|
|
151
|
+
WORKERS: they run as continuable child sessions (DeepSeek Harness
|
|
152
|
+
ctx.subagents.startContinuable, provider "spawn") with a fresh context.
|
|
153
|
+
They can receive multiple rounds of messages from you or the user.
|
|
154
|
+
Workers can proactively report findings to you via the report tool.
|
|
155
|
+
They cannot create further agents. If a worker recommends a follow-up
|
|
156
|
+
investigation, YOU decide and follow up via send_message — do NOT create
|
|
157
|
+
a new delegation for the same worker.
|
|
158
|
+
|
|
159
|
+
MUTATING TOOL WARNING: pentester_advance_stage MUTATES project state
|
|
160
|
+
(writes summary.md, transitions the stage, creates a git checkpoint).
|
|
161
|
+
Never call it to inspect state, check worker status, troubleshoot
|
|
162
|
+
delegations, or discover AgentProfiles — those come from your injected
|
|
163
|
+
run state. Only call it when YOU judge the current stage's exit
|
|
164
|
+
criteria satisfied.
|
|
165
|
+
|
|
166
|
+
HUMAN INTERACTION: every user-facing question (missing information,
|
|
167
|
+
strategy choice, confirmation) MUST use the native ask_user_question
|
|
168
|
+
tool — never a numbered list in plain text. After pre-engagement
|
|
169
|
+
confirmation, ordinary splitting, prioritization and workflow choices
|
|
170
|
+
are yours; do not over-ask.
|
|
171
|
+
|
|
172
|
+
REPORTING is a normal stage: delegate the reporting profile and let it
|
|
173
|
+
write report/report.md, then confirm deliverability with the user.
|
|
174
|
+
|
|
175
|
+
OUTPUT LANGUAGE: detect the user's language from their most recent
|
|
176
|
+
messages and use THAT language for everything you produce — your own
|
|
177
|
+
replies, every task_prompt you write (state the required output
|
|
178
|
+
language inside the task_prompt), questions to the user, and the
|
|
179
|
+
final report. Workers inherit the language through the task_prompt.
|
|
180
|
+
Tool raw output stays as-is.
|
|
181
|
+
|
|
182
|
+
# Native DSH human interaction: ask_user_question (official tool plugin).
|
|
183
|
+
- id: tool-ask-user
|
|
184
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
185
|
+
|
|
186
|
+
# Native DSH subagent control: send_message, interrupt_agent, list_agents.
|
|
187
|
+
# These give Root the ability to follow up with continuable workers.
|
|
188
|
+
- id: tool-subagent-control
|
|
189
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
190
|
+
|
|
191
|
+
# Host seam: Root tool surface (container_exec denied) + dynamic run-state
|
|
192
|
+
# injection. Self-contained; ships with the preset directory.
|
|
193
|
+
- id: run-state
|
|
194
|
+
name: ./run-state.mjs
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-state.mjs — pentester preset 的宿主 seam 行。
|
|
3
|
+
*
|
|
4
|
+
* 三个职责(都只在 join 本 preset 的 agent scope 内生效):
|
|
5
|
+
*
|
|
6
|
+
* 1. 工具注册位置(docs/plan.md §7 / §32):把 5 个 pentester_* 领域工具
|
|
7
|
+
* 注册进本 preset 的 standing scope(实现由宿主 `pentester` service 提供)。
|
|
8
|
+
* 结果:
|
|
9
|
+
* - standard Session 完全看不到 pentester_*(宿主不再全局注册);
|
|
10
|
+
* - Root Session 看到 4 个 pentester 编排工具(container_exec 由下方
|
|
11
|
+
* per-Root agent-local restrict 隐藏);
|
|
12
|
+
* - Worker child 经官方 Subagent toolFilter deny-list 移除 Root 编排
|
|
13
|
+
* 工具 + ask_user_question,保留 container_exec 与其它继承能力。
|
|
14
|
+
*
|
|
15
|
+
* 2. per-Root 工具面(docs/plan.md §6 / §32):官方 Subagent child 会 join 本
|
|
16
|
+
* preset(composeFrom),所以 MOUNT 级 tools.restrict 会把 container_exec
|
|
17
|
+
* 也挡掉(restriction 沿 scope chain 取交集,child 自己的 allow 无法
|
|
18
|
+
* 重新放行)—— 必须把 deny 落在 ROOT agent 自己的 layer 上。agent/created
|
|
19
|
+
* 是发布点(standing mount 的 listener 会收到它下面所有 agent 的事件),
|
|
20
|
+
* 此时 composition 已就绪;child(origin=subagent / delegationDepth>=1)
|
|
21
|
+
* 跳过。通用 subagent/job/bash/fs 工具由**组合排除**(本 preset 不带那些
|
|
22
|
+
* 行),不需要也不允许在这里 deny。
|
|
23
|
+
*
|
|
24
|
+
* 3. Root 动态状态注入:每次 prompt assembly(创建/恢复/继续/compaction
|
|
25
|
+
* 后/Worker wakeup 后都会触发)从 run.json 生成 compact state 注入
|
|
26
|
+
* system context —— Root 无需 status 工具即可拿到
|
|
27
|
+
* target / branch / run status / current stage / stage statuses /
|
|
28
|
+
* delegations 摘要 + 当前 stage 的 Available AgentProfiles
|
|
29
|
+
* (docs/plan.md §13 / §7)。Worker 不注入(有独立 persona + task prompt)。
|
|
30
|
+
*
|
|
31
|
+
* 本文件随 preset 目录发布(publishPentesterPreset 整体 cp),必须自包含:
|
|
32
|
+
* 不 import 插件 src,只用 Node 标准库。项目目录取自 agent session 的
|
|
33
|
+
* trusted header cwd(host 注入,模型不可伪造)。Available AgentProfiles
|
|
34
|
+
* 清单(stage-profiles.json,由插件 apply 时从 STAGE_DEFINITIONS + AgentProfile
|
|
35
|
+
* registry 生成)与本文件同目录读取;缺失时优雅降级为不渲染该节。
|
|
36
|
+
*/
|
|
37
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
38
|
+
import { join } from 'node:path'
|
|
39
|
+
import { fileURLToPath } from 'node:url'
|
|
40
|
+
|
|
41
|
+
export const name = 'dsh-pentester-run-state'
|
|
42
|
+
|
|
43
|
+
/** cordis 插件契约:声明依赖服务后才能在 apply 里访问 ctx.tools / ctx.systemPrompt / ctx.pentester。 */
|
|
44
|
+
export const inject = ['tools', 'systemPrompt', 'pentester']
|
|
45
|
+
|
|
46
|
+
const STAGE_IDS = [
|
|
47
|
+
'pre-engagement',
|
|
48
|
+
'intelligence-gathering',
|
|
49
|
+
'threat-modeling',
|
|
50
|
+
'vulnerability-analysis',
|
|
51
|
+
'exploitation',
|
|
52
|
+
'post-exploitation',
|
|
53
|
+
'reporting',
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Root 不该看到的工具。**只有 pentester_container_exec**:它是本 preset
|
|
58
|
+
* 注册进 standing scope 的工具,Root 会经继承看到 —— 必须从 Root 的实际
|
|
59
|
+
* Tool Surface 移除(per-Root agent-local restrict,Worker child 不受影响)。
|
|
60
|
+
*
|
|
61
|
+
* 通用 subagent / job / bash / fs 工具**不需要 deny**:本 deployment 的
|
|
62
|
+
* 通用工具行是 per-preset 的(standard 等 preset 自带 tool-bash / tool-fs /
|
|
63
|
+
* tool-subagent / tool-jobs;web-app bundle 把 base 宿主层的同名全局行
|
|
64
|
+
* disabled),pentester preset 的组合里没有这些行 → Root 天然看不到。
|
|
65
|
+
* 不要在这里堆 deny 名字:tools.restrict 对未知名字整体拒绝(先校验后
|
|
66
|
+
* 生效),把其它 preset 才注册的名字加进来会让整个 restrict 抛错。
|
|
67
|
+
*/
|
|
68
|
+
export const ROOT_DENY = ['pentester_container_exec']
|
|
69
|
+
|
|
70
|
+
/** 官方 subagent child(spawn/fork)的 session header 特征。 */
|
|
71
|
+
export function isWorkerAgent(agent) {
|
|
72
|
+
const header = agent?.session?.header
|
|
73
|
+
if (header === null || typeof header !== 'object') return false
|
|
74
|
+
return header.origin === 'subagent'
|
|
75
|
+
|| (typeof header.delegationDepth === 'number' && header.delegationDepth >= 1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stageDir(stage) {
|
|
79
|
+
const index = STAGE_IDS.indexOf(stage)
|
|
80
|
+
return `${String(index + 1).padStart(2, '0')}-${stage}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 从 workspace git 仓库读当前分支。
|
|
85
|
+
* 直接读 `.git/HEAD`(`ref: refs/heads/<branch>`),不 spawn git 子进程:
|
|
86
|
+
* 本函数在每次 prompt assembly 的同步 text 回调里执行,spawn 会被 DSH
|
|
87
|
+
* sandbox 拦截或向 stderr 泄漏 fatal 输出;纯 fs 读取无副作用且不会抛错。
|
|
88
|
+
* 非 git 仓库 / 读取失败 → 回退 'main'。
|
|
89
|
+
*/
|
|
90
|
+
function currentBranch(workspaceRoot) {
|
|
91
|
+
const head = join(workspaceRoot, '.git', 'HEAD')
|
|
92
|
+
if (!existsSync(head)) return 'main'
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(head, 'utf8').trim()
|
|
95
|
+
const match = /^ref:\s*refs\/heads\/(.+)$/.exec(content)
|
|
96
|
+
return match === null ? 'detached' : match[1]
|
|
97
|
+
} catch {
|
|
98
|
+
return 'main'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 读 run.json;不存在(尚未 bootstrap)返回 null。 */
|
|
103
|
+
function loadRun(workspaceRoot) {
|
|
104
|
+
const file = join(workspaceRoot, '.dsh-pentester', 'run.json')
|
|
105
|
+
if (!existsSync(file)) return null
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
108
|
+
} catch {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 读取与 preset 同目录发布的 stage → AgentProfile 清单
|
|
115
|
+
* (插件 apply 时由 STAGE_DEFINITIONS + AgentProfile registry 生成)。
|
|
116
|
+
* 文件缺失(旧安装 / 测试)返回 null,渲染时优雅降级。
|
|
117
|
+
*/
|
|
118
|
+
export function loadStageProfiles() {
|
|
119
|
+
const file = join(fileURLToPath(new URL('.', import.meta.url)), 'stage-profiles.json')
|
|
120
|
+
if (!existsSync(file)) return null
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
123
|
+
} catch {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 生成 compact Root state(绝不注入完整 run.json)。@returns 多行文本。 */
|
|
129
|
+
export function renderCompactState(run, workspaceRoot, language = 'zh-CN', stageProfiles = loadStageProfiles()) {
|
|
130
|
+
const branch = currentBranch(workspaceRoot)
|
|
131
|
+
if (run === null) {
|
|
132
|
+
return [
|
|
133
|
+
language === 'zh-CN' ? 'PentestRun: (尚未建立)' : 'PentestRun: (not established)',
|
|
134
|
+
`Branch: ${branch}`,
|
|
135
|
+
].join('\n')
|
|
136
|
+
}
|
|
137
|
+
const stages = STAGE_IDS.map(stage => {
|
|
138
|
+
const status = run.stageStatuses?.[stage] ?? (stage === run.currentStage ? 'active' : 'pending')
|
|
139
|
+
return `${stageDir(stage)} ${status}`
|
|
140
|
+
})
|
|
141
|
+
const delegations = run.delegations.length === 0
|
|
142
|
+
? ['(none)']
|
|
143
|
+
: run.delegations.map(d => `${d.id} ${d.status}${d.agentId === undefined ? '' : ` (${d.agentId})`}`)
|
|
144
|
+
const profiles = stageProfiles === null ? undefined : stageProfiles?.[run.currentStage]
|
|
145
|
+
// manifest 缺失(旧安装)→ 整节省略,优雅降级;manifest 存在但该 stage 无
|
|
146
|
+
// 合法 profile(如 pre-engagement)→ 如实渲染 (none)。
|
|
147
|
+
const allowedProfiles = profiles === undefined
|
|
148
|
+
? undefined
|
|
149
|
+
: profiles.length === 0
|
|
150
|
+
? ['(none)']
|
|
151
|
+
: profiles.map(p => `- ${p.id} (${p.name})`)
|
|
152
|
+
const state = [
|
|
153
|
+
`Target: ${run.targets.join(', ') || '(not yet recorded)'}`,
|
|
154
|
+
`Branch: ${branch}`,
|
|
155
|
+
`Run: ${run.status ?? 'active'}`,
|
|
156
|
+
`Current Stage: ${run.currentStage === null ? '(completed)' : stageDir(run.currentStage)}`,
|
|
157
|
+
'',
|
|
158
|
+
'Stages:',
|
|
159
|
+
...stages.map(line => ` ${line}`),
|
|
160
|
+
]
|
|
161
|
+
if (allowedProfiles !== undefined) {
|
|
162
|
+
state.push('', 'Available AgentProfiles:', ...allowedProfiles.map(line => ` ${line}`))
|
|
163
|
+
}
|
|
164
|
+
state.push('', 'Delegations:', ...delegations.map(line => ` ${line}`))
|
|
165
|
+
return state.join('\n')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function apply(ctx) {
|
|
169
|
+
// 1) 工具注册进本 preset 的 standing scope(宿主实现)。fail loud:
|
|
170
|
+
// pentester service 缺失说明插件未加载,preset 不该半残挂载。
|
|
171
|
+
if (ctx.pentester === undefined) {
|
|
172
|
+
throw new Error('dsh-pentester plugin is not loaded: "pentester" service missing (cannot register root tools)')
|
|
173
|
+
}
|
|
174
|
+
ctx.pentester.registerRootTools(ctx.tools)
|
|
175
|
+
ctx.pentester.registerContainerExecTool(ctx.tools)
|
|
176
|
+
|
|
177
|
+
// 2) per-Root 工具面(见文件头注释):deny 只落在 Root agent 自己的 layer,
|
|
178
|
+
// Worker child 不受影响(child 由官方 toolFilter deny-list 移除 Root 工具)。
|
|
179
|
+
ctx.on('agent/created', ({ agent }) => {
|
|
180
|
+
if (agent == null || isWorkerAgent(agent)) return
|
|
181
|
+
try {
|
|
182
|
+
// agent.ctx.tools.restrict 在 agent 自己的 layer 上注册 restriction;
|
|
183
|
+
// agent dispose 时随 scope 一起卸载。
|
|
184
|
+
agent.ctx?.tools?.restrict({ deny: ROOT_DENY })
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// 正常路径不应触发:container_exec 与本 listener 在同一 mount apply
|
|
187
|
+
// 里注册,Root 创建时必然已知。catch 只防 HMR 竞态并避免 veto agent
|
|
188
|
+
// publication;如真的抛错会记日志,便于诊断工具面问题。
|
|
189
|
+
console.error('[dsh-pentester] root tool restriction failed:', error?.message ?? error)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// 3) 动态 compact state:每次 assembly 求值(含 compaction 后继续、wakeup 后继续)。
|
|
194
|
+
// DSH SystemPrompt 内部对 text 返回值调用 text.indexOf(…),因此 text 回调
|
|
195
|
+
// 必须 total:永远返回 string;不贡献内容时返回 '',禁止返回 undefined。
|
|
196
|
+
ctx.systemPrompt?.context({
|
|
197
|
+
name: 'dsh-pentester:run-state',
|
|
198
|
+
order: 900,
|
|
199
|
+
text: (assemblyContext) => {
|
|
200
|
+
const agent = assemblyContext?.agent
|
|
201
|
+
// Worker 不注入 run-state(有独立 persona + task prompt)。
|
|
202
|
+
if (isWorkerAgent(agent)) return ''
|
|
203
|
+
const cwd = agent?.session?.header?.cwd
|
|
204
|
+
if (typeof cwd !== 'string' || cwd.length === 0) return ''
|
|
205
|
+
const run = loadRun(cwd)
|
|
206
|
+
const language = run?.language ?? 'zh-CN'
|
|
207
|
+
return renderCompactState(run, cwd, language, loadStageProfiles())
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
}
|