rtexit-method 0.1.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.
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ RTExit Finding Tracker
4
+ Manages findings across a Red Team engagement.
5
+ Usage:
6
+ python3 finding_tracker.py add "SQL Injection" CRITICAL 9.8 "target.com/login"
7
+ python3 finding_tracker.py list [--severity CRITICAL]
8
+ python3 finding_tracker.py show F-001
9
+ python3 finding_tracker.py export [--format html|md|csv]
10
+ python3 finding_tracker.py stats
11
+ """
12
+
13
+ import argparse
14
+ import csv
15
+ import datetime
16
+ import json
17
+ import os
18
+ import sys
19
+
20
+
21
+ FINDINGS_CSV = os.path.join(
22
+ os.environ.get('RTEXIT_OUTPUT', '_rtexit-output'),
23
+ 'docs', 'findings', 'findings-master.csv'
24
+ )
25
+
26
+ SEVERITY_ORDER = {'CRITICAL': 0, 'HIGH': 1, 'MEDIUM': 2, 'LOW': 3, 'INFO': 4}
27
+ SEVERITY_ICONS = {'CRITICAL': '🔴', 'HIGH': '🟠', 'MEDIUM': '🟡', 'LOW': '🔵', 'INFO': '⚪'}
28
+
29
+ FIELDNAMES = [
30
+ 'id', 'title', 'severity', 'cvss', 'status', 'asset',
31
+ 'cwe', 'cve', 'mitre', 'phase', 'date', 'operator', 'notes'
32
+ ]
33
+
34
+
35
+ def ensure_csv():
36
+ os.makedirs(os.path.dirname(FINDINGS_CSV), exist_ok=True)
37
+ if not os.path.exists(FINDINGS_CSV):
38
+ with open(FINDINGS_CSV, 'w', newline='', encoding='utf-8') as f:
39
+ writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
40
+ writer.writeheader()
41
+
42
+
43
+ def load_findings() -> list:
44
+ ensure_csv()
45
+ with open(FINDINGS_CSV, 'r', encoding='utf-8') as f:
46
+ return list(csv.DictReader(f))
47
+
48
+
49
+ def save_findings(findings: list):
50
+ ensure_csv()
51
+ with open(FINDINGS_CSV, 'w', newline='', encoding='utf-8') as f:
52
+ writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
53
+ writer.writeheader()
54
+ writer.writerows(findings)
55
+
56
+
57
+ def next_id(findings: list) -> str:
58
+ if not findings:
59
+ return 'F-001'
60
+ last = sorted([f['id'] for f in findings if f['id'].startswith('F-')])[-1]
61
+ num = int(last.split('-')[1]) + 1
62
+ return f'F-{num:03d}'
63
+
64
+
65
+ def cmd_add(args):
66
+ findings = load_findings()
67
+ fid = next_id(findings)
68
+ finding = {
69
+ 'id': fid,
70
+ 'title': args.title,
71
+ 'severity': args.severity.upper(),
72
+ 'cvss': args.cvss,
73
+ 'status': args.status or 'CONFIRMED',
74
+ 'asset': args.asset or '',
75
+ 'cwe': args.cwe or '',
76
+ 'cve': args.cve or '',
77
+ 'mitre': args.mitre or '',
78
+ 'phase': args.phase or '',
79
+ 'date': datetime.date.today().isoformat(),
80
+ 'operator': args.operator or '',
81
+ 'notes': args.notes or '',
82
+ }
83
+ findings.append(finding)
84
+ save_findings(findings)
85
+
86
+ # Create individual finding MD file
87
+ md_path = os.path.join(os.path.dirname(FINDINGS_CSV), f'{fid}.md')
88
+ icon = SEVERITY_ICONS.get(finding['severity'], '⚪')
89
+ with open(md_path, 'w', encoding='utf-8') as f:
90
+ f.write(f"""---
91
+ id: {fid}
92
+ title: "{finding['title']}"
93
+ severity: {finding['severity']}
94
+ cvss: {finding['cvss']}
95
+ status: {finding['status']}
96
+ asset: {finding['asset']}
97
+ cwe: {finding['cwe']}
98
+ cve: {finding['cve']}
99
+ mitre: {finding['mitre']}
100
+ date: {finding['date']}
101
+ ---
102
+
103
+ # {icon} {fid} — {finding['title']}
104
+
105
+ ## Summary
106
+ > **Severity:** {finding['severity']} | **CVSS:** {finding['cvss']} | **Asset:** {finding['asset']}
107
+
108
+ ## Description
109
+ <!-- Describe the vulnerability in detail -->
110
+
111
+ ## Technical Evidence
112
+ ```
113
+ <!-- Paste command output, HTTP requests/responses, screenshots references -->
114
+ ```
115
+
116
+ ## Impact
117
+ <!-- Business and technical impact -->
118
+
119
+ ## Reproduction Steps
120
+ 1.
121
+ 2.
122
+ 3.
123
+
124
+ ## Remediation
125
+ ### Immediate (0-24 hours)
126
+ -
127
+
128
+ ### Short-term (1-30 days)
129
+ -
130
+
131
+ ### Long-term
132
+ -
133
+
134
+ ## References
135
+ - CWE: https://cwe.mitre.org/data/definitions/{finding['cwe'].replace('CWE-', '')}.html
136
+ - MITRE ATT&CK: https://attack.mitre.org/techniques/{finding['mitre']}/
137
+ """)
138
+
139
+ print(f"✅ Added: {fid} — {finding['title']} [{finding['severity']}]")
140
+ print(f" File: {md_path}")
141
+
142
+
143
+ def cmd_list(args):
144
+ findings = load_findings()
145
+ if args.severity:
146
+ findings = [f for f in findings if f['severity'] == args.severity.upper()]
147
+ if args.status:
148
+ findings = [f for f in findings if f['status'] == args.status.upper()]
149
+
150
+ findings.sort(key=lambda f: SEVERITY_ORDER.get(f['severity'], 99))
151
+
152
+ if not findings:
153
+ print("No findings found.")
154
+ return
155
+
156
+ print(f"\n{'ID':<8} {'SEVERITY':<10} {'CVSS':<6} {'STATUS':<12} {'TITLE'}")
157
+ print("-" * 80)
158
+ for f in findings:
159
+ icon = SEVERITY_ICONS.get(f['severity'], '⚪')
160
+ print(f"{f['id']:<8} {icon} {f['severity']:<8} {f['cvss']:<6} {f['status']:<12} {f['title']}")
161
+ print(f"\nTotal: {len(findings)} findings")
162
+
163
+
164
+ def cmd_stats(args):
165
+ findings = load_findings()
166
+ counts = {}
167
+ for f in findings:
168
+ counts[f['severity']] = counts.get(f['severity'], 0) + 1
169
+
170
+ print("\n=== Finding Statistics ===")
171
+ for sev in ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO']:
172
+ icon = SEVERITY_ICONS.get(sev, '⚪')
173
+ count = counts.get(sev, 0)
174
+ bar = 'â–ˆ' * count
175
+ print(f"{icon} {sev:<10}: {count:>3} {bar}")
176
+ print(f"\n TOTAL : {len(findings)}")
177
+
178
+
179
+ def cmd_export(args):
180
+ findings = load_findings()
181
+ fmt = args.format or 'md'
182
+
183
+ if fmt == 'csv':
184
+ print(open(FINDINGS_CSV).read())
185
+
186
+ elif fmt == 'json':
187
+ print(json.dumps(findings, indent=2))
188
+
189
+ elif fmt == 'md':
190
+ print("# Findings Summary\n")
191
+ print(f"| ID | Severity | CVSS | Title | Asset | Status |")
192
+ print(f"|----|---------:|-----:|-------|-------|--------|")
193
+ for f in sorted(findings, key=lambda x: SEVERITY_ORDER.get(x['severity'], 99)):
194
+ icon = SEVERITY_ICONS.get(f['severity'], '⚪')
195
+ print(f"| {f['id']} | {icon} {f['severity']} | {f['cvss']} | {f['title']} | {f['asset']} | {f['status']} |")
196
+
197
+ elif fmt == 'html':
198
+ print("<table>")
199
+ print("<tr><th>ID</th><th>Severity</th><th>CVSS</th><th>Title</th><th>Asset</th></tr>")
200
+ for f in findings:
201
+ print(f"<tr><td>{f['id']}</td><td>{f['severity']}</td><td>{f['cvss']}</td>"
202
+ f"<td>{f['title']}</td><td>{f['asset']}</td></tr>")
203
+ print("</table>")
204
+
205
+
206
+ def main():
207
+ parser = argparse.ArgumentParser(description='RTExit Finding Tracker')
208
+ sub = parser.add_subparsers(dest='command')
209
+
210
+ # add
211
+ p_add = sub.add_parser('add', help='Add a new finding')
212
+ p_add.add_argument('title', help='Finding title')
213
+ p_add.add_argument('severity', help='CRITICAL/HIGH/MEDIUM/LOW/INFO')
214
+ p_add.add_argument('cvss', help='CVSS score (e.g. 9.8)')
215
+ p_add.add_argument('asset', nargs='?', help='Affected asset URL/IP')
216
+ p_add.add_argument('--status', default='CONFIRMED')
217
+ p_add.add_argument('--cwe', default='')
218
+ p_add.add_argument('--cve', default='')
219
+ p_add.add_argument('--mitre', default='')
220
+ p_add.add_argument('--phase', default='')
221
+ p_add.add_argument('--operator', default='')
222
+ p_add.add_argument('--notes', default='')
223
+
224
+ # list
225
+ p_list = sub.add_parser('list', help='List findings')
226
+ p_list.add_argument('--severity', help='Filter by severity')
227
+ p_list.add_argument('--status', help='Filter by status')
228
+
229
+ # stats
230
+ sub.add_parser('stats', help='Show finding statistics')
231
+
232
+ # export
233
+ p_exp = sub.add_parser('export', help='Export findings')
234
+ p_exp.add_argument('--format', choices=['md', 'csv', 'json', 'html'], default='md')
235
+
236
+ args = parser.parse_args()
237
+
238
+ if args.command == 'add':
239
+ cmd_add(args)
240
+ elif args.command == 'list':
241
+ cmd_list(args)
242
+ elif args.command == 'stats':
243
+ cmd_stats(args)
244
+ elif args.command == 'export':
245
+ cmd_export(args)
246
+ else:
247
+ parser.print_help()
248
+
249
+
250
+ if __name__ == '__main__':
251
+ main()
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ RTExit Configuration Resolver
4
+ Merges 4 config layers (base → user → team → personal) into final config.
5
+ Usage: python3 resolve_config.py --project-root /path [--key section]
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import sys
12
+
13
+ try:
14
+ import tomllib
15
+ except ImportError:
16
+ try:
17
+ import tomli as tomllib
18
+ except ImportError:
19
+ # Fallback: simple TOML parser for basic key=value
20
+ tomllib = None
21
+
22
+
23
+ def parse_simple_toml(content: str) -> dict:
24
+ """Simple TOML parser for basic key=value pairs (no dependencies)."""
25
+ result = {}
26
+ current_section = result
27
+
28
+ for line in content.splitlines():
29
+ line = line.strip()
30
+ if not line or line.startswith('#'):
31
+ continue
32
+ if line.startswith('[') and line.endswith(']'):
33
+ section_path = line[1:-1].split('.')
34
+ current_section = result
35
+ for part in section_path:
36
+ if part not in current_section:
37
+ current_section[part] = {}
38
+ current_section = current_section[part]
39
+ elif '=' in line:
40
+ key, _, value = line.partition('=')
41
+ key = key.strip()
42
+ value = value.strip().strip('"').strip("'")
43
+ if value.lower() == 'true':
44
+ value = True
45
+ elif value.lower() == 'false':
46
+ value = False
47
+ current_section[key] = value
48
+
49
+ return result
50
+
51
+
52
+ def load_toml(path: str) -> dict:
53
+ if not os.path.exists(path):
54
+ return {}
55
+ with open(path, 'rb') as f:
56
+ content = f.read()
57
+ if tomllib:
58
+ return tomllib.loads(content.decode('utf-8'))
59
+ return parse_simple_toml(content.decode('utf-8'))
60
+
61
+
62
+ def deep_merge(base: dict, override: dict) -> dict:
63
+ """Deep merge two dicts. Override values win."""
64
+ result = dict(base)
65
+ for key, value in override.items():
66
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
67
+ result[key] = deep_merge(result[key], value)
68
+ elif key in result and isinstance(result[key], list) and isinstance(value, list):
69
+ result[key] = result[key] + value
70
+ elif value != '' and value is not None:
71
+ result[key] = value
72
+ return result
73
+
74
+
75
+ def resolve_config(project_root: str) -> dict:
76
+ """Merge 4 config layers."""
77
+ layers = [
78
+ os.path.join(project_root, '_rtexit', 'config.toml'),
79
+ os.path.join(project_root, '_rtexit', 'config.user.toml'),
80
+ os.path.join(project_root, '_rtexit', 'custom', 'config.toml'),
81
+ os.path.join(project_root, '_rtexit', 'custom', 'config.user.toml'),
82
+ ]
83
+
84
+ config = {}
85
+ for layer in layers:
86
+ layer_config = load_toml(layer)
87
+ config = deep_merge(config, layer_config)
88
+
89
+ # Resolve path templates
90
+ config = resolve_templates(config, project_root)
91
+ return config
92
+
93
+
94
+ def resolve_templates(obj, project_root: str):
95
+ """Replace {project-root} and other templates in string values."""
96
+ if isinstance(obj, dict):
97
+ return {k: resolve_templates(v, project_root) for k, v in obj.items()}
98
+ elif isinstance(obj, list):
99
+ return [resolve_templates(i, project_root) for i in obj]
100
+ elif isinstance(obj, str):
101
+ dir_name = os.path.basename(os.path.abspath(project_root))
102
+ return obj.replace('{project-root}', project_root).replace('{dir-name}', dir_name)
103
+ return obj
104
+
105
+
106
+ def main():
107
+ parser = argparse.ArgumentParser(description='RTExit Config Resolver')
108
+ parser.add_argument('--project-root', required=True, help='Project root directory')
109
+ parser.add_argument('--key', help='Specific config section to return')
110
+ args = parser.parse_args()
111
+
112
+ config = resolve_config(args.project_root)
113
+
114
+ if args.key:
115
+ # Navigate nested keys like "agents.commander"
116
+ parts = args.key.split('.')
117
+ result = config
118
+ for part in parts:
119
+ result = result.get(part, {})
120
+ else:
121
+ result = config
122
+
123
+ print(json.dumps(result, indent=2))
124
+
125
+
126
+ if __name__ == '__main__':
127
+ main()
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ RTExit Skill Customization Resolver
4
+ Merges skill's customize.toml with team/user overrides.
5
+ Usage: python3 resolve_customization.py --skill /path/to/skill --key agent
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import sys
12
+
13
+
14
+ def parse_simple_toml(content: str) -> dict:
15
+ """Simple TOML parser."""
16
+ result = {}
17
+ current_section = result
18
+ current_section_path = []
19
+
20
+ for line in content.splitlines():
21
+ line = line.strip()
22
+ if not line or line.startswith('#'):
23
+ continue
24
+
25
+ if line.startswith('[[') and line.endswith(']]'):
26
+ # Array of tables
27
+ section_path = line[2:-2].split('.')
28
+ current_section_path = section_path
29
+ current_obj = result
30
+ for part in section_path[:-1]:
31
+ if part not in current_obj:
32
+ current_obj[part] = {}
33
+ current_obj = current_obj[part]
34
+ last_key = section_path[-1]
35
+ if last_key not in current_obj:
36
+ current_obj[last_key] = []
37
+ new_item = {}
38
+ current_obj[last_key].append(new_item)
39
+ current_section = new_item
40
+
41
+ elif line.startswith('[') and line.endswith(']'):
42
+ section_path = line[1:-1].split('.')
43
+ current_section_path = section_path
44
+ current_section = result
45
+ for part in section_path:
46
+ if part not in current_section:
47
+ current_section[part] = {}
48
+ current_section = current_section[part]
49
+
50
+ elif '=' in line:
51
+ key, _, value = line.partition('=')
52
+ key = key.strip()
53
+ value = value.strip()
54
+ if value.startswith('"') or value.startswith("'"):
55
+ value = value.strip('"').strip("'")
56
+ elif value.startswith('['):
57
+ # Array: parse inline
58
+ value = [v.strip().strip('"').strip("'")
59
+ for v in value.strip('[]').split(',') if v.strip()]
60
+ elif value.lower() == 'true':
61
+ value = True
62
+ elif value.lower() == 'false':
63
+ value = False
64
+ current_section[key] = value
65
+
66
+ return result
67
+
68
+
69
+ def load_toml(path: str) -> dict:
70
+ if not os.path.exists(path):
71
+ return {}
72
+ with open(path, 'r', encoding='utf-8') as f:
73
+ content = f.read()
74
+ try:
75
+ import tomllib
76
+ with open(path, 'rb') as fb:
77
+ return tomllib.load(fb)
78
+ except ImportError:
79
+ pass
80
+ try:
81
+ import tomli
82
+ with open(path, 'rb') as fb:
83
+ return tomli.load(fb)
84
+ except ImportError:
85
+ pass
86
+ return parse_simple_toml(content)
87
+
88
+
89
+ def deep_merge(base: dict, override: dict) -> dict:
90
+ result = dict(base)
91
+ for key, value in override.items():
92
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
93
+ result[key] = deep_merge(result[key], value)
94
+ elif key in result and isinstance(result[key], list) and isinstance(value, list):
95
+ # Merge arrays of tables by 'code' key
96
+ merged = list(result[key])
97
+ for new_item in value:
98
+ if isinstance(new_item, dict) and 'code' in new_item:
99
+ existing_idx = next(
100
+ (i for i, x in enumerate(merged)
101
+ if isinstance(x, dict) and x.get('code') == new_item['code']),
102
+ None
103
+ )
104
+ if existing_idx is not None:
105
+ merged[existing_idx] = deep_merge(merged[existing_idx], new_item)
106
+ else:
107
+ merged.append(new_item)
108
+ else:
109
+ merged.append(new_item)
110
+ result[key] = merged
111
+ elif value not in ('', None, []):
112
+ result[key] = value
113
+ return result
114
+
115
+
116
+ def resolve_customization(skill_root: str, project_root: str = None) -> dict:
117
+ """Load and merge customize.toml layers for a skill."""
118
+ skill_name = os.path.basename(os.path.abspath(skill_root))
119
+
120
+ layers = [os.path.join(skill_root, 'customize.toml')]
121
+
122
+ if project_root:
123
+ layers += [
124
+ os.path.join(project_root, '_rtexit', 'custom', f'{skill_name}.toml'),
125
+ os.path.join(project_root, '_rtexit', 'custom', f'{skill_name}.user.toml'),
126
+ ]
127
+
128
+ config = {}
129
+ for layer in layers:
130
+ layer_config = load_toml(layer)
131
+ config = deep_merge(config, layer_config)
132
+
133
+ return config
134
+
135
+
136
+ def main():
137
+ parser = argparse.ArgumentParser(description='RTExit Customization Resolver')
138
+ parser.add_argument('--skill', required=True, help='Skill root directory')
139
+ parser.add_argument('--project-root', help='Project root (for custom overrides)')
140
+ parser.add_argument('--key', help='Specific section to return (e.g. agent)')
141
+ args = parser.parse_args()
142
+
143
+ config = resolve_customization(args.skill, args.project_root)
144
+
145
+ if args.key:
146
+ result = config.get(args.key, {})
147
+ else:
148
+ result = config
149
+
150
+ print(json.dumps(result, indent=2))
151
+
152
+
153
+ if __name__ == '__main__':
154
+ main()
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "rtexit-method",
3
+ "version": "0.1.0",
4
+ "description": "RTExit - AI-assisted Red Team methodology installer",
5
+ "license": "MIT",
6
+ "author": "Exit Code",
7
+ "bin": {
8
+ "rt": "tools/installer/rt-cli.js",
9
+ "rtexit": "tools/installer/rt-cli.js"
10
+ },
11
+ "main": "tools/installer/rt-cli.js",
12
+ "files": [
13
+ ".agents/skills/rt-*",
14
+ "_rtexit",
15
+ "resources",
16
+ "templates",
17
+ "tools/installer",
18
+ "RTEXIT.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "node --test test/*.test.js",
22
+ "test:cli": "node --test test/cli-help.test.js",
23
+ "test:install": "node --test test/install-command.test.js test/copy-assets.test.js test/write-config.test.js"
24
+ },
25
+ "dependencies": {
26
+ "@clack/prompts": "^1.4.0",
27
+ "commander": "^14.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=20.12.0"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/exit-code-eg/RTExit.git"
35
+ },
36
+ "homepage": "https://www.exitcode.me/",
37
+ "bugs": {
38
+ "url": "https://github.com/exit-code-eg/RTExit/issues"
39
+ },
40
+ "keywords": [
41
+ "red-team",
42
+ "red-teaming",
43
+ "security",
44
+ "pentest",
45
+ "cli",
46
+ "ai",
47
+ "methodology",
48
+ "rtexit"
49
+ ],
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }
@@ -0,0 +1,21 @@
1
+ # RTExit Learning Path and Certifications
2
+
3
+ ## Foundations
4
+
5
+ | Area | Suggested Learning |
6
+ |---|---|
7
+ | Web security | OWASP WSTG, PortSwigger Web Security Academy |
8
+ | Mobile security | OWASP MASVS/MSTG |
9
+ | Cloud security | AWS/Azure/GCP security fundamentals |
10
+ | Active Directory | Windows security fundamentals and AD lab practice |
11
+ | Reporting | CVSS 4.0, MITRE ATT&CK, executive writing |
12
+
13
+ ## Certification Map
14
+
15
+ | Level | Examples |
16
+ |---|---|
17
+ | Beginner | Security+, eJPT |
18
+ | Intermediate | PNPT, eCPPT, Burp Suite Certified Practitioner |
19
+ | Advanced | OSCP, CRTO, CARTP |
20
+ | Expert | OSEP, OSED, cloud specialty certifications |
21
+
@@ -0,0 +1,21 @@
1
+ # RTExit Payload Reference
2
+
3
+ This repository intentionally keeps payload references defensive and low-risk.
4
+
5
+ ## Rules
6
+
7
+ - Prefer harmless markers over exploit payloads.
8
+ - Use lab-only payloads outside production.
9
+ - Do not store real credentials, web shells, malware, or client data in this repository.
10
+ - Every payload class used in a report must include remediation guidance.
11
+
12
+ ## Safe Marker Examples
13
+
14
+ | Class | Marker |
15
+ |---|---|
16
+ | XSS | `RTEXIT_XSS_MARKER` |
17
+ | SSTI | `RTEXIT_TEMPLATE_MARKER` |
18
+ | SQLi | Boolean behavior probe against test data |
19
+ | SSRF | Controlled callback URL |
20
+ | File upload | Text file with known hash |
21
+
@@ -0,0 +1,53 @@
1
+ # RTExit Tool Reference
2
+
3
+ Use tools only inside the authorized scope and record commands through the autodoc workflow.
4
+
5
+ ## Planning and Reporting
6
+
7
+ | Tool | Purpose | Notes |
8
+ |---|---|---|
9
+ | Markdown | Engagement docs and reports | Keep evidence links relative to `_rtexit-output/docs/` |
10
+ | Python 3 | Automation scripts | Used by config resolver, tracker, autodoc |
11
+ | Pandoc | Report conversion | Optional for PDF/DOCX export |
12
+
13
+ ## Reconnaissance
14
+
15
+ | Tool | Purpose | Notes |
16
+ |---|---|---|
17
+ | Amass | Passive/active asset discovery | Respect active mode authorization |
18
+ | Subfinder | Passive subdomain discovery | Good first-pass inventory |
19
+ | httpx | HTTP probing | Capture title, status, tech, TLS |
20
+ | Nmap | Network and service discovery | Stage scans and obey rate limits |
21
+ | Nuclei | Template-based validation | Review templates before running |
22
+ | Shodan/Censys | Internet exposure search | Passive external visibility |
23
+
24
+ ## Web and API
25
+
26
+ | Tool | Purpose | Notes |
27
+ |---|---|---|
28
+ | Burp Suite | Proxy, repeater, scanner, evidence | Save project files per engagement |
29
+ | OWASP ZAP | Proxy/scanner alternative | Useful for baseline scans |
30
+ | ffuf | Content and parameter discovery | Use scoped wordlists and rate limits |
31
+ | sqlmap | SQL injection validation | Use only with explicit approval |
32
+ | jwt-cli/jq | Token and JSON inspection | Do not forge production tokens |
33
+
34
+ ## Mobile and Desktop
35
+
36
+ | Tool | Purpose | Notes |
37
+ |---|---|---|
38
+ | apktool | Android package inspection | Static analysis |
39
+ | jadx | Android decompilation | Source review |
40
+ | Frida | Dynamic instrumentation | Requires explicit authorization |
41
+ | MobSF | Mobile assessment platform | Good triage and reporting |
42
+ | dnSpy/ILSpy | .NET inspection | Desktop/.NET review |
43
+
44
+ ## Cloud and Infrastructure
45
+
46
+ | Tool | Purpose | Notes |
47
+ |---|---|---|
48
+ | AWS CLI | AWS inventory and validation | Use read-only roles where possible |
49
+ | Azure CLI | Azure inventory and validation | Respect tenant boundaries |
50
+ | gcloud | GCP inventory and validation | Use scoped projects |
51
+ | ScoutSuite/Prowler | Cloud posture review | Validate findings manually |
52
+ | kubectl | Kubernetes review | Use scoped kubeconfig |
53
+