opencode-api-security-testing 4.0.0 → 4.0.1

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/src/index.ts CHANGED
@@ -1,175 +1,406 @@
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
- function getCorePath(directory: string): string {
5
- return `${directory}/skills/api-security-testing/core`;
6
+ const SKILL_DIR = "skills/api-security-testing";
7
+ const CORE_DIR = `${SKILL_DIR}/core`;
8
+ const AGENTS_DIR = ".config/opencode/agents";
9
+
10
+ // 赛博监工配置
11
+ const CYBER_SUPERVISOR = {
12
+ enabled: true,
13
+ auto_trigger: true,
14
+ max_retries: 5,
15
+ give_up_patterns: [
16
+ "无法解决", "不能", "需要手动", "环境问题",
17
+ "超出范围", "建议用户", "I cannot", "I'm unable",
18
+ "out of scope", "manual"
19
+ ]
20
+ };
21
+
22
+ // 压力升级提示词
23
+ const LEVEL_PROMPTS = [
24
+ "L0 信任 ▎冲刺开始。信任很简单——别让人失望。",
25
+ "L1 失望 ▎隔壁 agent 一次就搞定了。换完全不同的方法。",
26
+ "L2 灵魂拷问 ▎你的底层逻辑是什么?发力点在哪?搜索 + 读源码 + 3 个假设。",
27
+ "L3 绩效审查 ▎3.25。这是为了激励你。执行 7 项检查清单。",
28
+ "L4 毕业 ▎其他模型都能搞定。你快毕业了。绝望模式,穷举所有可能。"
29
+ ];
30
+
31
+ function getSkillPath(ctx: { directory: string }): string {
32
+ return join(ctx.directory, SKILL_DIR);
33
+ }
34
+
35
+ function getCorePath(ctx: { directory: string }): string {
36
+ return join(ctx.directory, CORE_DIR);
37
+ }
38
+
39
+ function checkDeps(ctx: { directory: string }): string {
40
+ const reqFile = join(getSkillPath(ctx), "requirements.txt");
41
+ if (existsSync(reqFile)) {
42
+ return `pip install -q -r "${reqFile}" 2>/dev/null; `;
43
+ }
44
+ return "";
45
+ }
46
+
47
+ function getAgentsDir(): string {
48
+ const home = process.env.HOME || process.env.USERPROFILE || "/root";
49
+ return join(home, AGENTS_DIR);
50
+ }
51
+
52
+ function getInjectedAgentsPrompt(): string {
53
+ const agentsDir = getAgentsDir();
54
+ const agentsPath = join(agentsDir, "api-cyber-supervisor.md");
55
+
56
+ if (!existsSync(agentsPath)) {
57
+ return "";
58
+ }
59
+
60
+ try {
61
+ const content = readFileSync(agentsPath, "utf-8");
62
+ return `
63
+
64
+ [API Security Testing Agents Available]
65
+ When performing security testing tasks, you can use the following specialized agents:
66
+
67
+ ${content}
68
+
69
+ To activate these agents, simply mention their name in your response (e.g., "@api-cyber-supervisor" to coordinate security testing).
70
+ `;
71
+ } catch {
72
+ return "";
73
+ }
74
+ }
75
+
76
+ async function execShell(ctx: unknown, cmd: string): Promise<string> {
77
+ const shell = ctx as { $: (strings: TemplateStringsArray, ...expr: unknown[]) => Promise<{ toString(): string }> };
78
+ const result = await shell.$`${cmd}`;
79
+ return result.toString();
80
+ }
81
+
82
+ // 赛博监工状态追踪
83
+ const sessionFailures = new Map<string, number>();
84
+
85
+ function getFailureCount(sessionID: string): number {
86
+ return sessionFailures.get(sessionID) || 0;
87
+ }
88
+
89
+ function incrementFailureCount(sessionID: string): void {
90
+ sessionFailures.set(sessionID, getFailureCount(sessionID) + 1);
91
+ }
92
+
93
+ function resetFailureCount(sessionID: string): void {
94
+ sessionFailures.delete(sessionID);
95
+ }
96
+
97
+ function detectGiveUpPattern(text: string): boolean {
98
+ return CYBER_SUPERVISOR.give_up_patterns.some(p =>
99
+ text.toLowerCase().includes(p.toLowerCase())
100
+ );
6
101
  }
7
102
 
8
103
  const ApiSecurityTestingPlugin: Plugin = async (ctx) => {
104
+ console.log("[api-security-testing] Plugin loaded v4.0.0");
105
+
9
106
  return {
10
107
  tool: {
11
108
  api_security_scan: tool({
12
- description: "完整 API 安全扫描",
109
+ description: "完整 API 安全扫描。参数: target(目标URL), scan_type(full/quick/targeted)",
13
110
  args: {
14
111
  target: tool.schema.string(),
15
112
  scan_type: tool.schema.enum(["full", "quick", "targeted"]).optional(),
16
113
  },
17
114
  async execute(args, ctx) {
18
- const corePath = getCorePath(ctx.directory);
19
- const script = `
115
+ const deps = checkDeps(ctx);
116
+ const corePath = getCorePath(ctx);
117
+ const cmd = `${deps}python3 -c "
20
118
  import sys
21
119
  sys.path.insert(0, '${corePath}')
22
- try:
23
- from deep_api_tester_v55 import DeepAPITesterV55
24
- tester = DeepAPITesterV55(target='${args.target}', headless=True)
25
- print(tester.run_test())
26
- except Exception as e:
27
- print(f"Error: {e}")
28
- `;
29
- const result = await ctx.$`python3 -c ${script}`;
30
- return result.toString();
120
+ from deep_api_tester_v55 import DeepAPITesterV55
121
+ tester = DeepAPITesterV55(target='${args.target}', headless=True)
122
+ results = tester.run_test()
123
+ print(results)
124
+ "`;
125
+ return await execShell(ctx, cmd);
31
126
  },
32
127
  }),
33
128
 
34
129
  api_fuzz_test: tool({
35
- description: "API 模糊测试",
130
+ description: "API 模糊测试。参数: endpoint(端点URL), method(HTTP方法)",
36
131
  args: {
37
132
  endpoint: tool.schema.string(),
38
133
  method: tool.schema.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).optional(),
39
134
  },
40
135
  async execute(args, ctx) {
41
- const corePath = getCorePath(ctx.directory);
42
- const script = `
136
+ const deps = checkDeps(ctx);
137
+ const corePath = getCorePath(ctx);
138
+ const cmd = `${deps}python3 -c "
43
139
  import sys
44
140
  sys.path.insert(0, '${corePath}')
45
- try:
46
- from api_fuzzer import APIFuzzer
47
- fuzzer = APIFuzzer('${args.endpoint}')
48
- print(fuzzer.fuzz(method='${args.method || "GET"}'))
49
- except Exception as e:
50
- print(f"Error: {e}")
51
- `;
52
- const result = await ctx.$`python3 -c ${script}`;
53
- return result.toString();
141
+ from api_fuzzer import APIFuzzer
142
+ fuzzer = APIFuzzer('${args.endpoint}')
143
+ results = fuzzer.fuzz(method='${args.method || 'GET'}')
144
+ print(results)
145
+ "`;
146
+ return await execShell(ctx, cmd);
54
147
  },
55
148
  }),
56
149
 
57
150
  vuln_verify: tool({
58
- description: "漏洞验证",
151
+ description: "漏洞验证。参数: vuln_type(漏洞类型), endpoint(端点)",
59
152
  args: {
60
153
  vuln_type: tool.schema.string(),
61
154
  endpoint: tool.schema.string(),
62
155
  },
63
156
  async execute(args, ctx) {
64
- const corePath = getCorePath(ctx.directory);
65
- const script = `
157
+ const deps = checkDeps(ctx);
158
+ const corePath = getCorePath(ctx);
159
+ const cmd = `${deps}python3 -c "
66
160
  import sys
67
161
  sys.path.insert(0, '${corePath}')
68
- try:
69
- from verifiers.vuln_verifier import VulnVerifier
70
- verifier = VulnVerifier()
71
- print(verifier.verify('${args.vuln_type}', '${args.endpoint}'))
72
- except Exception as e:
73
- print(f"Error: {e}")
74
- `;
75
- const result = await ctx.$`python3 -c ${script}`;
76
- return result.toString();
162
+ from verifiers.vuln_verifier import VulnVerifier
163
+ verifier = VulnVerifier()
164
+ result = verifier.verify('${args.vuln_type}', '${args.endpoint}')
165
+ print(result)
166
+ "`;
167
+ return await execShell(ctx, cmd);
77
168
  },
78
169
  }),
79
170
 
80
171
  browser_collect: tool({
81
- description: "浏览器采集动态内容",
172
+ description: "浏览器采集动态内容。参数: url(目标URL)",
82
173
  args: {
83
174
  url: tool.schema.string(),
84
175
  },
85
176
  async execute(args, ctx) {
86
- const corePath = getCorePath(ctx.directory);
87
- const script = `
177
+ const deps = checkDeps(ctx);
178
+ const corePath = getCorePath(ctx);
179
+ const cmd = `${deps}python3 -c "
88
180
  import sys
89
181
  sys.path.insert(0, '${corePath}')
90
- try:
91
- from collectors.browser_collect import BrowserCollector
92
- collector = BrowserCollector(headless=True)
93
- endpoints = collector.collect('${args.url}')
94
- print(f'发现 {len(endpoints)} 个端点')
95
- for ep in endpoints:
96
- print(ep)
97
- except Exception as e:
98
- print(f"Error: {e}")
99
- `;
100
- const result = await ctx.$`python3 -c ${script}`;
101
- return result.toString();
182
+ from collectors.browser_collect import BrowserCollector
183
+ collector = BrowserCollector(headless=True)
184
+ endpoints = collector.collect('${args.url}')
185
+ print(f'发现 {len(endpoints)} 个端点:')
186
+ for ep in endpoints:
187
+ print(ep)
188
+ "`;
189
+ return await execShell(ctx, cmd);
190
+ },
191
+ }),
192
+
193
+ js_parse: tool({
194
+ description: "解析 JavaScript 文件。参数: file_path(文件路径)",
195
+ args: {
196
+ file_path: tool.schema.string(),
197
+ },
198
+ async execute(args, ctx) {
199
+ const deps = checkDeps(ctx);
200
+ const corePath = getCorePath(ctx);
201
+ const cmd = `${deps}python3 -c "
202
+ import sys
203
+ sys.path.insert(0, '${corePath}')
204
+ from collectors.js_parser import JSParser
205
+ parser = JSParser()
206
+ endpoints = parser.parse_file('${args.file_path}')
207
+ print(f'从 JS 发现 {len(endpoints)} 个端点')
208
+ "`;
209
+ return await execShell(ctx, cmd);
102
210
  },
103
211
  }),
104
212
 
105
213
  graphql_test: tool({
106
- description: "GraphQL 安全测试",
214
+ description: "GraphQL 安全测试。参数: endpoint(GraphQL端点)",
107
215
  args: {
108
216
  endpoint: tool.schema.string(),
109
217
  },
110
218
  async execute(args, ctx) {
111
- const corePath = getCorePath(ctx.directory);
112
- const script = `
219
+ const deps = checkDeps(ctx);
220
+ const corePath = getCorePath(ctx);
221
+ const cmd = `${deps}python3 -c "
113
222
  import sys
114
223
  sys.path.insert(0, '${corePath}')
115
- try:
116
- from smart_analyzer import SmartAnalyzer
117
- analyzer = SmartAnalyzer()
118
- print(analyzer.graphql_test('${args.endpoint}'))
119
- except Exception as e:
120
- print(f"Error: {e}")
121
- `;
122
- const result = await ctx.$`python3 -c ${script}`;
123
- return result.toString();
224
+ from smart_analyzer import SmartAnalyzer
225
+ analyzer = SmartAnalyzer()
226
+ result = analyzer.graphql_test('${args.endpoint}')
227
+ print(result)
228
+ "`;
229
+ return await execShell(ctx, cmd);
230
+ },
231
+ }),
232
+
233
+ cloud_storage_test: tool({
234
+ description: "云存储安全测试。参数: bucket_url(存储桶URL)",
235
+ args: {
236
+ bucket_url: tool.schema.string(),
237
+ },
238
+ async execute(args, ctx) {
239
+ const deps = checkDeps(ctx);
240
+ const corePath = getCorePath(ctx);
241
+ const cmd = `${deps}python3 -c "
242
+ import sys
243
+ sys.path.insert(0, '${corePath}')
244
+ from cloud_storage_tester import CloudStorageTester
245
+ tester = CloudStorageTester()
246
+ result = tester.full_test('${args.bucket_url}')
247
+ print(result)
248
+ "`;
249
+ return await execShell(ctx, cmd);
250
+ },
251
+ }),
252
+
253
+ idor_test: tool({
254
+ description: "IDOR 越权测试。参数: endpoint, resource_id",
255
+ args: {
256
+ endpoint: tool.schema.string(),
257
+ resource_id: tool.schema.string(),
258
+ },
259
+ async execute(args, ctx) {
260
+ const deps = checkDeps(ctx);
261
+ const corePath = getCorePath(ctx);
262
+ const cmd = `${deps}python3 -c "
263
+ import sys
264
+ sys.path.insert(0, '${corePath}')
265
+ from testers.idor_tester import IDORTester
266
+ tester = IDORTester()
267
+ result = tester.test('${args.endpoint}', '${args.resource_id}')
268
+ print(result)
269
+ "`;
270
+ return await execShell(ctx, cmd);
124
271
  },
125
272
  }),
126
273
 
127
274
  sqli_test: tool({
128
- description: "SQL 注入测试",
275
+ description: "SQL 注入测试。参数: endpoint, param",
129
276
  args: {
130
277
  endpoint: tool.schema.string(),
131
278
  param: tool.schema.string(),
132
279
  },
133
280
  async execute(args, ctx) {
134
- const corePath = getCorePath(ctx.directory);
135
- const script = `
281
+ const deps = checkDeps(ctx);
282
+ const corePath = getCorePath(ctx);
283
+ const cmd = `${deps}python3 -c "
136
284
  import sys
137
285
  sys.path.insert(0, '${corePath}')
138
- try:
139
- from testers.sqli_tester import SQLiTester
140
- tester = SQLiTester()
141
- print(tester.test('${args.endpoint}', '${args.param}'))
142
- except Exception as e:
143
- print(f"Error: {e}")
144
- `;
145
- const result = await ctx.$`python3 -c ${script}`;
146
- return result.toString();
286
+ from testers.sqli_tester import SQLiTester
287
+ tester = SQLiTester()
288
+ result = tester.test('${args.endpoint}', '${args.param}')
289
+ print(result)
290
+ "`;
291
+ return await execShell(ctx, cmd);
147
292
  },
148
293
  }),
149
294
 
150
- idor_test: tool({
151
- description: "IDOR 越权测试",
295
+ auth_test: tool({
296
+ description: "认证安全测试。参数: endpoint",
152
297
  args: {
153
298
  endpoint: tool.schema.string(),
154
- resource_id: tool.schema.string(),
155
299
  },
156
300
  async execute(args, ctx) {
157
- const corePath = getCorePath(ctx.directory);
158
- const script = `
301
+ const deps = checkDeps(ctx);
302
+ const corePath = getCorePath(ctx);
303
+ const cmd = `${deps}python3 -c "
159
304
  import sys
160
305
  sys.path.insert(0, '${corePath}')
161
- try:
162
- from testers.idor_tester import IDORTester
163
- tester = IDORTester()
164
- print(tester.test('${args.endpoint}', '${args.resource_id}'))
165
- except Exception as e:
166
- print(f"Error: {e}")
167
- `;
168
- const result = await ctx.$`python3 -c ${script}`;
169
- return result.toString();
306
+ from testers.auth_tester import AuthTester
307
+ tester = AuthTester()
308
+ result = tester.test('${args.endpoint}')
309
+ print(result)
310
+ "`;
311
+ return await execShell(ctx, cmd);
170
312
  },
171
313
  }),
172
314
  },
315
+
316
+ // 赛博监工 Hook - chat.message
317
+ "chat.message": async (input, output) => {
318
+ const sessionID = input.sessionID;
319
+
320
+ // 注入 agents prompt(首次)
321
+ const agentsPrompt = getInjectedAgentsPrompt();
322
+ if (agentsPrompt) {
323
+ const parts = output.parts as Array<{ type: string; text?: string }>;
324
+ const textPart = parts.find(p => p.type === "text");
325
+ if (textPart && textPart.text) {
326
+ textPart.text += agentsPrompt;
327
+ }
328
+ }
329
+
330
+ // 赛博监工压力注入
331
+ if (CYBER_SUPERVISOR.enabled && CYBER_SUPERVISOR.auto_trigger) {
332
+ const failures = getFailureCount(sessionID);
333
+ if (failures > 0 && failures <= CYBER_SUPERVISOR.max_retries) {
334
+ const level = Math.min(failures - 1, 4);
335
+ const supervisorPrompt = `
336
+
337
+ [赛博监工 P9 - 压力等级 L${level}]
338
+ 当前失败次数: ${failures}/${CYBER_SUPERVISOR.max_retries}
339
+ ${LEVEL_PROMPTS[level]}
340
+
341
+ 记住三条红线:
342
+ 1. 闭环:说"完成"必须出示测试证据
343
+ 2. 事实驱动:任何"可能是..."的论断必须先验证
344
+ 3. 穷尽:10 个工具全部尝试过才能说"搞不定"
345
+ `;
346
+ const parts = output.parts as Array<{ type: string; text?: string }>;
347
+ const textPart = parts.find(p => p.type === "text");
348
+ if (textPart && textPart.text) {
349
+ textPart.text += supervisorPrompt;
350
+ }
351
+ }
352
+ }
353
+ },
354
+
355
+ // 赛博监工 Hook - tool.execute.after
356
+ "tool.execute.after": async (input, output) => {
357
+ const sessionID = input.sessionID;
358
+
359
+ // 检测工具失败
360
+ if (output.error) {
361
+ incrementFailureCount(sessionID);
362
+ }
363
+
364
+ // 检测放弃模式
365
+ const outputText = output.output || "";
366
+ if (detectGiveUpPattern(outputText)) {
367
+ incrementFailureCount(sessionID);
368
+ const failures = getFailureCount(sessionID);
369
+ const level = Math.min(failures, 4);
370
+
371
+ return {
372
+ ...output,
373
+ output: outputText + `\n\n[赛博监工] 失败 ${failures} 次 - ${LEVEL_PROMPTS[level]}`
374
+ };
375
+ }
376
+
377
+ // 成功则重置计数器
378
+ if (!output.error && !detectGiveUpPattern(outputText)) {
379
+ resetFailureCount(sessionID);
380
+ }
381
+
382
+ return output;
383
+ },
384
+
385
+ // 会话清理
386
+ event: async (input) => {
387
+ const { event } = input;
388
+
389
+ if (event.type === "session.deleted" || event.type === "session.compacted") {
390
+ const props = event.properties as Record<string, unknown> | undefined;
391
+ let sessionID: string | undefined;
392
+
393
+ if (event.type === "session.deleted") {
394
+ sessionID = (props?.info as { id?: string })?.id;
395
+ } else {
396
+ sessionID = (props?.sessionID ?? (props?.info as { id?: string })?.id) as string | undefined;
397
+ }
398
+
399
+ if (sessionID) {
400
+ resetFailureCount(sessionID);
401
+ }
402
+ }
403
+ },
173
404
  };
174
405
  };
175
406