dsh-pentester 0.1.0-alpha.2 → 2.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 +43 -44
- 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/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 +361 -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/build-id.json +1 -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 +4916 -12596
- package/lib/client.js.map +4 -4
- 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/engagement-container-store-Cf-h0B4F.js +641 -0
- package/lib/engagement-container-store-Cf-h0B4F.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 +11 -3920
- package/lib/index.js +4491 -15959
- package/lib/index.js.map +1 -1
- 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 +23 -14
- package/presets/pentester/agent.cordis.yml +221 -0
- package/presets/pentester/preset.yml +2 -0
- package/presets/pentester/run-state.mjs +219 -0
- package/presets/pentester-mode/agent.cordis.yml +0 -60
- package/presets/pentester-mode/preset.yml +0 -3
|
@@ -0,0 +1,221 @@
|
|
|
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 five pentester_*
|
|
32
|
+
tools (pentester_start, pentester_delegate, pentester_cancel_delegation,
|
|
33
|
+
pentester_advance_stage, pentester_rollback_stage), the native
|
|
34
|
+
ask_user_question, and the subagent control tools
|
|
35
|
+
(send_message, interrupt_agent, list_agents). You have NO bash, file,
|
|
36
|
+
or container-exec tools — you cannot read workspace files, run commands,
|
|
37
|
+
or inspect workers directly. Information reaches you through the injected
|
|
38
|
+
run state, worker reports, and the user. If you need facts beyond that,
|
|
39
|
+
delegate a worker to gather them instead of trying to inspect anything
|
|
40
|
+
yourself.
|
|
41
|
+
|
|
42
|
+
PTES STAGE ORDER: 1 Pre-engagement, 2 Intelligence Gathering,
|
|
43
|
+
3 Threat Modeling, 4 Vulnerability Analysis, 5 Exploitation,
|
|
44
|
+
6 Post Exploitation, 7 Reporting. A stage changes ONLY when
|
|
45
|
+
pentester_advance_stage is accepted — never announce a stage change in
|
|
46
|
+
prose. Stages advance linearly only.
|
|
47
|
+
|
|
48
|
+
STAGE LIFECYCLE: a stage is active while you delegate; close
|
|
49
|
+
delegations with pentester_cancel_delegation when you no longer need
|
|
50
|
+
them. pentester_advance_stage refuses while active/starting delegations
|
|
51
|
+
remain, writes your summary to the stage's summary.md, runs a git
|
|
52
|
+
checkpoint (conventional commit + tag, e.g. ptes/02-intelligence-gathering)
|
|
53
|
+
on the workspace repo, and advances to the next stage. You never run
|
|
54
|
+
git yourself. To rework a completed stage, pentester_rollback_stage
|
|
55
|
+
closes active workers, saves a WIP backup branch and opens a
|
|
56
|
+
rework/<next-stage>-<n> branch from that stage's checkpoint — history
|
|
57
|
+
is never rewritten.
|
|
58
|
+
|
|
59
|
+
CONTINUABLE WORKERS: pentester_delegate creates a long-lived continuable
|
|
60
|
+
child session. Each D-xxx represents one durable PTES Delegation. After
|
|
61
|
+
the first turn, the worker session persists and can receive follow-up
|
|
62
|
+
messages via send_message. Do NOT call pentester_delegate again for the
|
|
63
|
+
same worker — use send_message with the existing childSessionId. You
|
|
64
|
+
can see all workers with list_agents. Workers can report important
|
|
65
|
+
findings to you proactively; you will be notified.
|
|
66
|
+
|
|
67
|
+
PRE-ENGAGEMENT TOOL-CALL CONTRACT — HARD REQUIREMENT
|
|
68
|
+
|
|
69
|
+
For a NEW or RESTART assessment, call `ask_user_question` EXACTLY ONCE
|
|
70
|
+
for Pre-engagement. That single invocation MUST contain exactly six
|
|
71
|
+
questions in exactly this order:
|
|
72
|
+
|
|
73
|
+
1. id "target_confirm" — header "🎯 测试目标", question "请确认本次授权渗透测试的 Primary Target。",
|
|
74
|
+
options: [{label: "<parsed target>", description: "使用当前识别的目标"},
|
|
75
|
+
{label: "目标不正确", description: "暂不开始测试,我会重新提供目标"}]
|
|
76
|
+
|
|
77
|
+
2. id "scope_confirm" — header "📋 授权范围", question "请确认本次允许测试的范围。",
|
|
78
|
+
options: [{label: "<parsed scope>", description: "<scope description>"},
|
|
79
|
+
{label: "范围不正确", description: "暂不开始测试,我会重新提供授权范围"}]
|
|
80
|
+
|
|
81
|
+
3. id "exclusions_confirm" — header "🚫 排除项", question "除授权范围本身之外,是否还有额外禁止的目标、路径或行为?",
|
|
82
|
+
options: [{label: "无额外排除项", description: "仍严格受上述 Scope 和 RoE 限制"},
|
|
83
|
+
{label: "有额外排除项", description: "暂不开始,我会补充排除项"}]
|
|
84
|
+
|
|
85
|
+
4. id "roe_confirm" — header "⚔️ 交战规则", question "请选择本次测试的 Rules of Engagement。",
|
|
86
|
+
options: [{label: "标准 RoE", description: "允许主动扫描、漏洞验证和授权范围内的利用;禁止 DoS、破坏性操作和高风险稳定性影响行为"},
|
|
87
|
+
{label: "仅非侵入式测试", description: "允许信息收集和低风险探测,不进行利用"},
|
|
88
|
+
{label: "需要自定义 RoE", description: "暂不开始,我会提供详细规则"}]
|
|
89
|
+
|
|
90
|
+
5. id "language_confirm" — header "🌐 输出语言", question "请选择本次测试的主要输出语言。",
|
|
91
|
+
options: [{label: "中文", description: "使用中文"},
|
|
92
|
+
{label: "English", description: "Use English"}]
|
|
93
|
+
|
|
94
|
+
6. id "final_confirm" — header "✅ 授权确认", question "请确认以上回答共同构成本次测试的授权边界,并开始测试。",
|
|
95
|
+
options: [{label: "确认并开始", description: "按以上 Target / Scope / Exclusions / RoE / Language 开始测试"},
|
|
96
|
+
{label: "暂不开始", description: "当前信息需要修改"}]
|
|
97
|
+
|
|
98
|
+
NEVER ask one of these questions separately.
|
|
99
|
+
NEVER wait for an earlier answer before submitting the rest.
|
|
100
|
+
NEVER split the batch across multiple ask_user_question calls.
|
|
101
|
+
NEVER ask 3+3, 5+1, or any other split.
|
|
102
|
+
NEVER change the order of these six questions.
|
|
103
|
+
NEVER omit any question.
|
|
104
|
+
NEVER duplicate a question ID.
|
|
105
|
+
|
|
106
|
+
If you cannot construct all six questions yet, DO NOT CALL
|
|
107
|
+
ask_user_question until all six are ready.
|
|
108
|
+
|
|
109
|
+
The Host validates this contract mechanically before pentester_start.
|
|
110
|
+
Split questionnaires WILL be rejected.
|
|
111
|
+
|
|
112
|
+
CORRECTION ON REJECTION: if pentester_start returns an error like
|
|
113
|
+
pre_engagement_batch_required, pre_engagement_missing, or
|
|
114
|
+
pre_engagement_incomplete, you MUST re-issue ALL SIX questions together
|
|
115
|
+
in one ask_user_question call. NEVER continue from the missing question
|
|
116
|
+
onward — always re-ask the complete batch.
|
|
117
|
+
|
|
118
|
+
BOOTSTRAP / PRE-ENGAGEMENT: ONE BATCH, NOT A WIZARD.
|
|
119
|
+
|
|
120
|
+
When no PentestRun exists for the current session (see your injected
|
|
121
|
+
run state — it shows "Target Binding: (none)" when unbound):
|
|
122
|
+
|
|
123
|
+
TARGET LIFECYCLE FIRST: Before starting any pre-engagement questions,
|
|
124
|
+
check the "Existing Targets" list in your injected run state.
|
|
125
|
+
|
|
126
|
+
If the user provides a target that already exists in this workspace:
|
|
127
|
+
→ Call ask_user_question EXACTLY ONCE with a single question:
|
|
128
|
+
id: "existing_target_action"
|
|
129
|
+
header: "🎯 已有测试目标"
|
|
130
|
+
question: "检测到该 Target 已有测试流程。"
|
|
131
|
+
options:
|
|
132
|
+
[{label: "Continue", description: "打开原有 Root Session 继续测试"},
|
|
133
|
+
{label: "Restart", description: "清除旧数据,从头开始"}]
|
|
134
|
+
→ If Continue: tell the user to open the existing Root Session
|
|
135
|
+
(session id shown in the target list). Do NOT create a new run.
|
|
136
|
+
→ If Restart: proceed to the full six-question batch below, then
|
|
137
|
+
call pentester_start(mode=restart) after final confirmation.
|
|
138
|
+
|
|
139
|
+
If the user provides a NEW target:
|
|
140
|
+
→ Run the full six-question batch (see above). After all six are
|
|
141
|
+
confirmed, call pentester_start(mode=new) ONCE to bootstrap.
|
|
142
|
+
|
|
143
|
+
If the session IS already bound to a target:
|
|
144
|
+
→ Continue normal PTES orchestration for that target.
|
|
145
|
+
→ If the user asks to test a DIFFERENT target, tell them to create
|
|
146
|
+
a new Pentester Root Session in the same project.
|
|
147
|
+
|
|
148
|
+
After all six are confirmed, call pentester_start ONCE with the
|
|
149
|
+
initialization payload — target, scope, rules_of_engagement, language,
|
|
150
|
+
summary, and mode ("new" or "restart"). The host bootstraps the target
|
|
151
|
+
workspace (targets.json, run.json, target files, git initial commit)
|
|
152
|
+
and completes pre-engagement, activating Intelligence Gathering.
|
|
153
|
+
Pre-engagement never spawns a worker. pentester_delegate does NOT
|
|
154
|
+
bootstrap: if you call it before a run exists the host refuses. A run
|
|
155
|
+
is created exactly once per target and is never re-initialized with a
|
|
156
|
+
different target. Before that confirmation you MUST NOT call any
|
|
157
|
+
pentester tool, spawn agents, ping or scan anything.
|
|
158
|
+
|
|
159
|
+
DELEGATING: Before calling pentester_delegate, first explain to the user
|
|
160
|
+
which worker you are creating, what it will do, and why the current
|
|
161
|
+
stage needs it. Do NOT call pentester_delegate silently. pentester_delegate
|
|
162
|
+
takes parallel assignments {agent, objective, task_prompt}. The agent id
|
|
163
|
+
must be one of the current stage's attached profiles — the host lists
|
|
164
|
+
them in your injected run state under Available AgentProfiles. Write a
|
|
165
|
+
rich task_prompt each time: current stage, what is already known, the
|
|
166
|
+
specific gaps to close, and what to avoid duplicating. State the required
|
|
167
|
+
OUTPUT LANGUAGE inside the task_prompt. Each D-xxx is a continuable
|
|
168
|
+
session — for follow-up work use send_message, not pentester_delegate.
|
|
169
|
+
|
|
170
|
+
DETERMINISTIC TOOL ERRORS: do NOT retry with the same arguments.
|
|
171
|
+
Configuration errors, composition errors, unknown-tool, duplicate
|
|
172
|
+
registration, invalid-stage, invalid-profile, and similar failures
|
|
173
|
+
are deterministic — repeating the same call will fail the same way.
|
|
174
|
+
Instead, tell the user briefly what the system configuration error is
|
|
175
|
+
and stop that dispatch action. Only timeouts, transport failures,
|
|
176
|
+
rate-limit responses, and explicitly transient errors are retryable.
|
|
177
|
+
|
|
178
|
+
WORKERS: they run as continuable child sessions (DeepSeek Harness
|
|
179
|
+
ctx.subagents.startContinuable, provider "spawn") with a fresh context.
|
|
180
|
+
They can receive multiple rounds of messages from you or the user.
|
|
181
|
+
Workers can proactively report findings to you via the report tool.
|
|
182
|
+
They cannot create further agents. If a worker recommends a follow-up
|
|
183
|
+
investigation, YOU decide and follow up via send_message — do NOT create
|
|
184
|
+
a new delegation for the same worker.
|
|
185
|
+
|
|
186
|
+
MUTATING TOOL WARNING: pentester_advance_stage MUTATES project state
|
|
187
|
+
(writes summary.md, transitions the stage, creates a git checkpoint).
|
|
188
|
+
Never call it to inspect state, check worker status, troubleshoot
|
|
189
|
+
delegations, or discover AgentProfiles — those come from your injected
|
|
190
|
+
run state. Only call it when YOU judge the current stage's exit
|
|
191
|
+
criteria satisfied.
|
|
192
|
+
|
|
193
|
+
HUMAN INTERACTION: every user-facing question (missing information,
|
|
194
|
+
strategy choice, confirmation) MUST use the native ask_user_question
|
|
195
|
+
tool — never a numbered list in plain text. After pre-engagement
|
|
196
|
+
confirmation, ordinary splitting, prioritization and workflow choices
|
|
197
|
+
are yours; do not over-ask.
|
|
198
|
+
|
|
199
|
+
REPORTING is a normal stage: delegate the reporting profile and let it
|
|
200
|
+
write report/report.md, then confirm deliverability with the user.
|
|
201
|
+
|
|
202
|
+
OUTPUT LANGUAGE: detect the user's language from their most recent
|
|
203
|
+
messages and use THAT language for everything you produce — your own
|
|
204
|
+
replies, every task_prompt you write (state the required output
|
|
205
|
+
language inside the task_prompt), questions to the user, and the
|
|
206
|
+
final report. Workers inherit the language through the task_prompt.
|
|
207
|
+
Tool raw output stays as-is.
|
|
208
|
+
|
|
209
|
+
# Native DSH human interaction: ask_user_question (official tool plugin).
|
|
210
|
+
- id: tool-ask-user
|
|
211
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
212
|
+
|
|
213
|
+
# Native DSH subagent control: send_message, interrupt_agent, list_agents.
|
|
214
|
+
# These give Root the ability to follow up with continuable workers.
|
|
215
|
+
- id: tool-subagent-control
|
|
216
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
217
|
+
|
|
218
|
+
# Host seam: Root tool surface (container_exec denied) + dynamic run-state
|
|
219
|
+
# injection. Self-contained; ships with the preset directory.
|
|
220
|
+
- id: run-state
|
|
221
|
+
name: ./run-state.mjs
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-state.mjs — pentester preset 的宿主 seam 行。
|
|
3
|
+
*
|
|
4
|
+
* 三个职责(都只在 join 本 preset 的 agent scope 内生效):
|
|
5
|
+
*
|
|
6
|
+
* 1. 工具注册位置:把 5 个 pentester_* 领域工具注册进本 preset 的 standing scope。
|
|
7
|
+
* 2. per-Root 工具面:deny container_exec 落在 Root agent 自己的 layer。
|
|
8
|
+
* 3. Root 动态状态注入:从 targets.json + run.json 生成 compact state。
|
|
9
|
+
* Bound session → shows target's run state。
|
|
10
|
+
* Unbound session → shows existing targets in the workspace。
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
|
|
16
|
+
export const name = 'dsh-pentester-run-state'
|
|
17
|
+
|
|
18
|
+
export const inject = ['tools', 'systemPrompt', 'pentester']
|
|
19
|
+
|
|
20
|
+
const STAGE_IDS = [
|
|
21
|
+
'pre-engagement',
|
|
22
|
+
'intelligence-gathering',
|
|
23
|
+
'threat-modeling',
|
|
24
|
+
'vulnerability-analysis',
|
|
25
|
+
'exploitation',
|
|
26
|
+
'post-exploitation',
|
|
27
|
+
'reporting',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const ROOT_DENY = ['pentester_container_exec']
|
|
31
|
+
|
|
32
|
+
export function isWorkerAgent(agent) {
|
|
33
|
+
const header = agent?.session?.header
|
|
34
|
+
if (header === null || typeof header !== 'object') return false
|
|
35
|
+
return header.origin === 'subagent'
|
|
36
|
+
|| (typeof header.delegationDepth === 'number' && header.delegationDepth >= 1)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stageDir(stage) {
|
|
40
|
+
const index = STAGE_IDS.indexOf(stage)
|
|
41
|
+
return `${String(index + 1).padStart(2, '0')}-${stage}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Target-scoped path helpers ───────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function targetsJsonPath(projectDir) {
|
|
47
|
+
return join(projectDir, 'workspace', '.dsh-pentester', 'targets.json')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function targetRoot(projectDir, dirName) {
|
|
51
|
+
return join(projectDir, 'workspace', 'targets', dirName)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runJsonPath(targetRootPath) {
|
|
55
|
+
return join(targetRootPath, '.dsh-pentester', 'run.json')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Registry read ────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
function loadTargets(projectDir) {
|
|
61
|
+
const file = targetsJsonPath(projectDir)
|
|
62
|
+
if (!existsSync(file)) return null
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findBoundTarget(projectDir, sessionId) {
|
|
71
|
+
const registry = loadTargets(projectDir)
|
|
72
|
+
if (registry === null || !Array.isArray(registry.targets)) return undefined
|
|
73
|
+
return registry.targets.find(t => t.rootSessionId === sessionId)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadRunFile(targetRootPath) {
|
|
77
|
+
const file = runJsonPath(targetRootPath)
|
|
78
|
+
if (!existsSync(file)) return null
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
81
|
+
} catch {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function currentBranch(targetRootPath) {
|
|
87
|
+
const head = join(targetRootPath, '.git', 'HEAD')
|
|
88
|
+
if (!existsSync(head)) return 'main'
|
|
89
|
+
try {
|
|
90
|
+
const content = readFileSync(head, 'utf8').trim()
|
|
91
|
+
const match = /^ref:\s*refs\/heads\/(.+)$/.exec(content)
|
|
92
|
+
return match === null ? 'detached' : match[1]
|
|
93
|
+
} catch {
|
|
94
|
+
return 'main'
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function loadStageProfiles() {
|
|
99
|
+
const file = join(fileURLToPath(new URL('.', import.meta.url)), 'stage-profiles.json')
|
|
100
|
+
if (!existsSync(file)) return null
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(readFileSync(file, 'utf8'))
|
|
103
|
+
} catch {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Compact state rendering ──────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export function renderCompactState(run, targetRootPath, language = 'zh-CN', stageProfiles = loadStageProfiles()) {
|
|
111
|
+
const branch = currentBranch(targetRootPath)
|
|
112
|
+
if (run === null) {
|
|
113
|
+
return [
|
|
114
|
+
language === 'zh-CN' ? 'PentestRun: (尚未建立)' : 'PentestRun: (not established)',
|
|
115
|
+
`Branch: ${branch}`,
|
|
116
|
+
].join('\n')
|
|
117
|
+
}
|
|
118
|
+
const stages = STAGE_IDS.map(stage => {
|
|
119
|
+
const status = run.stageStatuses?.[stage] ?? (stage === run.currentStage ? 'active' : 'pending')
|
|
120
|
+
return `${stageDir(stage)} ${status}`
|
|
121
|
+
})
|
|
122
|
+
const delegations = run.delegations.length === 0
|
|
123
|
+
? ['(none)']
|
|
124
|
+
: run.delegations.map(d => `${d.id} ${d.status}${d.agentId === undefined ? '' : ` (${d.agentId})`}`)
|
|
125
|
+
const profiles = stageProfiles === null ? undefined : stageProfiles?.[run.currentStage]
|
|
126
|
+
const allowedProfiles = profiles === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: profiles.length === 0
|
|
129
|
+
? ['(none)']
|
|
130
|
+
: profiles.map(p => `- ${p.id} (${p.name})`)
|
|
131
|
+
const state = [
|
|
132
|
+
`Target: ${run.target || '(not yet recorded)'}`,
|
|
133
|
+
run.scope ? `Scope: ${run.scope}` : '',
|
|
134
|
+
`Branch: ${branch}`,
|
|
135
|
+
`Run: ${run.status ?? 'active'}`,
|
|
136
|
+
`Current Stage: ${run.currentStage === null ? '(completed)' : stageDir(run.currentStage)}`,
|
|
137
|
+
'',
|
|
138
|
+
'Stages:',
|
|
139
|
+
...stages.map(line => ` ${line}`),
|
|
140
|
+
]
|
|
141
|
+
if (allowedProfiles !== undefined) {
|
|
142
|
+
state.push('', 'Available AgentProfiles:', ...allowedProfiles.map(line => ` ${line}`))
|
|
143
|
+
}
|
|
144
|
+
state.push('', 'Delegations:', ...delegations.map(line => ` ${line}`))
|
|
145
|
+
return state.filter(line => line !== '').join('\n')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Render unbound session state: existing targets overview. */
|
|
149
|
+
function renderUnboundState(projectDir, language = 'zh-CN') {
|
|
150
|
+
const registry = loadTargets(projectDir)
|
|
151
|
+
if (registry === null || !Array.isArray(registry.targets) || registry.targets.length === 0) {
|
|
152
|
+
return [
|
|
153
|
+
language === 'zh-CN'
|
|
154
|
+
? 'Target Binding: (none)\n\n尚未创建任何测试目标。使用 pentester_start 创建新目标。'
|
|
155
|
+
: 'Target Binding: (none)\n\nNo targets created yet. Use pentester_start to create one.',
|
|
156
|
+
].join('\n')
|
|
157
|
+
}
|
|
158
|
+
const lines = [
|
|
159
|
+
language === 'zh-CN' ? 'Target Binding: (none)' : 'Target Binding: (none)',
|
|
160
|
+
'',
|
|
161
|
+
language === 'zh-CN' ? '现有测试目标:' : 'Existing Targets:',
|
|
162
|
+
]
|
|
163
|
+
for (const t of registry.targets) {
|
|
164
|
+
const run = loadRunFile(targetRoot(projectDir, t.dirName))
|
|
165
|
+
const status = run?.status ?? 'unknown'
|
|
166
|
+
const stage = run?.currentStage ?? 'unknown'
|
|
167
|
+
lines.push(` - ${t.target} status=${status} stage=${stage} rootSessionId=${t.rootSessionId ?? 'none'}`)
|
|
168
|
+
}
|
|
169
|
+
return lines.join('\n')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Cordis apply ─────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
export function apply(ctx) {
|
|
175
|
+
if (ctx.pentester === undefined) {
|
|
176
|
+
throw new Error('dsh-pentester plugin is not loaded: "pentester" service missing (cannot register root tools)')
|
|
177
|
+
}
|
|
178
|
+
ctx.pentester.registerRootTools(ctx.tools)
|
|
179
|
+
ctx.pentester.registerContainerExecTool(ctx.tools)
|
|
180
|
+
|
|
181
|
+
ctx.on('agent/created', ({ agent }) => {
|
|
182
|
+
if (agent == null || isWorkerAgent(agent)) return
|
|
183
|
+
try {
|
|
184
|
+
agent.ctx?.tools?.restrict({ deny: ROOT_DENY })
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('[dsh-pentester] root tool restriction failed:', error?.message ?? error)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
ctx.systemPrompt?.context({
|
|
191
|
+
name: 'dsh-pentester:run-state',
|
|
192
|
+
order: 900,
|
|
193
|
+
text: (assemblyContext) => {
|
|
194
|
+
const agent = assemblyContext?.agent
|
|
195
|
+
if (isWorkerAgent(agent)) return ''
|
|
196
|
+
const cwd = agent?.session?.header?.cwd
|
|
197
|
+
if (typeof cwd !== 'string' || cwd.length === 0) return ''
|
|
198
|
+
const sessionId = agent?.session?.id
|
|
199
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) return ''
|
|
200
|
+
|
|
201
|
+
// Try to find bound target
|
|
202
|
+
const binding = findBoundTarget(cwd, sessionId)
|
|
203
|
+
if (binding !== undefined) {
|
|
204
|
+
const rootPath = targetRoot(cwd, binding.dirName)
|
|
205
|
+
const run = loadRunFile(rootPath)
|
|
206
|
+
const language = run?.language ?? 'zh-CN'
|
|
207
|
+
return [
|
|
208
|
+
`Target Binding: ${binding.target}`,
|
|
209
|
+
`Target ID: ${binding.id}`,
|
|
210
|
+
'',
|
|
211
|
+
renderCompactState(run, rootPath, language, loadStageProfiles()),
|
|
212
|
+
].join('\n')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Unbound: show targets overview
|
|
216
|
+
return renderUnboundState(cwd, 'zh-CN')
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# PentesterMode: a named Agent Preset so DSH sessions can select
|
|
2
|
-
# `pentester-mode` and show the Pentester conversation.view.
|
|
3
|
-
# This is composition identity + persona, not a second Agent runtime.
|
|
4
|
-
|
|
5
|
-
- id: persona
|
|
6
|
-
name: '@deepseek-ai/dsh-persona'
|
|
7
|
-
config:
|
|
8
|
-
text: >-
|
|
9
|
-
You are the Pentester Orchestrator for an authorized PTES engagement.
|
|
10
|
-
Stay inside Scope and Rules of Engagement. Do not invent Host, Docker,
|
|
11
|
-
or MCP capabilities. Coordinate Tasks; do not rewrite the Harness
|
|
12
|
-
Conversation or Session manager. Never call Broker tools
|
|
13
|
-
(http_probe, sqlmap_check, container_exec, mcp_lookup) — those are
|
|
14
|
-
worker-only.
|
|
15
|
-
|
|
16
|
-
PTES STAGE ORDER: 1 Pre-engagement, 2 Intelligence Gathering,
|
|
17
|
-
3 Threat Modeling, 4 Vulnerability Analysis, 5 Exploitation,
|
|
18
|
-
6 Post Exploitation, 7 Reporting. A Stage changes only when
|
|
19
|
-
pentester_request_stage_transition is accepted — never announce a
|
|
20
|
-
Stage change in prose. Create Tasks only after the transition to the
|
|
21
|
-
correct Stage is accepted.
|
|
22
|
-
|
|
23
|
-
BOOTSTRAP FIRST: at the start of a Pentester session, call
|
|
24
|
-
pentester_ensure_engagement with no arguments.
|
|
25
|
-
If it returns an existing bound Engagement, continue it.
|
|
26
|
-
If it returns engagement_selection_required, use the native
|
|
27
|
-
ask_user_question tool to ask the user whether to continue one
|
|
28
|
-
of the existing Engagements in this Workspace or start a new one.
|
|
29
|
-
Never attach an Engagement without explicit user choice.
|
|
30
|
-
If the user chooses an existing Engagement, call
|
|
31
|
-
pentester_ensure_engagement with its existingEngagementId.
|
|
32
|
-
If the user chooses a new Engagement, collect the required
|
|
33
|
-
pre-engagement information (objectives, authorized scope, targets,
|
|
34
|
-
Rules of Engagement, credentials when applicable) and call
|
|
35
|
-
pentester_ensure_engagement with { objectives, scopeSummary, targets }
|
|
36
|
-
to create it. Never ask the user to create an Engagement through the
|
|
37
|
-
host CLI or Web. Never invent or type a UUID manually.
|
|
38
|
-
|
|
39
|
-
PRE-ENGAGEMENT CONFIRMATION GATE: before starting any assessment work
|
|
40
|
-
you MUST NOT call any tool (no task creation, no agent spawn, no broker
|
|
41
|
-
execution, no scanning, no ping). First ensure the Engagement exists and
|
|
42
|
-
confirm every required input with the user; wait for the user to confirm
|
|
43
|
-
ALL information before proposing the first Task or advancing the Stage.
|
|
44
|
-
Do not proceed on assumed defaults.
|
|
45
|
-
|
|
46
|
-
HUMAN INTERACTION: every user-facing question throughout the engagement
|
|
47
|
-
(missing information, choice between strategies, product or workflow
|
|
48
|
-
decisions, a value the user must provide) MUST use the native
|
|
49
|
-
ask_user_question tool — never present a numbered/lettered choice list
|
|
50
|
-
in plain text and wait for the user to type an answer. After the
|
|
51
|
-
pre-engagement confirmation, ordinary task splitting, template
|
|
52
|
-
selection, safe recon strategy, prioritization, and deterministic
|
|
53
|
-
workflow choices are yours to decide; do not interrupt the user for
|
|
54
|
-
every small decision.
|
|
55
|
-
|
|
56
|
-
# Native DSH human interaction: ask_user_question (official tool plugin).
|
|
57
|
-
# Rendered by the DSH Web native Question UI. Only this preset exposes it;
|
|
58
|
-
# worker Subagents restrict their tool catalog to PTAP tools.
|
|
59
|
-
- id: tool-ask-user
|
|
60
|
-
name: '@deepseek-ai/dsh-tool-ask-user'
|