opencode-api-security-testing 2.0.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 (63) hide show
  1. package/README.md +30 -24
  2. package/SKILL.md +1797 -0
  3. package/core/advanced_recon.py +788 -0
  4. package/core/agentic_analyzer.py +445 -0
  5. package/core/analyzers/api_parser.py +210 -0
  6. package/core/analyzers/response_analyzer.py +212 -0
  7. package/core/analyzers/sensitive_finder.py +184 -0
  8. package/core/api_fuzzer.py +422 -0
  9. package/core/api_interceptor.py +525 -0
  10. package/core/api_parser.py +955 -0
  11. package/core/browser_tester.py +479 -0
  12. package/core/cloud_storage_tester.py +1330 -0
  13. package/core/collectors/__init__.py +23 -0
  14. package/core/collectors/api_path_finder.py +300 -0
  15. package/core/collectors/browser_collect.py +645 -0
  16. package/core/collectors/browser_collector.py +411 -0
  17. package/core/collectors/http_client.py +111 -0
  18. package/core/collectors/js_collector.py +490 -0
  19. package/core/collectors/js_parser.py +780 -0
  20. package/core/collectors/url_collector.py +319 -0
  21. package/core/context_manager.py +682 -0
  22. package/core/deep_api_tester_v35.py +844 -0
  23. package/core/deep_api_tester_v55.py +366 -0
  24. package/core/dynamic_api_analyzer.py +532 -0
  25. package/core/http_client.py +179 -0
  26. package/core/models.py +296 -0
  27. package/core/orchestrator.py +890 -0
  28. package/core/prerequisite.py +227 -0
  29. package/core/reasoning_engine.py +1042 -0
  30. package/core/response_classifier.py +606 -0
  31. package/core/runner.py +938 -0
  32. package/core/scan_engine.py +599 -0
  33. package/core/skill_executor.py +435 -0
  34. package/core/skill_executor_v2.py +670 -0
  35. package/core/skill_executor_v3.py +704 -0
  36. package/core/smart_analyzer.py +687 -0
  37. package/core/strategy_pool.py +707 -0
  38. package/core/testers/auth_tester.py +264 -0
  39. package/core/testers/idor_tester.py +200 -0
  40. package/core/testers/sqli_tester.py +211 -0
  41. package/core/testing_loop.py +655 -0
  42. package/core/utils/base_path_dict.py +255 -0
  43. package/core/utils/payload_lib.py +167 -0
  44. package/core/utils/ssrf_detector.py +220 -0
  45. package/core/verifiers/vuln_verifier.py +536 -0
  46. package/package.json +17 -13
  47. package/references/asset-discovery.md +119 -612
  48. package/references/graphql-guidance.md +65 -641
  49. package/references/intake.md +84 -0
  50. package/references/report-template.md +131 -38
  51. package/references/rest-guidance.md +55 -526
  52. package/references/severity-model.md +52 -264
  53. package/references/test-matrix.md +65 -263
  54. package/references/validation.md +53 -400
  55. package/scripts/postinstall.js +46 -0
  56. package/src/index.ts +259 -275
  57. package/agents/cyber-supervisor.md +0 -55
  58. package/agents/probing-miner.md +0 -42
  59. package/agents/resource-specialist.md +0 -31
  60. package/commands/api-security-testing-scan.md +0 -59
  61. package/commands/api-security-testing-test.md +0 -49
  62. package/commands/api-security-testing.md +0 -72
  63. package/tsconfig.json +0 -17
