opencode-api-security-testing 4.0.0 → 5.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/SKILL.md +74 -1795
- package/agents/api-cyber-supervisor.md +9 -3
- package/agents/api-probing-miner.md +10 -2
- package/agents/api-resource-specialist.md +44 -35
- package/agents/api-vuln-verifier.md +56 -24
- package/package.json +48 -29
- package/postinstall.mjs +149 -0
- package/preuninstall.mjs +87 -0
- package/references/references/README.md +72 -0
- package/references/references/asset-discovery.md +119 -0
- package/references/references/fuzzing-patterns.md +129 -0
- package/references/references/graphql-guidance.md +108 -0
- package/references/references/intake.md +84 -0
- package/references/references/pua-agent.md +192 -0
- package/references/references/report-template.md +156 -0
- package/references/references/rest-guidance.md +76 -0
- package/references/references/severity-model.md +76 -0
- package/references/references/test-matrix.md +86 -0
- package/references/references/validation.md +78 -0
- package/references/references/vulnerabilities/01-sqli-tests.md +1128 -0
- package/references/references/vulnerabilities/02-user-enum-tests.md +423 -0
- package/references/references/vulnerabilities/03-jwt-tests.md +499 -0
- package/references/references/vulnerabilities/04-idor-tests.md +362 -0
- package/references/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
- package/references/references/vulnerabilities/06-biz-logic-tests.md +501 -0
- package/references/references/vulnerabilities/07-security-config-tests.md +511 -0
- package/references/references/vulnerabilities/08-brute-force-tests.md +457 -0
- package/references/references/vulnerabilities/09-vulnerability-chains.md +465 -0
- package/references/references/vulnerabilities/10-auth-tests.md +537 -0
- package/references/references/vulnerabilities/11-graphql-tests.md +355 -0
- package/references/references/vulnerabilities/12-ssrf-tests.md +396 -0
- package/references/references/vulnerabilities/README.md +148 -0
- package/references/references/workflows.md +192 -0
- package/src/index.ts +450 -91
- package/src/src/index.ts +535 -0
package/src/index.ts
CHANGED
|
@@ -1,175 +1,534 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
2
|
import { tool } from "@opencode-ai/plugin";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
const SKILL_DIR = "skills/api-security-testing";
|
|
7
|
+
const CORE_DIR = `${SKILL_DIR}/core`;
|
|
8
|
+
const AGENTS_DIR = ".config/opencode/agents";
|
|
9
|
+
const CONFIG_FILE = "api-security-testing.config.json";
|
|
10
|
+
|
|
11
|
+
// 默认配置
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
agents: {
|
|
14
|
+
"api-cyber-supervisor": { model: "anthropic/claude-sonnet-4-20250514", temperature: 0.3 },
|
|
15
|
+
"api-probing-miner": { model: "anthropic/claude-haiku-4-20250514", temperature: 0.5 },
|
|
16
|
+
"api-resource-specialist": { model: "anthropic/claude-haiku-4-20250514", temperature: 0.4 },
|
|
17
|
+
"api-vuln-verifier": { model: "anthropic/claude-haiku-4-20250514", temperature: 0.2 }
|
|
18
|
+
},
|
|
19
|
+
model_fallback: {
|
|
20
|
+
supervisor: [
|
|
21
|
+
"anthropic/claude-sonnet-4-20250514",
|
|
22
|
+
"openai/gpt-4o",
|
|
23
|
+
"google/gemini-2.0-flash",
|
|
24
|
+
"anthropic/claude-haiku-4-20250514"
|
|
25
|
+
],
|
|
26
|
+
subagent: [
|
|
27
|
+
"anthropic/claude-haiku-4-20250514",
|
|
28
|
+
"openai/gpt-4o-mini",
|
|
29
|
+
"google/gemini-2.0-flash-lite"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
cyber_supervisor: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
auto_trigger: true,
|
|
35
|
+
max_retries: 5,
|
|
36
|
+
give_up_patterns: [
|
|
37
|
+
"无法解决", "不能", "需要手动", "环境问题",
|
|
38
|
+
"超出范围", "建议用户", "I cannot", "I'm unable",
|
|
39
|
+
"out of scope", "manual"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
collection_mode: "auto" // auto | static | dynamic
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 赛博监工配置
|
|
46
|
+
const CYBER_SUPERVISOR = DEFAULT_CONFIG.cyber_supervisor;
|
|
47
|
+
|
|
48
|
+
// 模型回退状态追踪
|
|
49
|
+
const modelFailureCounts = new Map<string, Map<string, number>>();
|
|
50
|
+
const sessionFailures = new Map<string, number>();
|
|
51
|
+
|
|
52
|
+
function getConfigPath(ctx: { directory: string }): string {
|
|
53
|
+
return join(ctx.directory, SKILL_DIR, "assets", CONFIG_FILE);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadConfig(ctx: { directory: string }): typeof DEFAULT_CONFIG {
|
|
57
|
+
const configPath = getConfigPath(ctx);
|
|
58
|
+
if (existsSync(configPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const content = readFileSync(configPath, "utf-8");
|
|
61
|
+
const loaded = JSON.parse(content);
|
|
62
|
+
return { ...DEFAULT_CONFIG, ...loaded };
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.log(`[api-security-testing] Failed to load config: ${e}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return DEFAULT_CONFIG;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getModelFailureCount(sessionID: string, modelID: string): number {
|
|
71
|
+
const sessionMap = modelFailureCounts.get(sessionID);
|
|
72
|
+
if (!sessionMap) return 0;
|
|
73
|
+
return sessionMap.get(modelID) || 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function incrementModelFailure(sessionID: string, modelID: string): void {
|
|
77
|
+
if (!modelFailureCounts.has(sessionID)) {
|
|
78
|
+
modelFailureCounts.set(sessionID, new Map());
|
|
79
|
+
}
|
|
80
|
+
const sessionMap = modelFailureCounts.get(sessionID)!;
|
|
81
|
+
sessionMap.set(modelID, (sessionMap.get(modelID) || 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resetModelFailures(sessionID: string): void {
|
|
85
|
+
modelFailureCounts.delete(sessionID);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getNextFallbackModel(
|
|
89
|
+
config: typeof DEFAULT_CONFIG,
|
|
90
|
+
sessionID: string,
|
|
91
|
+
currentModel: string,
|
|
92
|
+
isSupervisor: boolean
|
|
93
|
+
): string | null {
|
|
94
|
+
const chain = isSupervisor
|
|
95
|
+
? config.model_fallback.supervisor
|
|
96
|
+
: config.model_fallback.subagent;
|
|
97
|
+
|
|
98
|
+
const currentIndex = chain.indexOf(currentModel);
|
|
99
|
+
if (currentIndex === -1 || currentIndex >= chain.length - 1) {
|
|
100
|
+
return null; // No fallback available
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 检查下一个模型是否也已失败过多
|
|
104
|
+
const nextModel = chain[currentIndex + 1];
|
|
105
|
+
const failures = getModelFailureCount(sessionID, nextModel);
|
|
106
|
+
if (failures >= 3) {
|
|
107
|
+
// 递归查找下一个可用模型
|
|
108
|
+
return getNextFallbackModel(config, sessionID, nextModel, isSupervisor);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return nextModel;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function detectModelError(output: string): { isModelError: boolean; modelID?: string } {
|
|
115
|
+
const errorPatterns = [
|
|
116
|
+
/model (.+?) (is overloaded|is not available|rate limit|timeout|failed)/i,
|
|
117
|
+
/(.+?) (rate limit exceeded|context length exceeded)/i,
|
|
118
|
+
/API error.*model[:\s]*(.+)/i,
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
for (const pattern of errorPatterns) {
|
|
122
|
+
const match = output.match(pattern);
|
|
123
|
+
if (match) {
|
|
124
|
+
return { isModelError: true, modelID: match[1]?.trim() };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { isModelError: false };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 压力升级提示词
|
|
132
|
+
const LEVEL_PROMPTS = [
|
|
133
|
+
"L0 信任 ▎冲刺开始。信任很简单——别让人失望。",
|
|
134
|
+
"L1 失望 ▎隔壁 agent 一次就搞定了。换完全不同的方法。",
|
|
135
|
+
"L2 灵魂拷问 ▎你的底层逻辑是什么?发力点在哪?搜索 + 读源码 + 3 个假设。",
|
|
136
|
+
"L3 绩效审查 ▎3.25。这是为了激励你。执行 7 项检查清单。",
|
|
137
|
+
"L4 毕业 ▎其他模型都能搞定。你快毕业了。绝望模式,穷举所有可能。"
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
function getSkillPath(ctx: { directory: string }): string {
|
|
141
|
+
return join(ctx.directory, SKILL_DIR);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getCorePath(ctx: { directory: string }): string {
|
|
145
|
+
return join(ctx.directory, CORE_DIR);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function checkDeps(ctx: { directory: string }): string {
|
|
149
|
+
const reqFile = join(getSkillPath(ctx), "requirements.txt");
|
|
150
|
+
if (existsSync(reqFile)) {
|
|
151
|
+
return `pip install -q -r "${reqFile}" 2>/dev/null; `;
|
|
152
|
+
}
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getAgentsDir(): string {
|
|
157
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/root";
|
|
158
|
+
return join(home, AGENTS_DIR);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getInjectedAgentsPrompt(): string {
|
|
162
|
+
const agentsDir = getAgentsDir();
|
|
163
|
+
const agentsPath = join(agentsDir, "api-cyber-supervisor.md");
|
|
164
|
+
|
|
165
|
+
if (!existsSync(agentsPath)) {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const content = readFileSync(agentsPath, "utf-8");
|
|
171
|
+
return `
|
|
172
|
+
|
|
173
|
+
[API Security Testing Agents Available]
|
|
174
|
+
When performing security testing tasks, you can use the following specialized agents:
|
|
175
|
+
|
|
176
|
+
${content}
|
|
177
|
+
|
|
178
|
+
To activate these agents, simply mention their name in your response (e.g., "@api-cyber-supervisor" to coordinate security testing).
|
|
179
|
+
`;
|
|
180
|
+
} catch {
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function execShell(ctx: unknown, cmd: string): Promise<string> {
|
|
186
|
+
const shell = ctx as { $: (strings: TemplateStringsArray, ...expr: unknown[]) => Promise<{ toString(): string }> };
|
|
187
|
+
const result = await shell.$`${cmd}`;
|
|
188
|
+
return result.toString();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getFailureCount(sessionID: string): number {
|
|
192
|
+
return sessionFailures.get(sessionID) || 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function incrementFailureCount(sessionID: string): void {
|
|
196
|
+
sessionFailures.set(sessionID, getFailureCount(sessionID) + 1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function resetFailureCount(sessionID: string): void {
|
|
200
|
+
sessionFailures.delete(sessionID);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function detectGiveUpPattern(text: string): boolean {
|
|
204
|
+
return CYBER_SUPERVISOR.give_up_patterns.some(p =>
|
|
205
|
+
text.toLowerCase().includes(p.toLowerCase())
|
|
206
|
+
);
|
|
6
207
|
}
|
|
7
208
|
|
|
8
209
|
const ApiSecurityTestingPlugin: Plugin = async (ctx) => {
|
|
210
|
+
const config = loadConfig(ctx);
|
|
211
|
+
console.log(`[api-security-testing] Plugin loaded v4.0.2 - collection_mode: ${config.collection_mode}`);
|
|
212
|
+
|
|
9
213
|
return {
|
|
10
214
|
tool: {
|
|
11
215
|
api_security_scan: tool({
|
|
12
|
-
description: "完整 API
|
|
216
|
+
description: "完整 API 安全扫描。参数: target(目标URL), scan_type(full/quick/targeted)",
|
|
13
217
|
args: {
|
|
14
218
|
target: tool.schema.string(),
|
|
15
219
|
scan_type: tool.schema.enum(["full", "quick", "targeted"]).optional(),
|
|
16
220
|
},
|
|
17
221
|
async execute(args, ctx) {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
222
|
+
const deps = checkDeps(ctx);
|
|
223
|
+
const corePath = getCorePath(ctx);
|
|
224
|
+
const cmd = `${deps}python3 -c "
|
|
20
225
|
import sys
|
|
21
226
|
sys.path.insert(0, '${corePath}')
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`;
|
|
29
|
-
const result = await ctx.$`python3 -c ${script}`;
|
|
30
|
-
return result.toString();
|
|
227
|
+
from deep_api_tester_v55 import DeepAPITesterV55
|
|
228
|
+
tester = DeepAPITesterV55(target='${args.target}', headless=True)
|
|
229
|
+
results = tester.run_test()
|
|
230
|
+
print(results)
|
|
231
|
+
"`;
|
|
232
|
+
return await execShell(ctx, cmd);
|
|
31
233
|
},
|
|
32
234
|
}),
|
|
33
235
|
|
|
34
236
|
api_fuzz_test: tool({
|
|
35
|
-
description: "API
|
|
237
|
+
description: "API 模糊测试。参数: endpoint(端点URL), method(HTTP方法)",
|
|
36
238
|
args: {
|
|
37
239
|
endpoint: tool.schema.string(),
|
|
38
240
|
method: tool.schema.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(),
|
|
39
241
|
},
|
|
40
242
|
async execute(args, ctx) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
243
|
+
const deps = checkDeps(ctx);
|
|
244
|
+
const corePath = getCorePath(ctx);
|
|
245
|
+
const cmd = `${deps}python3 -c "
|
|
43
246
|
import sys
|
|
44
247
|
sys.path.insert(0, '${corePath}')
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`;
|
|
52
|
-
const result = await ctx.$`python3 -c ${script}`;
|
|
53
|
-
return result.toString();
|
|
248
|
+
from api_fuzzer import APIFuzzer
|
|
249
|
+
fuzzer = APIFuzzer('${args.endpoint}')
|
|
250
|
+
results = fuzzer.fuzz(method='${args.method || 'GET'}')
|
|
251
|
+
print(results)
|
|
252
|
+
"`;
|
|
253
|
+
return await execShell(ctx, cmd);
|
|
54
254
|
},
|
|
55
255
|
}),
|
|
56
256
|
|
|
57
257
|
vuln_verify: tool({
|
|
58
|
-
description: "
|
|
258
|
+
description: "漏洞验证。参数: vuln_type(漏洞类型), endpoint(端点)",
|
|
59
259
|
args: {
|
|
60
260
|
vuln_type: tool.schema.string(),
|
|
61
261
|
endpoint: tool.schema.string(),
|
|
62
262
|
},
|
|
63
263
|
async execute(args, ctx) {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
264
|
+
const deps = checkDeps(ctx);
|
|
265
|
+
const corePath = getCorePath(ctx);
|
|
266
|
+
const cmd = `${deps}python3 -c "
|
|
66
267
|
import sys
|
|
67
268
|
sys.path.insert(0, '${corePath}')
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`;
|
|
75
|
-
const result = await ctx.$`python3 -c ${script}`;
|
|
76
|
-
return result.toString();
|
|
269
|
+
from verifiers.vuln_verifier import VulnVerifier
|
|
270
|
+
verifier = VulnVerifier()
|
|
271
|
+
result = verifier.verify('${args.vuln_type}', '${args.endpoint}')
|
|
272
|
+
print(result)
|
|
273
|
+
"`;
|
|
274
|
+
return await execShell(ctx, cmd);
|
|
77
275
|
},
|
|
78
276
|
}),
|
|
79
277
|
|
|
80
278
|
browser_collect: tool({
|
|
81
|
-
description: "
|
|
279
|
+
description: "浏览器采集动态内容。参数: url(目标URL), mode(auto/static/dynamic)",
|
|
82
280
|
args: {
|
|
83
281
|
url: tool.schema.string(),
|
|
282
|
+
mode: tool.schema.enum(["auto", "static", "dynamic"]).optional(),
|
|
84
283
|
},
|
|
85
284
|
async execute(args, ctx) {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
285
|
+
const deps = checkDeps(ctx);
|
|
286
|
+
const corePath = getCorePath(ctx);
|
|
287
|
+
const collectionMode = args.mode || config.collection_mode;
|
|
288
|
+
const cmd = `${deps}python3 -c "
|
|
88
289
|
import sys
|
|
89
290
|
sys.path.insert(0, '${corePath}')
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
291
|
+
from collectors.browser_collector import BrowserCollectorFacade
|
|
292
|
+
facade = BrowserCollectorFacade(headless=True)
|
|
293
|
+
result = facade.collect_all('${args.url}', {'mode': '${collectionMode}'})
|
|
294
|
+
import json
|
|
295
|
+
print(json.dumps(result, indent=2))
|
|
296
|
+
"`;
|
|
297
|
+
return await execShell(ctx, cmd);
|
|
298
|
+
},
|
|
299
|
+
}),
|
|
300
|
+
|
|
301
|
+
js_parse: tool({
|
|
302
|
+
description: "解析 JavaScript 文件。参数: file_path(文件路径)",
|
|
303
|
+
args: {
|
|
304
|
+
file_path: tool.schema.string(),
|
|
305
|
+
},
|
|
306
|
+
async execute(args, ctx) {
|
|
307
|
+
const deps = checkDeps(ctx);
|
|
308
|
+
const corePath = getCorePath(ctx);
|
|
309
|
+
const cmd = `${deps}python3 -c "
|
|
310
|
+
import sys
|
|
311
|
+
sys.path.insert(0, '${corePath}')
|
|
312
|
+
from collectors.js_parser import JSParser
|
|
313
|
+
parser = JSParser()
|
|
314
|
+
endpoints = parser.parse_file('${args.file_path}')
|
|
315
|
+
print(f'从 JS 发现 {len(endpoints)} 个端点')
|
|
316
|
+
"`;
|
|
317
|
+
return await execShell(ctx, cmd);
|
|
102
318
|
},
|
|
103
319
|
}),
|
|
104
320
|
|
|
105
321
|
graphql_test: tool({
|
|
106
|
-
description: "GraphQL
|
|
322
|
+
description: "GraphQL 安全测试。参数: endpoint(GraphQL端点)",
|
|
107
323
|
args: {
|
|
108
324
|
endpoint: tool.schema.string(),
|
|
109
325
|
},
|
|
110
326
|
async execute(args, ctx) {
|
|
111
|
-
const
|
|
112
|
-
const
|
|
327
|
+
const deps = checkDeps(ctx);
|
|
328
|
+
const corePath = getCorePath(ctx);
|
|
329
|
+
const cmd = `${deps}python3 -c "
|
|
113
330
|
import sys
|
|
114
331
|
sys.path.insert(0, '${corePath}')
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
332
|
+
from smart_analyzer import SmartAnalyzer
|
|
333
|
+
analyzer = SmartAnalyzer()
|
|
334
|
+
result = analyzer.graphql_test('${args.endpoint}')
|
|
335
|
+
print(result)
|
|
336
|
+
"`;
|
|
337
|
+
return await execShell(ctx, cmd);
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
|
|
341
|
+
cloud_storage_test: tool({
|
|
342
|
+
description: "云存储安全测试。参数: bucket_url(存储桶URL)",
|
|
343
|
+
args: {
|
|
344
|
+
bucket_url: tool.schema.string(),
|
|
345
|
+
},
|
|
346
|
+
async execute(args, ctx) {
|
|
347
|
+
const deps = checkDeps(ctx);
|
|
348
|
+
const corePath = getCorePath(ctx);
|
|
349
|
+
const cmd = `${deps}python3 -c "
|
|
350
|
+
import sys
|
|
351
|
+
sys.path.insert(0, '${corePath}')
|
|
352
|
+
from cloud_storage_tester import CloudStorageTester
|
|
353
|
+
tester = CloudStorageTester()
|
|
354
|
+
result = tester.full_test('${args.bucket_url}')
|
|
355
|
+
print(result)
|
|
356
|
+
"`;
|
|
357
|
+
return await execShell(ctx, cmd);
|
|
358
|
+
},
|
|
359
|
+
}),
|
|
360
|
+
|
|
361
|
+
idor_test: tool({
|
|
362
|
+
description: "IDOR 越权测试。参数: endpoint, resource_id",
|
|
363
|
+
args: {
|
|
364
|
+
endpoint: tool.schema.string(),
|
|
365
|
+
resource_id: tool.schema.string(),
|
|
366
|
+
},
|
|
367
|
+
async execute(args, ctx) {
|
|
368
|
+
const deps = checkDeps(ctx);
|
|
369
|
+
const corePath = getCorePath(ctx);
|
|
370
|
+
const cmd = `${deps}python3 -c "
|
|
371
|
+
import sys
|
|
372
|
+
sys.path.insert(0, '${corePath}')
|
|
373
|
+
from testers.idor_tester import IDORTester
|
|
374
|
+
tester = IDORTester()
|
|
375
|
+
result = tester.test('${args.endpoint}', '${args.resource_id}')
|
|
376
|
+
print(result)
|
|
377
|
+
"`;
|
|
378
|
+
return await execShell(ctx, cmd);
|
|
124
379
|
},
|
|
125
380
|
}),
|
|
126
381
|
|
|
127
382
|
sqli_test: tool({
|
|
128
|
-
description: "SQL
|
|
383
|
+
description: "SQL 注入测试。参数: endpoint, param",
|
|
129
384
|
args: {
|
|
130
385
|
endpoint: tool.schema.string(),
|
|
131
386
|
param: tool.schema.string(),
|
|
132
387
|
},
|
|
133
388
|
async execute(args, ctx) {
|
|
134
|
-
const
|
|
135
|
-
const
|
|
389
|
+
const deps = checkDeps(ctx);
|
|
390
|
+
const corePath = getCorePath(ctx);
|
|
391
|
+
const cmd = `${deps}python3 -c "
|
|
136
392
|
import sys
|
|
137
393
|
sys.path.insert(0, '${corePath}')
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`;
|
|
145
|
-
const result = await ctx.$`python3 -c ${script}`;
|
|
146
|
-
return result.toString();
|
|
394
|
+
from testers.sqli_tester import SQLiTester
|
|
395
|
+
tester = SQLiTester()
|
|
396
|
+
result = tester.test('${args.endpoint}', '${args.param}')
|
|
397
|
+
print(result)
|
|
398
|
+
"`;
|
|
399
|
+
return await execShell(ctx, cmd);
|
|
147
400
|
},
|
|
148
401
|
}),
|
|
149
402
|
|
|
150
|
-
|
|
151
|
-
description: "
|
|
403
|
+
auth_test: tool({
|
|
404
|
+
description: "认证安全测试。参数: endpoint",
|
|
152
405
|
args: {
|
|
153
406
|
endpoint: tool.schema.string(),
|
|
154
|
-
resource_id: tool.schema.string(),
|
|
155
407
|
},
|
|
156
408
|
async execute(args, ctx) {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
409
|
+
const deps = checkDeps(ctx);
|
|
410
|
+
const corePath = getCorePath(ctx);
|
|
411
|
+
const cmd = `${deps}python3 -c "
|
|
159
412
|
import sys
|
|
160
413
|
sys.path.insert(0, '${corePath}')
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
`;
|
|
168
|
-
const result = await ctx.$`python3 -c ${script}`;
|
|
169
|
-
return result.toString();
|
|
414
|
+
from testers.auth_tester import AuthTester
|
|
415
|
+
tester = AuthTester()
|
|
416
|
+
result = tester.test('${args.endpoint}')
|
|
417
|
+
print(result)
|
|
418
|
+
"`;
|
|
419
|
+
return await execShell(ctx, cmd);
|
|
170
420
|
},
|
|
171
421
|
}),
|
|
172
422
|
},
|
|
423
|
+
|
|
424
|
+
// 赛博监工 Hook - chat.message
|
|
425
|
+
"chat.message": async (input, output) => {
|
|
426
|
+
const sessionID = input.sessionID;
|
|
427
|
+
|
|
428
|
+
// 注入 agents prompt(首次)
|
|
429
|
+
const agentsPrompt = getInjectedAgentsPrompt();
|
|
430
|
+
if (agentsPrompt) {
|
|
431
|
+
const parts = output.parts as Array<{ type: string; text?: string }>;
|
|
432
|
+
const textPart = parts.find(p => p.type === "text");
|
|
433
|
+
if (textPart && textPart.text) {
|
|
434
|
+
textPart.text += agentsPrompt;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 赛博监工压力注入
|
|
439
|
+
if (config.cyber_supervisor.enabled && config.cyber_supervisor.auto_trigger) {
|
|
440
|
+
const failures = getFailureCount(sessionID);
|
|
441
|
+
if (failures > 0 && failures <= config.cyber_supervisor.max_retries) {
|
|
442
|
+
const level = Math.min(failures - 1, 4);
|
|
443
|
+
const supervisorPrompt = `
|
|
444
|
+
|
|
445
|
+
[赛博监工 P9 - 压力等级 L${level}]
|
|
446
|
+
当前失败次数: ${failures}/${config.cyber_supervisor.max_retries}
|
|
447
|
+
${LEVEL_PROMPTS[level]}
|
|
448
|
+
|
|
449
|
+
记住三条红线:
|
|
450
|
+
1. 闭环:说"完成"必须出示测试证据
|
|
451
|
+
2. 事实驱动:任何"可能是..."的论断必须先验证
|
|
452
|
+
3. 穷尽:10 个工具全部尝试过才能说"搞不定"
|
|
453
|
+
`;
|
|
454
|
+
const parts = output.parts as Array<{ type: string; text?: string }>;
|
|
455
|
+
const textPart = parts.find(p => p.type === "text");
|
|
456
|
+
if (textPart && textPart.text) {
|
|
457
|
+
textPart.text += supervisorPrompt;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// 赛博监工 Hook - tool.execute.after
|
|
464
|
+
"tool.execute.after": async (input, output) => {
|
|
465
|
+
const sessionID = input.sessionID;
|
|
466
|
+
const outputText = output.output || "";
|
|
467
|
+
|
|
468
|
+
// 检测模型错误并触发回退
|
|
469
|
+
const modelError = detectModelError(outputText);
|
|
470
|
+
if (modelError.isModelError && modelError.modelID) {
|
|
471
|
+
incrementModelFailure(sessionID, modelError.modelID);
|
|
472
|
+
|
|
473
|
+
// 获取下一个可用模型
|
|
474
|
+
const isSupervisor = input.toolName?.includes("supervisor") ||
|
|
475
|
+
input.toolName?.includes("scan");
|
|
476
|
+
const nextModel = getNextFallbackModel(config, sessionID, modelError.modelID, isSupervisor);
|
|
477
|
+
|
|
478
|
+
if (nextModel) {
|
|
479
|
+
return {
|
|
480
|
+
...output,
|
|
481
|
+
output: outputText + `\n\n[模型回退] ${modelError.modelID} 失败,已切换到 ${nextModel}`,
|
|
482
|
+
_fallbackModel: nextModel
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// 检测工具失败
|
|
488
|
+
if (output.error) {
|
|
489
|
+
incrementFailureCount(sessionID);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 检测放弃模式
|
|
493
|
+
if (detectGiveUpPattern(outputText)) {
|
|
494
|
+
incrementFailureCount(sessionID);
|
|
495
|
+
const failures = getFailureCount(sessionID);
|
|
496
|
+
const level = Math.min(failures, 4);
|
|
497
|
+
|
|
498
|
+
return {
|
|
499
|
+
...output,
|
|
500
|
+
output: outputText + `\n\n[赛博监工] 失败 ${failures} 次 - ${LEVEL_PROMPTS[level]}`
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 成功则重置计数器
|
|
505
|
+
if (!output.error && !detectGiveUpPattern(outputText)) {
|
|
506
|
+
resetFailureCount(sessionID);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return output;
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
// 会话清理
|
|
513
|
+
event: async (input) => {
|
|
514
|
+
const { event } = input;
|
|
515
|
+
|
|
516
|
+
if (event.type === "session.deleted" || event.type === "session.compacted") {
|
|
517
|
+
const props = event.properties as Record<string, unknown> | undefined;
|
|
518
|
+
let sessionID: string | undefined;
|
|
519
|
+
|
|
520
|
+
if (event.type === "session.deleted") {
|
|
521
|
+
sessionID = (props?.info as { id?: string })?.id;
|
|
522
|
+
} else {
|
|
523
|
+
sessionID = (props?.sessionID ?? (props?.info as { id?: string })?.id) as string | undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (sessionID) {
|
|
527
|
+
resetFailureCount(sessionID);
|
|
528
|
+
resetModelFailures(sessionID);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
},
|
|
173
532
|
};
|
|
174
533
|
};
|
|
175
534
|
|