opencode-api-security-testing 2.1.0 → 2.1.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.
Files changed (61) hide show
  1. package/SKILL.md +1797 -0
  2. package/core/advanced_recon.py +788 -0
  3. package/core/agentic_analyzer.py +445 -0
  4. package/core/analyzers/api_parser.py +210 -0
  5. package/core/analyzers/response_analyzer.py +212 -0
  6. package/core/analyzers/sensitive_finder.py +184 -0
  7. package/core/api_fuzzer.py +422 -0
  8. package/core/api_interceptor.py +525 -0
  9. package/core/api_parser.py +955 -0
  10. package/core/browser_tester.py +479 -0
  11. package/core/cloud_storage_tester.py +1330 -0
  12. package/core/collectors/__init__.py +23 -0
  13. package/core/collectors/api_path_finder.py +300 -0
  14. package/core/collectors/browser_collect.py +645 -0
  15. package/core/collectors/browser_collector.py +411 -0
  16. package/core/collectors/http_client.py +111 -0
  17. package/core/collectors/js_collector.py +490 -0
  18. package/core/collectors/js_parser.py +780 -0
  19. package/core/collectors/url_collector.py +319 -0
  20. package/core/context_manager.py +682 -0
  21. package/core/deep_api_tester_v35.py +844 -0
  22. package/core/deep_api_tester_v55.py +366 -0
  23. package/core/dynamic_api_analyzer.py +532 -0
  24. package/core/http_client.py +179 -0
  25. package/core/models.py +296 -0
  26. package/core/orchestrator.py +890 -0
  27. package/core/prerequisite.py +227 -0
  28. package/core/reasoning_engine.py +1042 -0
  29. package/core/response_classifier.py +606 -0
  30. package/core/runner.py +938 -0
  31. package/core/scan_engine.py +599 -0
  32. package/core/skill_executor.py +435 -0
  33. package/core/skill_executor_v2.py +670 -0
  34. package/core/skill_executor_v3.py +704 -0
  35. package/core/smart_analyzer.py +687 -0
  36. package/core/strategy_pool.py +707 -0
  37. package/core/testers/auth_tester.py +264 -0
  38. package/core/testers/idor_tester.py +200 -0
  39. package/core/testers/sqli_tester.py +211 -0
  40. package/core/testing_loop.py +655 -0
  41. package/core/utils/base_path_dict.py +255 -0
  42. package/core/utils/payload_lib.py +167 -0
  43. package/core/utils/ssrf_detector.py +220 -0
  44. package/core/verifiers/vuln_verifier.py +536 -0
  45. package/package.json +17 -13
  46. package/references/asset-discovery.md +119 -612
  47. package/references/graphql-guidance.md +65 -641
  48. package/references/intake.md +84 -0
  49. package/references/report-template.md +131 -38
  50. package/references/rest-guidance.md +55 -526
  51. package/references/severity-model.md +52 -264
  52. package/references/test-matrix.md +65 -263
  53. package/references/validation.md +53 -400
  54. package/scripts/postinstall.js +46 -0
  55. package/agents/cyber-supervisor.md +0 -55
  56. package/agents/probing-miner.md +0 -42
  57. package/agents/resource-specialist.md +0 -31
  58. package/commands/api-security-testing-scan.md +0 -59
  59. package/commands/api-security-testing-test.md +0 -49
  60. package/commands/api-security-testing.md +0 -72
  61. package/tsconfig.json +0 -17