@@ -0,0 +1,212 @@
1
+ """
2
+ 响应类型分析 - 识别响应是真实API还是WAF/路由/错误页
3
+ 输入: {response, expected_type}
4
+ 输出: {type, is_suspicious, suspicious_reasons, sensitive_fields, is_valid_json, parsed_json}
5
+ """
6
+
7
+ import re
8
+ import json
9
+
10
+
11
+ def response_analyzer(config):
12
+ """
13
+ 分析HTTP响应类型和内容
14
+
15
+ 输入:
16
+ response: {
17
+ status: number,
18
+ headers: dict,
19
+ body: string,
20
+ content_type: string
21
+ }
22
+ expected_type?: "json" | "html" | "any"
23
+
24
+ 输出:
25
+ type: "json" | "html" | "empty" | "redirect"
26
+ is_suspicious: boolean
27
+ suspicious_reasons: string[]
28
+ sensitive_fields: string[]
29
+ is_valid_json: boolean
30
+ parsed_json?: object
31
+ """
32
+ response = config.get('response', {})
33
+ expected_type = config.get('expected_type', 'any')
34
+
35
+ status = response.get('status', 0)
36
+ body = response.get('body', '')
37
+ headers = response.get('headers', {})
38
+ content_type = response.get('content_type', '') or headers.get('Content-Type', '')
39
+
40
+ result = {
41
+ 'type': 'unknown',
42
+ 'is_suspicious': False,
43
+ 'suspicious_reasons': [],
44
+ 'sensitive_fields': [],
45
+ 'is_valid_json': False,
46
+ 'parsed_json': None
47
+ }
48
+
49
+ # 判断响应类型
50
+ if status in [301, 302, 303, 307, 308]:
51
+ result['type'] = 'redirect'
52
+ result['is_suspicious'] = True
53
+ result['suspicious_reasons'].append('redirect')
54
+ return result
55
+
56
+ body_lower = body.lower()
57
+ body_len = len(body)
58
+
59
+ # 检查是否是HTML
60
+ is_html = (
61
+ '<!doctype html>' in body_lower or
62
+ '<html' in body_lower or
63
+ 'text/html' in content_type.lower()
64
+ )
65
+
66
+ # 检查是否包含DOCTYPE
67
+ hasdoctype = '<!doctype' in body_lower
68
+
69
+ # 检查是否是JSON
70
+ is_json = False
71
+ parsed_json = None
72
+
73
+ if 'application/json' in content_type.lower():
74
+ try:
75
+ parsed_json = json.loads(body)
76
+ is_json = True
77
+ result['is_valid_json'] = True
78
+ result['parsed_json'] = parsed_json
79
+ except:
80
+ pass
81
+
82
+ # 也尝试直接解析body
83
+ if not is_json and body.strip().startswith('{'):
84
+ try:
85
+ parsed_json = json.loads(body)
86
+ is_json = True
87
+ result['is_valid_json'] = True
88
+ result['parsed_json'] = parsed_json
89
+ except:
90
+ pass
91
+
92
+ # 分类响应类型
93
+ if body_len < 50:
94
+ result['type'] = 'empty'
95
+ if status == 200:
96
+ result['is_suspicious'] = True
97
+ result['suspicious_reasons'].append('empty_response')
98
+ elif is_html:
99
+ result['type'] = 'html'
100
+ # HTML可能是WAF、SPA路由或错误页
101
+ if 'waf' in body_lower or '安全' in body_lower or '拦截' in body_lower:
102
+ result['suspicious_reasons'].append('waf_block')
103
+ if 'not found' in body_lower or '404' in body_lower:
104
+ result['suspicious_reasons'].append('not_found')
105
+ if hasdoctype and 'vue' in body_lower or 'react' in body_lower:
106
+ result['suspicious_reasons'].append('spa_route')
107
+ elif is_json:
108
+ result['type'] = 'json'
109
+ else:
110
+ result['type'] = 'other'
111
+
112
+ # 检查敏感字段
113
+ sensitive_patterns = {
114
+ 'password': r'password["\']?\s*[:=]\s*["\']([^"\']+)',
115
+ 'token': r'(?:token|Token|TOKEN)["\']?\s*[:=]\s*["\']([^"\']{10,})',
116
+ 'secret': r'secret["\']?\s*[:=]\s*["\']([^"\']+)',
117
+ 'api_key': r'api[_-]?key["\']?\s*[:=]\s*["\']([^"\']+)',
118
+ 'jwt': r'eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+',
119
+ }
120
+
121
+ if parsed_json:
122
+ body_str = json.dumps(parsed_json)
123
+ else:
124
+ body_str = body
125
+
126
+ for field_name, pattern in sensitive_patterns.items():
127
+ matches = re.findall(pattern, body_str, re.I)
128
+ if matches:
129
+ result['sensitive_fields'].append({
130
+ 'field': field_name,
131
+ 'count': len(matches),
132
+ 'preview': matches[0][:50] if matches else ''
133
+ })
134
+
135
+ # 判断是否可疑
136
+ if 'waf_block' in result['suspicious_reasons']:
137
+ result['is_suspicious'] = True
138
+ if 'spa_route' in result['suspicious_reasons']:
139
+ result['is_suspicious'] = True
140
+ if result['type'] == 'html' and expected_type == 'json':
141
+ result['is_suspicious'] = True
142
+
143
+ return result
144
+
145
+
146
+ def compare_responses(baseline, test):
147
+ """
148
+ 对比两个响应的差异
149
+
150
+ 输入:
151
+ baseline: response - 正常响应
152
+ test: response - 测试响应
153
+
154
+ 输出:
155
+ identical: boolean
156
+ differences: Array<{field, baseline_value, test_value, significance}>
157
+ """
158
+ differences = []
159
+
160
+ # 比较状态码
161
+ if baseline.get('status') != test.get('status'):
162
+ differences.append({
163
+ 'field': 'status',
164
+ 'baseline_value': baseline.get('status'),
165
+ 'test_value': test.get('status'),
166
+ 'significance': 'high'
167
+ })
168
+
169
+ # 比较响应长度
170
+ baseline_len = len(baseline.get('body', ''))
171
+ test_len = len(test.get('body', ''))
172
+
173
+ if baseline_len != test_len:
174
+ diff_ratio = abs(baseline_len - test_len) / max(baseline_len, test_len)
175
+ significance = 'high' if diff_ratio > 0.5 else 'low'
176
+ differences.append({
177
+ 'field': 'body_length',
178
+ 'baseline_value': baseline_len,
179
+ 'test_value': test_len,
180
+ 'significance': significance
181
+ })
182
+
183
+ # 比较响应类型
184
+ baseline_type = response_analyzer({'response': baseline}).get('type')
185
+ test_type = response_analyzer({'response': test}).get('type')
186
+
187
+ if baseline_type != test_type:
188
+ differences.append({
189
+ 'field': 'response_type',
190
+ 'baseline_value': baseline_type,
191
+ 'test_value': test_type,
192
+ 'significance': 'high'
193
+ })
194
+
195
+ identical = len(differences) == 0
196
+
197
+ return {
198
+ 'identical': identical,
199
+ 'differences': differences
200
+ }
201
+
202
+
203
+ if __name__ == '__main__':
204
+ # 测试
205
+ result = response_analyzer({
206
+ 'response': {
207
+ 'status': 200,
208
+ 'headers': {'Content-Type': 'application/json'},
209
+ 'body': '{"code": 200, "data": {"userId": 1}}'
210
+ }
211
+ })
212
+ print(f"Type: {result['type']}, Suspicious: {result['is_suspicious']}")
@@ -0,0 +1,184 @@
1
+ """
2
+ 敏感信息发现 - 从响应/源码中提取敏感信息
3
+ 输入: {content, check_fields}
4
+ 输出: {found: [{field, value, position}], severity}
5
+ """
6
+
7
+ import re
8
+ import json
9
+
10
+
11
+ def sensitive_finder(config):
12
+ """
13
+ 发现敏感信息
14
+
15
+ 输入:
16
+ content: string - 响应body或JS内容
17
+ check_fields?: string[] - 自定义敏感字段
18
+
19
+ 输出:
20
+ found: Array<{field, value, position}>
21
+ severity: "high" | "medium" | "low"
22
+ """
23
+ content = str(content)
24
+ check_fields = config.get('check_fields', [])
25
+
26
+ # 默认敏感字段
27
+ default_fields = [
28
+ 'password', 'passwd', 'pwd',
29
+ 'token', 'access_token', 'refresh_token',
30
+ 'secret', 'secret_key', 'app_secret',
31
+ 'api_key', 'apikey', 'api_secret',
32
+ 'private_key',
33
+ 'aws_access_key', 'aws_secret_key',
34
+ 'phone', 'mobile',
35
+ 'email',
36
+ 'id_card', 'idcard', '身份证',
37
+ 'balance', 'account', '余额'
38
+ ]
39
+
40
+ # 合并字段
41
+ all_fields = set(default_fields + check_fields)
42
+
43
+ found = []
44
+
45
+ # 通用敏感信息模式
46
+ patterns = {
47
+ 'password': [
48
+ r'password["\']?\s*[:=]\s*["\']([^"\']{1,100})["\']',
49
+ r'"pwd"\s*:\s*"([^"]+)"',
50
+ r'"passwd"\s*:\s*"([^"]+)"',
51
+ ],
52
+ 'token': [
53
+ r'(?:token|Token|TOKEN)["\']?\s*[:=]\s*["\']([^"\']{10,200})["\']',
54
+ r'Bearer\s+([a-zA-Z0-9\-_\.]+)',
55
+ ],
56
+ 'jwt': [
57
+ r'eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+',
58
+ ],
59
+ 'phone': [
60
+ r'1[3-9]\d{9}',
61
+ r'\d{3}-?\d{4}-?\d{4}',
62
+ ],
63
+ 'email': [
64
+ r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
65
+ ],
66
+ 'secret': [
67
+ r'secret["\']?\s*[:=]\s*["\']([^"\']{1,100})["\']',
68
+ r'appSecret["\']?\s*[:=]\s*["\']([^"\']{1,100})["\']',
69
+ ],
70
+ 'api_key': [
71
+ r'apiKey["\']?\s*[:=]\s*["\']([^"\']{1,100})["\']',
72
+ r'api[_-]?key["\']?\s*[:=]\s*["\']([^"\']{1,100})["\']',
73
+ ],
74
+ 'aws_key': [
75
+ r'AKIA[0-9A-Z]{16}',
76
+ ],
77
+ 'private_key': [
78
+ r'-----BEGIN[ A-Z]*PRIVATE KEY-----',
79
+ ]
80
+ }
81
+
82
+ # 搜索敏感信息
83
+ for field_name, field_patterns in patterns.items():
84
+ for pattern in field_patterns:
85
+ matches = re.finditer(pattern, content, re.I)
86
+ for match in matches:
87
+ value = match.group(0)
88
+ # 过滤掉太长的值
89
+ if len(value) > 200:
90
+ continue
91
+ # 过滤掉测试数据
92
+ if is_test_data(value):
93
+ continue
94
+ found.append({
95
+ 'field': field_name,
96
+ 'value': value[:100],
97
+ 'position': match.start()
98
+ })
99
+
100
+ # 搜索用户定义的字段
101
+ for field in all_fields:
102
+ if field.lower() in ['password', 'token', 'secret', 'api_key']:
103
+ continue # 已处理
104
+ pattern = rf'{field}["\']?\s*[:=]\s*["\']([^"\']{{1,100}})["\']'
105
+ matches = re.finditer(pattern, content, re.I)
106
+ for match in matches:
107
+ value = match.group(1)
108
+ if len(value) > 100:
109
+ continue
110
+ if is_test_data(value):
111
+ continue
112
+ found.append({
113
+ 'field': field,
114
+ 'value': value[:50],
115
+ 'position': match.start()
116
+ })
117
+
118
+ # 判断严重性
119
+ severity = 'low'
120
+ high_severity = ['password', 'token', 'jwt', 'secret', 'api_key', 'aws_key', 'private_key']
121
+ medium_severity = ['phone', 'email', 'id_card']
122
+
123
+ found_fields = [f['field'].lower() for f in found]
124
+ if any(f in found_fields for f in high_severity):
125
+ severity = 'high'
126
+ elif any(f in found_fields for f in medium_severity):
127
+ severity = 'medium'
128
+
129
+ return {
130
+ 'found': found,
131
+ 'severity': severity,
132
+ 'count': len(found)
133
+ }
134
+
135
+
136
+ def is_test_data(value):
137
+ """判断是否是测试数据"""
138
+ test_patterns = [
139
+ 'test', 'TEST', 'Test',
140
+ 'xxx', 'xxx.xxx',
141
+ 'null', 'undefined',
142
+ 'example', 'sample',
143
+ 'placeholder'
144
+ ]
145
+ value_lower = value.lower()
146
+ return any(t in value_lower for t in test_patterns)
147
+
148
+
149
+ def extract_secrets_from_json(data, path=''):
150
+ """从JSON中递归提取敏感信息"""
151
+ secrets = []
152
+
153
+ if isinstance(data, dict):
154
+ for key, value in data.items():
155
+ current_path = f"{path}.{key}" if path else key
156
+
157
+ sensitive_keys = ['password', 'token', 'secret', 'key', 'api', 'credential']
158
+ if any(s in key.lower() for s in sensitive_keys):
159
+ if isinstance(value, str) and len(value) > 0:
160
+ secrets.append({
161
+ 'path': current_path,
162
+ 'value': value[:50],
163
+ 'key': key
164
+ })
165
+
166
+ if isinstance(value, (dict, list)):
167
+ secrets.extend(extract_secrets_from_json(value, current_path))
168
+
169
+ elif isinstance(data, list):
170
+ for i, item in enumerate(data):
171
+ current_path = f"{path}[{i}]"
172
+ if isinstance(item, (dict, list)):
173
+ secrets.extend(extract_secrets_from_json(item, current_path))
174
+
175
+ return secrets
176
+
177
+
178
+ if __name__ == '__main__':
179
+ # 测试
180
+ result = sensitive_finder({
181
+ 'content': '{"token": "eyJhbGciOiJIUzI1NiJ9", "password": "admin123"}',
182
+ 'check_fields': ['custom_field']
183
+ })
184
+ print(f"Found: {result['count']}, Severity: {result['severity']}")