@@ -0,0 +1,23 @@
1
+ """
2
+ Collectors Package - 信息采集模块
3
+ """
4
+
5
+ from .js_collector import JSCollector, JSFingerprintCache, ParsedJSResult
6
+ from .api_path_finder import ApiPathFinder, ApiPathCombiner, APIFindResult
7
+ from .url_collector import URLCollector, DomainURLCollector, URLCollectionResult
8
+ from .browser_collector import HeadlessBrowserCollector, BrowserCollectorFacade, BrowserCollectionResult
9
+
10
+ __all__ = [
11
+ 'JSCollector',
12
+ 'JSFingerprintCache',
13
+ 'ParsedJSResult',
14
+ 'ApiPathFinder',
15
+ 'ApiPathCombiner',
16
+ 'APIFindResult',
17
+ 'URLCollector',
18
+ 'DomainURLCollector',
19
+ 'URLCollectionResult',
20
+ 'HeadlessBrowserCollector',
21
+ 'BrowserCollectorFacade',
22
+ 'BrowserCollectionResult',
23
+ ]
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ API Path Finder - API 路径发现器
4
+ 使用 25+ 正则规则从 JS/HTML/API 响应中提取 API 路径
5
+ """
6
+
7
+ import re
8
+ from typing import List, Dict, Set, Tuple
9
+ from urllib.parse import urljoin, urlparse
10
+ from dataclasses import dataclass, field
11
+
12
+
13
+ @dataclass
14
+ class APIFindResult:
15
+ """API 发现结果"""
16
+ path: str
17
+ method: str = "GET"
18
+ source_type: str = ""
19
+ url_type: str = ""
20
+ parameters: Set[str] = field(default_factory=set)
21
+ full_url: str = ""
22
+
23
+
24
+ class ApiPathFinder:
25
+ """
26
+ API 路径发现器
27
+
28
+ 功能:
29
+ - 25+ 正则规则发现 API 路径
30
+ - 智能路径组合
31
+ - 父路径探测
32
+ - 跨来源 Fuzzing
33
+ """
34
+
35
+ # 常见 RESTful 后缀
36
+ RESTFUL_SUFFIXES = [
37
+ 'list', 'get', 'add', 'create', 'update', 'edit', 'delete', 'remove',
38
+ 'detail', 'info', 'view', 'show', 'query', 'search', 'fetch', 'load',
39
+ 'save', 'submit', 'submit', 'export', 'import', 'upload', 'download',
40
+ 'config', 'setting', 'settings', 'options', 'permissions', 'all',
41
+ ]
42
+
43
+ # 常见参数名
44
+ COMMON_PARAMS = [
45
+ 'id', 'page', 'pageNum', 'pageSize', 'page_size', 'num', 'size',
46
+ 'limit', 'offset', 'start', 'end', 'from', 'to', 'date', 'time',
47
+ 'type', 'category', 'status', 'state', 'flag', 'mode', 'action',
48
+ 'name', 'title', 'desc', 'description', 'content', 'text', 'data',
49
+ 'token', 'key', 'secret', 'email', 'phone', 'mobile', 'username',
50
+ 'userId', 'user_id', 'userid', 'role', 'permission', 'menu',
51
+ 'search', 'query', 'keyword', 'filter', 'sort', 'order', 'by',
52
+ ]
53
+
54
+ # API 路径正则 (25+ 规则)
55
+ API_PATTERNS = [
56
+ # fetch
57
+ (r"fetch\s*\(\s*['\"]([^'\"]+)['\"]", "GET"),
58
+ (r"fetch\s*\(\s*[`'\"]([^`'\"]+)[`'\"]", "GET"),
59
+
60
+ # axios
61
+ (r"axios\.(get|post|put|delete|patch|head)\s*\(\s*['\"]([^'\"]+)['\"]", None),
62
+ (r"axios\.(get|post|put|delete|patch|head)\s*\(\s*[`'\"]([`'\"]+)[`'\"]", None),
63
+
64
+ # $.ajax
65
+ (r"\$\.ajax\s*\(\s*\{[^}]*url\s*:\s*['\"](/[^'\"]+)['\"]", "GET"),
66
+ (r"\$\.ajax\s*\(\s*\{[^}]*type\s*:\s*['\"]([^'\"]+)['\"]", None),
67
+
68
+ # request
69
+ (r"request\s*\(\s*\{[^}]*url\s*:\s*['\"](/[^'\"]+)['\"]", None),
70
+ (r"request\s*\(\s*['\"]([^'\"]+)['\"]", "GET"),
71
+
72
+ # 直接路径匹配
73
+ (r"['\"](/api/[a-zA-Z0-9_/-]+)['\"]", "GET"),
74
+ (r"['\"](\/v\d+/[a-zA-Z0-9_/-]+)['\"]", "GET"),
75
+ (r"['\"](\/[a-zA-Z]+/[a-zA-Z0-9_/-]+)['\"]", "GET"),
76
+
77
+ # RESTful 模板
78
+ (r"['\"](/[a-zA-Z]+/\{[a-zA-Z_][a-zA-Z0-9_]*\})['\"]", "GET"),
79
+ (r"['\"](/[a-zA-Z]+/[a-zA-Z]+/\{[a-zA-Z_][a-zA-Z0-9_]*\})['\"]", "GET"),
80
+
81
+ # WebSocket
82
+ (r"new\s+WebSocket\s*\(\s*['\"]([^'\"]+)['\"]", "WS"),
83
+ (r"wss?://[^\s'\"<>]+", "WS"),
84
+
85
+ # GraphQL
86
+ (r"graphql\s*\(\s*\{[^}]*query\s*:\s*['\"]([^'\"]+)['\"]", "POST"),
87
+ (r"gql\s*`[^`]+query\s+(\w+)", "POST"),
88
+
89
+ # 相对路径
90
+ (r"\.\s*\+\s*['\"](/[^'\"]+)['\"]", "GET"),
91
+ (r"baseURL\s*\+\s*['\"](/[^'\"]+)['\"]", "GET"),
92
+
93
+ # JSON 数据中的路径
94
+ (r"\"url\"\s*:\s*\"(/[^\"]+)\"", "GET"),
95
+ (r"\"path\"\s*:\s*\"(/[^\"]+)\"", "GET"),
96
+ (r"\"endpoint\"\s*:\s*\"(/[^\"]+)\"", "GET"),
97
+ (r"\"uri\"\s*:\s*\"(/[^\"]+)\"", "GET"),
98
+
99
+ # 完整 URL
100
+ (r"https?://[a-zA-Z0-9.-]+(:\d+)?(/[a-zA-Z0-9_/.-]*)?['\"]", "GET"),
101
+
102
+ # Vue Router
103
+ (r"path\s*:\s*['\"](/[^'\"]+)['\"]", "GET"),
104
+ (r"router\.push\s*\(\s*['\"](/[^'\"]+)['\"]", "GET"),
105
+ (r"router\.replace\s*\(\s*['\"](/[^'\"]+)['\"]", "GET"),
106
+
107
+ # React Router
108
+ (r"<Route\s+[^>]*path=['\"](/[^'\"]+)['\"]", "GET"),
109
+ (r"Link\s+[^>]*to=['\"](/[^'\"]+)['\"]", "GET"),
110
+
111
+ # 路径参数
112
+ (r":([a-zA-Z_][a-zA-Z0-9_]*)\s*[,})]", ""),
113
+ ]
114
+
115
+ def __init__(self):
116
+ self.found_paths: Set[str] = set()
117
+ self.found_apis: List[APIFindResult] = []
118
+
119
+ def find_api_paths_in_text(self, text: str, source: str = "text") -> List[APIFindResult]:
120
+ """从文本中发现 API 路径"""
121
+ results = []
122
+
123
+ for pattern, default_method in self.API_PATTERNS:
124
+ matches = re.findall(pattern, text, re.IGNORECASE)
125
+ for match in matches:
126
+ if isinstance(match, tuple):
127
+ if len(match) == 2:
128
+ method = match[0].upper() if match[0].lower() in ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] else default_method or "GET"
129
+ path = match[1]
130
+ else:
131
+ method = default_method or "GET"
132
+ path = match[0]
133
+ else:
134
+ method = default_method or "GET"
135
+ path = match
136
+
137
+ if self._is_valid_path(path):
138
+ full_path = self._normalize_path(path)
139
+ if full_path and full_path not in self.found_paths:
140
+ self.found_paths.add(full_path)
141
+
142
+ api_result = APIFindResult(
143
+ path=full_path,
144
+ method=method,
145
+ source_type=source,
146
+ url_type="discovered"
147
+ )
148
+ results.append(api_result)
149
+ self.found_apis.append(api_result)
150
+
151
+ return results
152
+
153
+ def _is_valid_path(self, path: str) -> bool:
154
+ """验证路径是否有效"""
155
+ if not path or len(path) < 2:
156
+ return False
157
+
158
+ if path.startswith('//') or path.startswith('http'):
159
+ return False
160
+
161
+ skip_patterns = [
162
+ r'\.css', r'\.jpg', r'\.png', r'\.gif', r'\.svg', r'\.ico',
163
+ r'\.woff', r'\.ttf', r'\.eot', r'\.map$', r'\.js$',
164
+ r'github\.com', r'cdn\.', r'googleapis\.com',
165
+ ]
166
+
167
+ for pattern in skip_patterns:
168
+ if re.search(pattern, path, re.IGNORECASE):
169
+ return False
170
+
171
+ return True
172
+
173
+ def _normalize_path(self, path: str) -> str:
174
+ """规范化路径"""
175
+ path = path.strip()
176
+
177
+ if not path.startswith('/') and not path.startswith('http'):
178
+ path = '/' + path
179
+
180
+ path = re.sub(r'["\']\s*\+\s*["\']', '', path)
181
+
182
+ path = re.sub(r'\{[^}]+\}', '{param}', path)
183
+
184
+ return path
185
+
186
+ def get_all_paths(self) -> List[str]:
187
+ """获取所有发现的路径"""
188
+ return list(self.found_paths)
189
+
190
+ def get_parent_paths(self) -> Set[str]:
191
+ """获取所有父路径"""
192
+ parent_paths = set()
193
+
194
+ for path in self.found_paths:
195
+ parts = path.strip('/').split('/')
196
+ if len(parts) > 1:
197
+ for i in range(1, len(parts)):
198
+ parent = '/' + '/'.join(parts[:i])
199
+ parent_paths.add(parent)
200
+
201
+ return parent_paths
202
+
203
+ def get_resource_names(self) -> Set[str]:
204
+ """获取所有资源名"""
205
+ resources = set()
206
+
207
+ for path in self.found_paths:
208
+ parts = path.strip('/').split('/')
209
+ for part in parts:
210
+ if part not in ['api', 'v1', 'v2', 'v3', 'rest']:
211
+ if not part.startswith('{') and not part.startswith(':'):
212
+ if len(part) > 1 and not part.isdigit():
213
+ resources.add(part)
214
+
215
+ return resources
216
+
217
+ def combine_paths(self, base_paths: Set[str], suffixes: List[str]) -> List[str]:
218
+ """组合路径: 父路径 + 后缀"""
219
+ combined = []
220
+
221
+ for base in base_paths:
222
+ for suffix in suffixes[:20]:
223
+ path = f"{base}/{suffix}"
224
+ if path not in self.found_paths:
225
+ combined.append(path)
226
+
227
+ return combined
228
+
229
+ def generate_fuzz_targets(self, parent_paths: Set[str], resources: Set[str]) -> List[str]:
230
+ """生成 Fuzz 目标"""
231
+ targets = []
232
+
233
+ for parent in parent_paths:
234
+ targets.append(parent)
235
+
236
+ for suffix in self.RESTFUL_SUFFIXES[:15]:
237
+ path = f"{parent}/{suffix}"
238
+ if path not in self.found_paths:
239
+ targets.append(path)
240
+
241
+ for resource in list(resources)[:10]:
242
+ path = f"{parent}/{resource}"
243
+ if path not in self.found_paths:
244
+ targets.append(path)
245
+
246
+ for suffix in self.RESTFUL_SUFFIXES[:10]:
247
+ path = f"{parent}/{resource}/{suffix}"
248
+ if path not in self.found_paths:
249
+ targets.append(path)
250
+
251
+ return targets[:200]
252
+
253
+
254
+ class ApiPathCombiner:
255
+ """API 路径组合器 - 跨来源智能路径组合"""
256
+
257
+ def __init__(self):
258
+ self.path_segments: Set[str] = set()
259
+ self.base_urls: Set[str] = set()
260
+
261
+ def add_path_segment(self, segment: str):
262
+ """添加路径片段"""
263
+ if segment and len(segment) > 1:
264
+ self.path_segments.add(segment)
265
+
266
+ def add_base_url(self, url: str):
267
+ """添加 Base URL"""
268
+ parsed = urlparse(url)
269
+ base = f"{parsed.scheme}://{parsed.netloc}"
270
+ self.base_urls.add(base)
271
+
272
+ path = parsed.path
273
+ if path:
274
+ parts = path.strip('/').split('/')
275
+ for part in parts:
276
+ if part:
277
+ self.path_segments.add(part)
278
+
279
+ def combine_cross_source(self, html_paths: List[str], js_paths: List[str], api_paths: List[str]) -> List[str]:
280
+ """跨来源组合路径"""
281
+ all_segments: Set[str] = set()
282
+
283
+ for path in html_paths + js_paths + api_paths:
284
+ parts = path.strip('/').split('/')
285
+ for part in parts:
286
+ if part and not part.startswith('{') and not part.isdigit():
287
+ all_segments.add(part)
288
+
289
+ combined = []
290
+
291
+ common_prefixes = ['/api', '/v1', '/v2', '/admin', '/user', '/auth', '/service']
292
+
293
+ for prefix in common_prefixes:
294
+ for segment in list(all_segments)[:30]:
295
+ if segment not in common_prefixes:
296
+ path = f"{prefix}/{segment}"
297
+ if path not in combined:
298
+ combined.append(path)
299
+
300
+ return combined[:100]