@kernel.chat/kbot 3.69.1 → 3.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1619 @@
1
+ // kbot Threat Intelligence Tools — Cyber threat intelligence for defenders
2
+ // Inspired by Iran cyber operations (coordinated DDoS, defacement, hack-and-leak)
3
+ // and Zen-AI-Pentest AI-guided tool orchestration.
4
+ //
5
+ // Tools:
6
+ // 1. threat_feed — Aggregate CVEs, exploits, and threat news from free sources
7
+ // 2. ioc_check — Check IPs, domains, hashes, URLs against threat intel databases
8
+ // 3. attack_surface_scan — Passive reconnaissance via DNS, headers, SSL
9
+ // 4. incident_response — AI-guided incident response playbook generation (Ollama)
10
+ // 5. threat_model — STRIDE threat modeling with AI analysis (Ollama)
11
+ //
12
+ // All tools use free/public APIs only. No paid keys required.
13
+ // Uses Node.js built-in modules + global fetch. No external deps.
14
+ import { registerTool } from './index.js';
15
+ import { Resolver } from 'node:dns/promises';
16
+ import { connect as tlsConnect } from 'node:tls';
17
+ import { homedir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { existsSync, readFileSync } from 'node:fs';
20
+ // ═══════════════════════════════════════════════════════════════════════════════
21
+ // Constants
22
+ // ═══════════════════════════════════════════════════════════════════════════════
23
+ const OLLAMA_URL = 'http://localhost:11434';
24
+ const OLLAMA_TIMEOUT = 90_000;
25
+ const OLLAMA_MODEL = 'kernel-coder:latest';
26
+ const FETCH_TIMEOUT = 15_000;
27
+ // Severity weights for scoring
28
+ const SEVERITY_WEIGHT = {
29
+ CRITICAL: 10, HIGH: 7, MEDIUM: 4, LOW: 1, NONE: 0,
30
+ };
31
+ // Security header expectations
32
+ const EXPECTED_HEADERS = {
33
+ 'strict-transport-security': { required: true, description: 'Enforces HTTPS connections (HSTS)' },
34
+ 'content-security-policy': { required: true, description: 'Prevents XSS and injection attacks' },
35
+ 'x-frame-options': { required: true, description: 'Prevents clickjacking attacks' },
36
+ 'x-content-type-options': { required: true, description: 'Prevents MIME-type sniffing' },
37
+ 'referrer-policy': { required: true, description: 'Controls referrer information leakage' },
38
+ 'permissions-policy': { required: false, description: 'Controls browser feature access' },
39
+ 'x-xss-protection': { required: false, description: 'Legacy XSS filter (deprecated but still checked)' },
40
+ 'cross-origin-opener-policy': { required: false, description: 'Isolates browsing context' },
41
+ 'cross-origin-embedder-policy': { required: false, description: 'Controls cross-origin embedding' },
42
+ 'cross-origin-resource-policy': { required: false, description: 'Controls cross-origin resource loading' },
43
+ };
44
+ // ═══════════════════════════════════════════════════════════════════════════════
45
+ // Helpers
46
+ // ═══════════════════════════════════════════════════════════════════════════════
47
+ /** Safe HTTP fetch with timeout */
48
+ async function safeFetch(url, options) {
49
+ try {
50
+ const timeout = options?.timeout ?? FETCH_TIMEOUT;
51
+ const controller = new AbortController();
52
+ const timer = setTimeout(() => controller.abort(), timeout);
53
+ const res = await fetch(url, { ...options, signal: controller.signal });
54
+ clearTimeout(timer);
55
+ return res;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ /** Safe JSON fetch */
62
+ async function fetchJson(url, options) {
63
+ const res = await safeFetch(url, options);
64
+ if (!res?.ok)
65
+ return null;
66
+ try {
67
+ return await res.json();
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** Safe text fetch */
74
+ async function fetchText(url, options) {
75
+ const res = await safeFetch(url, options);
76
+ if (!res?.ok)
77
+ return null;
78
+ try {
79
+ return await res.text();
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /** Ask Ollama for AI analysis — returns null if unavailable */
86
+ async function ollamaGenerate(prompt, model = OLLAMA_MODEL) {
87
+ try {
88
+ const controller = new AbortController();
89
+ const timer = setTimeout(() => controller.abort(), OLLAMA_TIMEOUT);
90
+ const res = await fetch(`${OLLAMA_URL}/api/generate`, {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({
94
+ model,
95
+ prompt,
96
+ stream: false,
97
+ options: { temperature: 0.3, num_predict: 2048 },
98
+ }),
99
+ signal: controller.signal,
100
+ });
101
+ clearTimeout(timer);
102
+ if (!res.ok)
103
+ return null;
104
+ const data = await res.json();
105
+ return data.response?.trim() || null;
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ }
111
+ /** Check if Ollama is running */
112
+ async function isOllamaAvailable() {
113
+ try {
114
+ const res = await fetch(`${OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) });
115
+ return res.ok;
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ /** Get dream journal insights for tech stack matching */
122
+ function getDreamInsights() {
123
+ try {
124
+ const dreamPath = join(homedir(), '.kbot', 'dreams', 'journal.json');
125
+ if (!existsSync(dreamPath))
126
+ return [];
127
+ const data = JSON.parse(readFileSync(dreamPath, 'utf-8'));
128
+ if (Array.isArray(data.insights)) {
129
+ return data.insights
130
+ .filter((i) => i.type === 'tech_stack' || i.type === 'dependency')
131
+ .map((i) => String(i.content || '').toLowerCase());
132
+ }
133
+ return [];
134
+ }
135
+ catch {
136
+ return [];
137
+ }
138
+ }
139
+ /** Parse CVSS score to severity */
140
+ function cvssToSeverity(score) {
141
+ if (score >= 9.0)
142
+ return 'CRITICAL';
143
+ if (score >= 7.0)
144
+ return 'HIGH';
145
+ if (score >= 4.0)
146
+ return 'MEDIUM';
147
+ if (score > 0.0)
148
+ return 'LOW';
149
+ return 'NONE';
150
+ }
151
+ /** Format date as ISO date string */
152
+ function isoDate(d) {
153
+ return d.toISOString().split('T')[0];
154
+ }
155
+ /** DNS resolve with timeout wrapper */
156
+ async function dnsResolve(resolver, hostname, type) {
157
+ try {
158
+ const result = await Promise.race([
159
+ resolver.resolve(hostname, type),
160
+ new Promise((_, reject) => setTimeout(() => reject(new Error('DNS timeout')), 8000)),
161
+ ]);
162
+ if (Array.isArray(result)) {
163
+ return result.map(r => typeof r === 'string' ? r : JSON.stringify(r));
164
+ }
165
+ return [];
166
+ }
167
+ catch {
168
+ return [];
169
+ }
170
+ }
171
+ /** Get TLS certificate info for a hostname */
172
+ function getTlsCertInfo(hostname, port = 443) {
173
+ return new Promise((resolve) => {
174
+ const timer = setTimeout(() => resolve(null), 10_000);
175
+ try {
176
+ const socket = tlsConnect({ host: hostname, port, servername: hostname, rejectUnauthorized: false }, () => {
177
+ clearTimeout(timer);
178
+ const cert = socket.getPeerCertificate();
179
+ socket.destroy();
180
+ if (!cert || !cert.subject) {
181
+ resolve(null);
182
+ return;
183
+ }
184
+ resolve({
185
+ subject: cert.subject,
186
+ issuer: cert.issuer,
187
+ valid_from: cert.valid_from,
188
+ valid_to: cert.valid_to,
189
+ serial: cert.serialNumber,
190
+ fingerprint: cert.fingerprint256 || cert.fingerprint,
191
+ bits: cert.bits,
192
+ subjectaltname: cert.subjectaltname,
193
+ });
194
+ });
195
+ socket.on('error', () => { clearTimeout(timer); resolve(null); });
196
+ }
197
+ catch {
198
+ clearTimeout(timer);
199
+ resolve(null);
200
+ }
201
+ });
202
+ }
203
+ async function fetchRecentCves(days = 7) {
204
+ const end = new Date();
205
+ const start = new Date(end.getTime() - days * 24 * 60 * 60 * 1000);
206
+ const url = `https://services.nvd.nist.gov/rest/json/cves/2.0?` +
207
+ `pubStartDate=${start.toISOString()}&pubEndDate=${end.toISOString()}` +
208
+ `&cvssV3Severity=HIGH&resultsPerPage=20`;
209
+ const data = await fetchJson(url, { timeout: 20_000 });
210
+ if (!data?.vulnerabilities)
211
+ return [];
212
+ return data.vulnerabilities.map(v => {
213
+ const cve = v.cve;
214
+ const desc = cve.descriptions.find(d => d.lang === 'en')?.value || 'No description';
215
+ const cvss31 = cve.metrics?.cvssMetricV31?.[0]?.cvssData;
216
+ const cvss2 = cve.metrics?.cvssMetricV2?.[0]?.cvssData;
217
+ const score = cvss31?.baseScore ?? cvss2?.baseScore ?? 0;
218
+ const severity = cvss31?.baseSeverity ?? cvssToSeverity(score);
219
+ // Extract affected software from CPE matches
220
+ const affected = [];
221
+ for (const config of cve.configurations || []) {
222
+ for (const node of config.nodes) {
223
+ for (const match of node.cpeMatch) {
224
+ if (match.vulnerable) {
225
+ // CPE format: cpe:2.3:a:vendor:product:version:...
226
+ const parts = match.criteria.split(':');
227
+ if (parts.length >= 5) {
228
+ affected.push(`${parts[3]}/${parts[4]}`);
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ return {
235
+ id: cve.id,
236
+ description: desc.length > 200 ? desc.slice(0, 200) + '...' : desc,
237
+ score,
238
+ severity: severity.toUpperCase(),
239
+ published: cve.published.split('T')[0],
240
+ affected: Array.from(new Set(affected)).slice(0, 5),
241
+ };
242
+ }).sort((a, b) => b.score - a.score);
243
+ }
244
+ async function fetchExploitDbRecent() {
245
+ // Exploit-DB RSS via their Atom/RSS feed or GitLab mirror
246
+ const url = 'https://gitlab.com/exploit-database/exploitdb/-/raw/main/files_exploits.csv';
247
+ const text = await fetchText(url, { timeout: 20_000 });
248
+ if (!text)
249
+ return [];
250
+ // Parse CSV — last N entries (most recent at bottom)
251
+ const lines = text.trim().split('\n');
252
+ const recent = [];
253
+ const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
254
+ // Process last 200 lines for recency
255
+ for (const line of lines.slice(-200)) {
256
+ const cols = line.split(',');
257
+ if (cols.length < 5)
258
+ continue;
259
+ const dateStr = cols[3]?.replace(/"/g, '').trim();
260
+ const date = new Date(dateStr);
261
+ if (isNaN(date.getTime()) || date < cutoff)
262
+ continue;
263
+ recent.push({
264
+ title: cols[2]?.replace(/"/g, '').trim() || 'Unknown',
265
+ date: isoDate(date),
266
+ type: cols[5]?.replace(/"/g, '').trim() || 'Unknown',
267
+ platform: cols[6]?.replace(/"/g, '').trim() || 'Unknown',
268
+ });
269
+ }
270
+ return recent.slice(-10).reverse();
271
+ }
272
+ /** Classify IOC type */
273
+ function classifyIoc(value) {
274
+ // URL
275
+ if (/^https?:\/\//.test(value))
276
+ return 'url';
277
+ // IPv4
278
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(value))
279
+ return 'ip';
280
+ // IPv6
281
+ if (/^[0-9a-fA-F:]+$/.test(value) && value.includes(':') && value.length > 7)
282
+ return 'ip';
283
+ // SHA-256
284
+ if (/^[0-9a-fA-F]{64}$/.test(value))
285
+ return 'hash_sha256';
286
+ // SHA-1
287
+ if (/^[0-9a-fA-F]{40}$/.test(value))
288
+ return 'hash_sha1';
289
+ // MD5
290
+ if (/^[0-9a-fA-F]{32}$/.test(value))
291
+ return 'hash_md5';
292
+ // Domain
293
+ if (/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/.test(value))
294
+ return 'domain';
295
+ return 'unknown';
296
+ }
297
+ /** Check IP against AbuseIPDB (free API — rate limited, no key version) */
298
+ async function checkAbuseIpdb(ip) {
299
+ // AbuseIPDB requires an API key, but we can use their public check page
300
+ // For truly free checking, we use URLhaus + VirusTotal public
301
+ // We'll attempt AbuseIPDB if the user has configured a key
302
+ const configPath = join(homedir(), '.kbot', 'config.json');
303
+ let apiKey;
304
+ try {
305
+ if (existsSync(configPath)) {
306
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
307
+ apiKey = config.abuseipdb_key || config.ABUSEIPDB_KEY;
308
+ }
309
+ }
310
+ catch { /* no config */ }
311
+ if (apiKey) {
312
+ const data = await fetchJson(`https://api.abuseipdb.com/api/v2/check?ipAddress=${encodeURIComponent(ip)}&maxAgeInDays=90`, { headers: { Key: apiKey, Accept: 'application/json' }, timeout: 10_000 });
313
+ if (data?.data) {
314
+ return {
315
+ source: 'AbuseIPDB',
316
+ data: {
317
+ ip: data.data.ipAddress,
318
+ abuse_score: data.data.abuseConfidenceScore,
319
+ country: data.data.countryCode,
320
+ isp: data.data.isp,
321
+ reports: data.data.totalReports,
322
+ last_reported: data.data.lastReportedAt,
323
+ usage: data.data.usageType,
324
+ },
325
+ };
326
+ }
327
+ }
328
+ return null;
329
+ }
330
+ /** Check domain/IP against URLhaus (free, no API key) */
331
+ async function checkUrlhaus(value, type) {
332
+ let endpoint;
333
+ let body;
334
+ if (type === 'url') {
335
+ endpoint = 'https://urlhaus-api.abuse.ch/v1/url/';
336
+ body = `url=${encodeURIComponent(value)}`;
337
+ }
338
+ else if (type === 'ip' || type === 'domain') {
339
+ endpoint = 'https://urlhaus-api.abuse.ch/v1/host/';
340
+ body = `host=${encodeURIComponent(value)}`;
341
+ }
342
+ else {
343
+ // Hash lookup
344
+ endpoint = 'https://urlhaus-api.abuse.ch/v1/payload/';
345
+ const hashType = type === 'hash_md5' ? 'md5_hash' : 'sha256_hash';
346
+ body = `${hashType}=${encodeURIComponent(value)}`;
347
+ }
348
+ const res = await safeFetch(endpoint, {
349
+ method: 'POST',
350
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
351
+ body,
352
+ timeout: 10_000,
353
+ });
354
+ if (!res?.ok)
355
+ return null;
356
+ try {
357
+ const data = await res.json();
358
+ if (data.query_status === 'no_results')
359
+ return null;
360
+ const result = { status: data.query_status };
361
+ if (type === 'url') {
362
+ result.threat = data.threat || 'unknown';
363
+ result.url_status = data.url_status;
364
+ result.tags = data.tags || [];
365
+ result.blacklists = data.blacklists;
366
+ result.reference = data.urlhaus_reference;
367
+ }
368
+ else if (type === 'ip' || type === 'domain') {
369
+ result.url_count = data.url_count;
370
+ result.urls_online = data.urls_online;
371
+ result.blacklists = data.blacklists;
372
+ result.reference = data.urlhaus_reference;
373
+ if (data.urls && data.urls.length > 0) {
374
+ result.recent_threats = data.urls.slice(0, 5).map(u => ({
375
+ url: u.url,
376
+ threat: u.threat,
377
+ status: u.url_status,
378
+ tags: u.tags,
379
+ date: u.date_added,
380
+ }));
381
+ }
382
+ }
383
+ else {
384
+ // Payload/hash
385
+ result.file_type = data.file_type;
386
+ result.file_size = data.file_size;
387
+ result.signature = data.signature;
388
+ result.first_seen = data.firstseen;
389
+ result.last_seen = data.lastseen;
390
+ result.url_count = data.url_count;
391
+ if (data.urls && data.urls.length > 0) {
392
+ result.distribution_urls = data.urls.slice(0, 5).map(u => ({
393
+ url: u.url,
394
+ status: u.url_status,
395
+ filename: u.filename,
396
+ }));
397
+ }
398
+ }
399
+ return { source: 'URLhaus (abuse.ch)', data: result };
400
+ }
401
+ catch {
402
+ return null;
403
+ }
404
+ }
405
+ /** Check hash against VirusTotal (free public API — requires key, optional) */
406
+ async function checkVirusTotal(value, type) {
407
+ const configPath = join(homedir(), '.kbot', 'config.json');
408
+ let apiKey;
409
+ try {
410
+ if (existsSync(configPath)) {
411
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
412
+ apiKey = config.virustotal_key || config.VIRUSTOTAL_KEY || config.vt_key || config.VT_KEY;
413
+ }
414
+ }
415
+ catch { /* no config */ }
416
+ if (!apiKey)
417
+ return null;
418
+ let endpoint;
419
+ if (type.startsWith('hash_')) {
420
+ endpoint = `https://www.virustotal.com/api/v3/files/${value}`;
421
+ }
422
+ else if (type === 'domain') {
423
+ endpoint = `https://www.virustotal.com/api/v3/domains/${value}`;
424
+ }
425
+ else if (type === 'ip') {
426
+ endpoint = `https://www.virustotal.com/api/v3/ip_addresses/${value}`;
427
+ }
428
+ else {
429
+ // URL — needs base64 encoding
430
+ const urlId = Buffer.from(value).toString('base64url');
431
+ endpoint = `https://www.virustotal.com/api/v3/urls/${urlId}`;
432
+ }
433
+ const data = await fetchJson(endpoint, {
434
+ headers: { 'x-apikey': apiKey },
435
+ timeout: 10_000,
436
+ });
437
+ if (!data?.data?.attributes)
438
+ return null;
439
+ const attrs = data.data.attributes;
440
+ const result = {};
441
+ if (type.startsWith('hash_')) {
442
+ const stats = attrs.last_analysis_stats;
443
+ result.detections = stats?.malicious ?? 0;
444
+ result.total_engines = Object.values(stats || {}).reduce((a, b) => a + b, 0);
445
+ result.detection_rate = stats ? `${stats.malicious || 0}/${result.total_engines}` : 'unknown';
446
+ result.file_type = attrs.type_description;
447
+ result.file_size = attrs.size;
448
+ result.names = attrs.names?.slice(0, 5);
449
+ result.reputation = attrs.reputation;
450
+ result.tags = attrs.tags?.slice(0, 10);
451
+ }
452
+ else if (type === 'domain' || type === 'ip') {
453
+ const stats = attrs.last_analysis_stats;
454
+ result.malicious = stats?.malicious ?? 0;
455
+ result.suspicious = stats?.suspicious ?? 0;
456
+ result.harmless = stats?.harmless ?? 0;
457
+ result.reputation = attrs.reputation;
458
+ result.as_owner = attrs.as_owner;
459
+ result.country = attrs.country;
460
+ result.tags = attrs.tags?.slice(0, 10);
461
+ }
462
+ else {
463
+ const stats = attrs.last_analysis_stats;
464
+ result.malicious = stats?.malicious ?? 0;
465
+ result.total_engines = Object.values(stats || {}).reduce((a, b) => a + b, 0);
466
+ result.threat_names = attrs.threat_names?.slice(0, 5);
467
+ result.tags = attrs.tags?.slice(0, 10);
468
+ }
469
+ return { source: 'VirusTotal', data: result };
470
+ }
471
+ /** Check hash/domain against ThreatFox (free, no key) */
472
+ async function checkThreatFox(value, type) {
473
+ let searchType;
474
+ if (type === 'hash_md5')
475
+ searchType = 'hash';
476
+ else if (type === 'hash_sha256')
477
+ searchType = 'hash';
478
+ else if (type === 'domain')
479
+ searchType = 'domain';
480
+ else if (type === 'ip')
481
+ searchType = 'ip:port';
482
+ else
483
+ return null;
484
+ const body = JSON.stringify({
485
+ query: 'search_ioc',
486
+ search_term: type === 'ip' ? `${value}:` : value,
487
+ });
488
+ const res = await safeFetch('https://threatfox-api.abuse.ch/api/v1/', {
489
+ method: 'POST',
490
+ headers: { 'Content-Type': 'application/json' },
491
+ body,
492
+ timeout: 10_000,
493
+ });
494
+ if (!res?.ok)
495
+ return null;
496
+ try {
497
+ const data = await res.json();
498
+ if (data.query_status !== 'ok' || !data.data?.length)
499
+ return null;
500
+ return {
501
+ source: 'ThreatFox (abuse.ch)',
502
+ data: {
503
+ matches: data.data.slice(0, 5).map(entry => ({
504
+ ioc: entry.ioc,
505
+ threat_type: entry.threat_type,
506
+ malware: entry.malware,
507
+ confidence: entry.confidence_level,
508
+ first_seen: entry.first_seen,
509
+ last_seen: entry.last_seen,
510
+ tags: entry.tags,
511
+ })),
512
+ },
513
+ };
514
+ }
515
+ catch {
516
+ return null;
517
+ }
518
+ }
519
+ async function enumerateDns(hostname) {
520
+ const resolver = new Resolver();
521
+ resolver.setServers(['1.1.1.1', '8.8.8.8']);
522
+ const [A, AAAA, MX, NS, TXT, CNAME, SOA] = await Promise.all([
523
+ dnsResolve(resolver, hostname, 'A'),
524
+ dnsResolve(resolver, hostname, 'AAAA'),
525
+ dnsResolve(resolver, hostname, 'MX'),
526
+ dnsResolve(resolver, hostname, 'NS'),
527
+ dnsResolve(resolver, hostname, 'TXT'),
528
+ dnsResolve(resolver, hostname, 'CNAME'),
529
+ dnsResolve(resolver, hostname, 'SOA'),
530
+ ]);
531
+ return { A, AAAA, MX, NS, TXT, CNAME, SOA };
532
+ }
533
+ async function checkHttpHeaders(url) {
534
+ const res = await safeFetch(url, {
535
+ method: 'HEAD',
536
+ timeout: 10_000,
537
+ redirect: 'follow',
538
+ });
539
+ if (!res)
540
+ return { headers: {}, missing: [], present: [], score: 0, server: null, poweredBy: null };
541
+ const headers = {};
542
+ res.headers.forEach((value, key) => { headers[key.toLowerCase()] = value; });
543
+ const missing = [];
544
+ const present = [];
545
+ for (const [header, config] of Object.entries(EXPECTED_HEADERS)) {
546
+ if (headers[header]) {
547
+ present.push(header);
548
+ }
549
+ else if (config.required) {
550
+ missing.push(header);
551
+ }
552
+ }
553
+ // Score: each required header present = 20 points (5 required = 100 max)
554
+ const requiredCount = Object.entries(EXPECTED_HEADERS).filter(([, c]) => c.required).length;
555
+ const presentRequired = present.filter(h => EXPECTED_HEADERS[h]?.required).length;
556
+ const score = Math.round((presentRequired / requiredCount) * 100);
557
+ return {
558
+ headers,
559
+ missing,
560
+ present,
561
+ score,
562
+ server: headers['server'] || null,
563
+ poweredBy: headers['x-powered-by'] || null,
564
+ };
565
+ }
566
+ // ═══════════════════════════════════════════════════════════════════════════════
567
+ // Tool 4 & 5: Incident Response & Threat Model templates
568
+ // ═══════════════════════════════════════════════════════════════════════════════
569
+ const INCIDENT_TYPES = {
570
+ ransomware: {
571
+ containment: [
572
+ 'Isolate infected systems from the network immediately',
573
+ 'Disable affected user accounts',
574
+ 'Block C2 domains and IPs at firewall/proxy',
575
+ 'Preserve forensic evidence before cleanup (memory dumps, disk images)',
576
+ 'Identify the ransomware variant (check ID Ransomware, No More Ransom)',
577
+ 'Notify legal, executive leadership, and insurance carrier',
578
+ ],
579
+ eradication: [
580
+ 'Identify initial infection vector (phishing email, RDP, vulnerability)',
581
+ 'Remove ransomware binaries and persistence mechanisms',
582
+ 'Patch the vulnerability or close the access path that was exploited',
583
+ 'Scan all endpoints with updated AV/EDR signatures',
584
+ 'Check for lateral movement — scan for other compromised systems',
585
+ 'Reset all credentials that may have been exposed',
586
+ ],
587
+ recovery: [
588
+ 'Restore from clean, verified backups (test backup integrity first)',
589
+ 'Rebuild systems from known-good images if backups are suspect',
590
+ 'Gradually reconnect systems to network with monitoring',
591
+ 'Verify data integrity post-restoration',
592
+ 'Monitor for re-infection for 72+ hours',
593
+ 'Update firewall rules and endpoint protections',
594
+ ],
595
+ indicators: [
596
+ 'File encryption with unusual extensions',
597
+ 'Ransom notes on desktop or in directories',
598
+ 'Mass file modification timestamps',
599
+ 'Unusual process execution (PowerShell, wmic, certutil)',
600
+ 'Network connections to Tor or known C2 infrastructure',
601
+ ],
602
+ },
603
+ data_breach: {
604
+ containment: [
605
+ 'Identify scope: what data was accessed, how much, whose data',
606
+ 'Revoke compromised credentials and API keys immediately',
607
+ 'Block attacker IP addresses and suspicious sessions',
608
+ 'Enable enhanced logging on all affected systems',
609
+ 'Preserve logs and evidence (do not rotate or delete)',
610
+ 'Engage legal counsel — assess notification requirements (GDPR 72h, CCPA, HIPAA)',
611
+ ],
612
+ eradication: [
613
+ 'Close the access path (patch vulnerability, fix misconfiguration)',
614
+ 'Remove any backdoors or persistent access mechanisms',
615
+ 'Rotate all secrets, tokens, and certificates on affected systems',
616
+ 'Audit access control lists and permissions',
617
+ 'Review and harden authentication mechanisms',
618
+ 'Scan for exfiltration tools or staged data',
619
+ ],
620
+ recovery: [
621
+ 'Implement additional monitoring on affected data stores',
622
+ 'Notify affected individuals per legal requirements',
623
+ 'Offer credit monitoring if PII was exposed',
624
+ 'File regulatory notifications within required timeframes',
625
+ 'Conduct thorough access review across all systems',
626
+ 'Implement data loss prevention (DLP) controls',
627
+ ],
628
+ indicators: [
629
+ 'Unusual database queries or large data exports',
630
+ 'Unauthorized access to sensitive file shares',
631
+ 'Anomalous outbound data transfers',
632
+ 'Login attempts from unusual locations or times',
633
+ 'New user accounts or privilege escalation events',
634
+ ],
635
+ },
636
+ ddos: {
637
+ containment: [
638
+ 'Enable DDoS mitigation (Cloudflare, AWS Shield, Akamai)',
639
+ 'Rate limit at load balancer and application level',
640
+ 'Implement geographic blocking if attack is from specific regions',
641
+ 'Scale infrastructure horizontally if cloud-based',
642
+ 'Enable syn cookies and connection limiting',
643
+ 'Communicate with ISP/hosting provider for upstream filtering',
644
+ ],
645
+ eradication: [
646
+ 'Identify attack vectors (volumetric, protocol, application layer)',
647
+ 'Block attack source IPs/ranges at network edge',
648
+ 'Implement CAPTCHA or proof-of-work for application-layer attacks',
649
+ 'Deploy web application firewall (WAF) rules for HTTP floods',
650
+ 'Analyze traffic patterns to create targeted filters',
651
+ 'Check for amplification/reflection sources',
652
+ ],
653
+ recovery: [
654
+ 'Gradually remove emergency blocking rules (avoid over-blocking)',
655
+ 'Monitor for renewed attacks for 48+ hours',
656
+ 'Document attack timeline, vectors, and mitigation effectiveness',
657
+ 'Implement permanent anti-DDoS architecture improvements',
658
+ 'Set up automated alerting for similar traffic patterns',
659
+ 'Conduct capacity planning based on attack volume',
660
+ ],
661
+ indicators: [
662
+ 'Massive spike in inbound traffic volume',
663
+ 'Server resource exhaustion (CPU, memory, connections)',
664
+ 'Unusual source IP distribution',
665
+ 'High volume of malformed packets',
666
+ 'Application timeout errors spike',
667
+ ],
668
+ },
669
+ insider_threat: {
670
+ containment: [
671
+ 'Disable suspected insider access WITHOUT alerting them (coordinate with HR/Legal)',
672
+ 'Preserve all access logs and audit trails',
673
+ 'Monitor (do not block) communications if legally permitted',
674
+ 'Secure any physical access they have to infrastructure',
675
+ 'Inventory what data and systems they have accessed',
676
+ 'Enable enhanced monitoring on their usual access patterns',
677
+ ],
678
+ eradication: [
679
+ 'Revoke all credentials, tokens, VPN access, and SSH keys',
680
+ 'Remove from all groups, distribution lists, and shared drives',
681
+ 'Change shared secrets or keys they had access to',
682
+ 'Review their code commits and infrastructure changes',
683
+ 'Check for time-bombs, backdoors, or logic bombs in code',
684
+ 'Audit any third-party accounts or services they configured',
685
+ ],
686
+ recovery: [
687
+ 'Implement least-privilege access review for all similar roles',
688
+ 'Enable user behavior analytics (UBA/UEBA)',
689
+ 'Implement data classification and access controls',
690
+ 'Review and improve separation of duties',
691
+ 'Update off-boarding procedures and access revocation checklists',
692
+ 'Conduct organization-wide access audit',
693
+ ],
694
+ indicators: [
695
+ 'Access to data outside normal job function',
696
+ 'Large file downloads or emails to personal accounts',
697
+ 'Access at unusual hours',
698
+ 'Attempts to access terminated or restricted accounts',
699
+ 'Circumvention of security controls',
700
+ ],
701
+ },
702
+ supply_chain: {
703
+ containment: [
704
+ 'Identify the compromised component (dependency, update, vendor)',
705
+ 'Pin/lock the dependency to last known-good version',
706
+ 'Block update servers or package registries temporarily',
707
+ 'Scan all deployed instances for the malicious component',
708
+ 'Isolate systems running the compromised version',
709
+ 'Notify vendor/maintainer of the compromise',
710
+ ],
711
+ eradication: [
712
+ 'Remove or downgrade the compromised dependency',
713
+ 'Audit all changes from the compromised version',
714
+ 'Scan for persistence mechanisms installed by the supply chain attack',
715
+ 'Review build pipeline integrity (CI/CD, signing, checksums)',
716
+ 'Check if attack included credential harvesting — rotate if so',
717
+ 'Verify integrity of all artifacts built during compromised period',
718
+ ],
719
+ recovery: [
720
+ 'Rebuild and redeploy from clean, audited sources',
721
+ 'Implement dependency pinning and lock files',
722
+ 'Set up automated dependency scanning (Dependabot, Snyk)',
723
+ 'Implement SLSA or SBOM for build provenance',
724
+ 'Establish vendor security review process',
725
+ 'Monitor for downstream effects on users/customers',
726
+ ],
727
+ indicators: [
728
+ 'Unexpected dependency version changes',
729
+ 'New network connections from updated components',
730
+ 'Build hash mismatches',
731
+ 'Unusual post-install scripts in packages',
732
+ 'Typosquatted package names',
733
+ ],
734
+ },
735
+ };
736
+ const STRIDE_CATEGORIES = [
737
+ {
738
+ id: 'S',
739
+ name: 'Spoofing',
740
+ question: 'Can an attacker pretend to be someone or something else?',
741
+ examples: [
742
+ 'Authentication bypass or weak authentication',
743
+ 'Session hijacking or token theft',
744
+ 'IP/DNS spoofing',
745
+ 'Phishing attacks using similar domains',
746
+ 'API key theft or replay attacks',
747
+ ],
748
+ mitigations: [
749
+ 'Strong authentication (MFA, OAuth 2.0, WebAuthn)',
750
+ 'Certificate pinning for critical connections',
751
+ 'Session management with secure, rotating tokens',
752
+ 'DMARC/DKIM/SPF for email',
753
+ 'API key rotation and IP allowlisting',
754
+ ],
755
+ },
756
+ {
757
+ id: 'T',
758
+ name: 'Tampering',
759
+ question: 'Can an attacker modify data in transit or at rest?',
760
+ examples: [
761
+ 'Man-in-the-middle attacks on unencrypted connections',
762
+ 'SQL injection modifying database records',
763
+ 'File upload vulnerabilities allowing code injection',
764
+ 'Parameter tampering in forms/APIs',
765
+ 'Log tampering to hide attacker activity',
766
+ ],
767
+ mitigations: [
768
+ 'TLS for all data in transit',
769
+ 'Input validation and parameterized queries',
770
+ 'Digital signatures for critical data',
771
+ 'File integrity monitoring (FIM)',
772
+ 'Append-only/immutable logging',
773
+ ],
774
+ },
775
+ {
776
+ id: 'R',
777
+ name: 'Repudiation',
778
+ question: 'Can a user deny performing an action?',
779
+ examples: [
780
+ 'Missing or insufficient audit logs',
781
+ 'Shared accounts making attribution impossible',
782
+ 'No timestamp verification on actions',
783
+ 'Deletable or modifiable log files',
784
+ 'Unsigned transactions',
785
+ ],
786
+ mitigations: [
787
+ 'Comprehensive audit logging with tamper protection',
788
+ 'Individual user accounts with MFA',
789
+ 'Digital signatures for critical actions',
790
+ 'Centralized, append-only log aggregation (SIEM)',
791
+ 'Non-repudiation protocols for financial transactions',
792
+ ],
793
+ },
794
+ {
795
+ id: 'I',
796
+ name: 'Information Disclosure',
797
+ question: 'Can an attacker access data they should not see?',
798
+ examples: [
799
+ 'Sensitive data in error messages or stack traces',
800
+ 'Directory traversal exposing system files',
801
+ 'IDOR (Insecure Direct Object Reference)',
802
+ 'API responses including excess data',
803
+ 'Backup files accessible via web',
804
+ ],
805
+ mitigations: [
806
+ 'Encrypt sensitive data at rest and in transit',
807
+ 'Implement proper access controls and authorization',
808
+ 'Minimize data in API responses (field-level security)',
809
+ 'Custom error pages (no stack traces in production)',
810
+ 'Data classification and handling policies',
811
+ ],
812
+ },
813
+ {
814
+ id: 'D',
815
+ name: 'Denial of Service',
816
+ question: 'Can an attacker make the system unavailable?',
817
+ examples: [
818
+ 'Volumetric DDoS attacks',
819
+ 'Application-layer attacks (slowloris, hash DoS)',
820
+ 'Resource exhaustion (CPU, memory, disk, connections)',
821
+ 'Algorithmic complexity attacks (ReDoS, billion laughs)',
822
+ 'Dependency on single points of failure',
823
+ ],
824
+ mitigations: [
825
+ 'Rate limiting and throttling',
826
+ 'CDN and DDoS protection services',
827
+ 'Input size limits and timeout enforcement',
828
+ 'Horizontal scaling and load balancing',
829
+ 'Circuit breakers and graceful degradation',
830
+ ],
831
+ },
832
+ {
833
+ id: 'E',
834
+ name: 'Elevation of Privilege',
835
+ question: 'Can an attacker gain higher privileges than intended?',
836
+ examples: [
837
+ 'Privilege escalation via misconfigured RBAC',
838
+ 'JWT manipulation to change roles',
839
+ 'Insecure deserialization leading to RCE',
840
+ 'Container escape or VM breakout',
841
+ 'Default credentials on admin interfaces',
842
+ ],
843
+ mitigations: [
844
+ 'Principle of least privilege everywhere',
845
+ 'Server-side role validation (never trust client)',
846
+ 'Secure defaults (deny by default)',
847
+ 'Regular privilege audits',
848
+ 'Sandboxing and isolation (containers, VMs)',
849
+ ],
850
+ },
851
+ ];
852
+ // ═══════════════════════════════════════════════════════════════════════════════
853
+ // REGISTRATION
854
+ // ═══════════════════════════════════════════════════════════════════════════════
855
+ export function registerThreatIntelTools() {
856
+ // ─── 1. threat_feed ─────────────────────────────────────────────────────────
857
+ registerTool({
858
+ name: 'threat_feed',
859
+ description: 'Aggregate cyber threat intelligence from free public sources. Fetches recent high/critical CVEs from NIST NVD, recent exploits from Exploit-DB, and optionally matches against your tech stack (from dream journal insights). Returns top threats with severity, affected software, and stack relevance.',
860
+ parameters: {
861
+ days: { type: 'number', description: 'Look-back period in days (default: 7, max: 30)' },
862
+ tech_stack: { type: 'string', description: 'Comma-separated tech stack to match against (e.g. "node,react,postgres"). Auto-detected from dream journal if not provided.' },
863
+ include_exploits: { type: 'boolean', description: 'Include recent Exploit-DB entries (default: true)' },
864
+ },
865
+ tier: 'free',
866
+ timeout: 60_000,
867
+ maxResultSize: 50_000,
868
+ async execute(args) {
869
+ const days = Math.min(Math.max(Number(args.days) || 7, 1), 30);
870
+ const includeExploits = args.include_exploits !== false;
871
+ const lines = ['## Threat Intelligence Feed', ''];
872
+ // Parse tech stack
873
+ let stackTerms = [];
874
+ if (args.tech_stack) {
875
+ stackTerms = String(args.tech_stack).toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
876
+ }
877
+ else {
878
+ // Auto-detect from dream journal
879
+ const insights = getDreamInsights();
880
+ if (insights.length > 0) {
881
+ stackTerms = insights.slice(0, 20);
882
+ lines.push(`*Tech stack auto-detected from dream journal: ${stackTerms.slice(0, 10).join(', ')}*`, '');
883
+ }
884
+ }
885
+ // Fetch CVEs
886
+ lines.push(`### Recent CVEs (last ${days} days, HIGH/CRITICAL)`, '');
887
+ const cves = await fetchRecentCves(days);
888
+ if (cves.length === 0) {
889
+ lines.push('*No high/critical CVEs found in the specified period, or NVD API is rate-limited. Try again in a few minutes.*', '');
890
+ }
891
+ else {
892
+ lines.push('| CVE | Score | Severity | Affected | Published |', '|-----|-------|----------|----------|-----------|');
893
+ let stackMatches = 0;
894
+ for (const cve of cves.slice(0, 10)) {
895
+ const affectedStr = cve.affected.length > 0 ? cve.affected.join(', ') : 'See NVD';
896
+ const isStackMatch = stackTerms.length > 0 && (stackTerms.some(term => cve.affected.some(a => a.toLowerCase().includes(term)) ||
897
+ cve.description.toLowerCase().includes(term)));
898
+ const marker = isStackMatch ? ' **[STACK MATCH]**' : '';
899
+ if (isStackMatch)
900
+ stackMatches++;
901
+ lines.push(`| ${cve.id} | ${cve.score} | ${cve.severity} | ${affectedStr} | ${cve.published} |${marker}`);
902
+ }
903
+ lines.push('');
904
+ if (stackMatches > 0) {
905
+ lines.push(`> **${stackMatches} CVE(s) match your tech stack.** Review these immediately.`, '');
906
+ }
907
+ // Details for top 3
908
+ lines.push('### Top CVE Details', '');
909
+ for (const cve of cves.slice(0, 3)) {
910
+ lines.push(`#### ${cve.id} (${cve.severity}, CVSS ${cve.score})`);
911
+ lines.push(cve.description, '');
912
+ }
913
+ }
914
+ // Fetch exploits
915
+ if (includeExploits) {
916
+ lines.push('### Recent Exploits (Exploit-DB, last 30 days)', '');
917
+ const exploits = await fetchExploitDbRecent();
918
+ if (exploits.length === 0) {
919
+ lines.push('*Could not fetch Exploit-DB data. The GitLab mirror may be unavailable.*', '');
920
+ }
921
+ else {
922
+ lines.push('| Title | Date | Type | Platform |', '|-------|------|------|----------|');
923
+ for (const ex of exploits.slice(0, 10)) {
924
+ lines.push(`| ${ex.title.slice(0, 60)} | ${ex.date} | ${ex.type} | ${ex.platform} |`);
925
+ }
926
+ lines.push('');
927
+ }
928
+ }
929
+ // Summary
930
+ lines.push('### Intelligence Summary', '');
931
+ lines.push(`- **CVEs found**: ${cves.length} (showing top 10)`);
932
+ lines.push(`- **Period**: last ${days} days`);
933
+ lines.push(`- **Sources**: NIST NVD${includeExploits ? ', Exploit-DB' : ''}`);
934
+ if (stackTerms.length > 0) {
935
+ lines.push(`- **Stack monitoring**: ${stackTerms.slice(0, 10).join(', ')}`);
936
+ }
937
+ lines.push('');
938
+ lines.push('*Tip: Run `ioc_check` to verify specific indicators. Run `attack_surface_scan` to assess your own exposure.*');
939
+ return lines.join('\n');
940
+ },
941
+ });
942
+ // ─── 2. ioc_check ──────────────────────────────────────────────────────────
943
+ registerTool({
944
+ name: 'ioc_check',
945
+ description: 'Check an Indicator of Compromise (IOC) against free threat intelligence databases. Accepts IP addresses, domains, file hashes (MD5/SHA1/SHA256), or URLs. Checks URLhaus (abuse.ch), ThreatFox, and optionally VirusTotal and AbuseIPDB (if API keys are configured in ~/.kbot/config.json). Returns reputation score, reports, and associated malware families.',
946
+ parameters: {
947
+ indicator: { type: 'string', description: 'The IOC to check: IP address, domain, file hash (MD5/SHA1/SHA256), or URL', required: true },
948
+ },
949
+ tier: 'free',
950
+ timeout: 45_000,
951
+ async execute(args) {
952
+ const indicator = String(args.indicator || '').trim();
953
+ if (!indicator)
954
+ return 'Error: No indicator provided. Provide an IP, domain, hash, or URL.';
955
+ const iocType = classifyIoc(indicator);
956
+ if (iocType === 'unknown') {
957
+ return `Error: Could not classify indicator "${indicator}". Provide a valid IP, domain, file hash (MD5/SHA1/SHA256), or URL.`;
958
+ }
959
+ const lines = [
960
+ '## IOC Check Report',
961
+ '',
962
+ `| Field | Value |`,
963
+ `|-------|-------|`,
964
+ `| Indicator | \`${indicator}\` |`,
965
+ `| Type | ${iocType.replace('_', ' ').toUpperCase()} |`,
966
+ `| Checked | ${new Date().toISOString()} |`,
967
+ '',
968
+ ];
969
+ const results = [];
970
+ const checks = [];
971
+ // URLhaus — free, no key needed
972
+ checks.push(checkUrlhaus(indicator, iocType));
973
+ // ThreatFox — free, no key needed
974
+ if (['hash_md5', 'hash_sha256', 'domain', 'ip'].includes(iocType)) {
975
+ checks.push(checkThreatFox(indicator, iocType));
976
+ }
977
+ // AbuseIPDB — needs key
978
+ if (iocType === 'ip') {
979
+ checks.push(checkAbuseIpdb(indicator));
980
+ }
981
+ // VirusTotal — needs key
982
+ checks.push(checkVirusTotal(indicator, iocType));
983
+ const checkResults = await Promise.all(checks);
984
+ for (const r of checkResults) {
985
+ if (r)
986
+ results.push(r);
987
+ }
988
+ if (results.length === 0) {
989
+ lines.push('### Results', '');
990
+ lines.push('**No threat intelligence found** for this indicator across all checked sources.', '');
991
+ lines.push('This means either:', '');
992
+ lines.push('- The indicator is clean/benign', '');
993
+ lines.push('- It has not been reported to these databases yet', '');
994
+ lines.push('- The free APIs were rate-limited', '');
995
+ lines.push('');
996
+ lines.push('*Configure VirusTotal or AbuseIPDB API keys in `~/.kbot/config.json` for broader coverage.*');
997
+ }
998
+ else {
999
+ // Compute reputation score
1000
+ let threatScore = 0;
1001
+ let maxScore = 0;
1002
+ const malwareFamilies = new Set();
1003
+ for (const r of results) {
1004
+ if (r.source === 'AbuseIPDB') {
1005
+ const abuseScore = Number(r.data.abuse_score || 0);
1006
+ threatScore += abuseScore;
1007
+ maxScore += 100;
1008
+ }
1009
+ if (r.source === 'VirusTotal') {
1010
+ const malicious = Number(r.data.malicious ?? r.data.detections ?? 0);
1011
+ const total = Number(r.data.total_engines ?? 80);
1012
+ threatScore += Math.round((malicious / Math.max(total, 1)) * 100);
1013
+ maxScore += 100;
1014
+ }
1015
+ if (r.source.includes('URLhaus')) {
1016
+ const urlCount = Number(r.data.url_count || r.data.urls_online || 0);
1017
+ if (urlCount > 0) {
1018
+ threatScore += 80;
1019
+ maxScore += 100;
1020
+ }
1021
+ else {
1022
+ maxScore += 100;
1023
+ }
1024
+ }
1025
+ if (r.source.includes('ThreatFox')) {
1026
+ const matches = r.data.matches;
1027
+ if (matches?.length) {
1028
+ threatScore += 90;
1029
+ maxScore += 100;
1030
+ for (const m of matches) {
1031
+ if (m.malware)
1032
+ malwareFamilies.add(m.malware);
1033
+ }
1034
+ }
1035
+ else {
1036
+ maxScore += 100;
1037
+ }
1038
+ }
1039
+ }
1040
+ const reputationScore = maxScore > 0 ? Math.round((threatScore / maxScore) * 100) : 0;
1041
+ const verdict = reputationScore >= 70 ? 'MALICIOUS' :
1042
+ reputationScore >= 40 ? 'SUSPICIOUS' :
1043
+ reputationScore >= 10 ? 'LOW RISK' : 'CLEAN';
1044
+ lines.push(`### Reputation: **${verdict}** (score: ${reputationScore}/100)`, '');
1045
+ if (malwareFamilies.size > 0) {
1046
+ lines.push(`### Associated Malware Families`, '');
1047
+ for (const family of Array.from(malwareFamilies)) {
1048
+ lines.push(`- ${family}`);
1049
+ }
1050
+ lines.push('');
1051
+ }
1052
+ lines.push('### Source Reports', '');
1053
+ for (const r of results) {
1054
+ lines.push(`#### ${r.source}`, '');
1055
+ for (const [key, value] of Object.entries(r.data)) {
1056
+ if (value === null || value === undefined)
1057
+ continue;
1058
+ if (Array.isArray(value)) {
1059
+ if (value.length === 0)
1060
+ continue;
1061
+ if (typeof value[0] === 'object') {
1062
+ lines.push(`**${key}**:`);
1063
+ for (const item of value) {
1064
+ lines.push(`- ${JSON.stringify(item)}`);
1065
+ }
1066
+ }
1067
+ else {
1068
+ lines.push(`**${key}**: ${value.join(', ')}`);
1069
+ }
1070
+ }
1071
+ else if (typeof value === 'object') {
1072
+ lines.push(`**${key}**: ${JSON.stringify(value)}`);
1073
+ }
1074
+ else {
1075
+ lines.push(`**${key}**: ${value}`);
1076
+ }
1077
+ }
1078
+ lines.push('');
1079
+ }
1080
+ }
1081
+ lines.push('---', '');
1082
+ lines.push(`*Sources checked: ${results.length > 0 ? results.map(r => r.source).join(', ') : 'URLhaus, ThreatFox'}*`);
1083
+ return lines.join('\n');
1084
+ },
1085
+ });
1086
+ // ─── 3. attack_surface_scan ─────────────────────────────────────────────────
1087
+ registerTool({
1088
+ name: 'attack_surface_scan',
1089
+ description: 'Passive reconnaissance of a domain\'s attack surface. Enumerates DNS records (A, AAAA, MX, NS, TXT, CNAME, SOA), checks HTTP security headers, inspects SSL/TLS certificate, and identifies exposed services. No active scanning — purely passive, legal information gathering from public sources.',
1090
+ parameters: {
1091
+ domain: { type: 'string', description: 'Target domain name (e.g., "example.com")', required: true },
1092
+ check_subdomains: { type: 'boolean', description: 'Check common subdomains (www, api, mail, etc.) via DNS. Default: false.' },
1093
+ },
1094
+ tier: 'free',
1095
+ timeout: 90_000,
1096
+ maxResultSize: 50_000,
1097
+ async execute(args) {
1098
+ const domain = String(args.domain || '').trim().replace(/^https?:\/\//, '').replace(/\/.*$/, '');
1099
+ if (!domain || !domain.includes('.')) {
1100
+ return 'Error: Provide a valid domain name (e.g., "example.com").';
1101
+ }
1102
+ const checkSubs = args.check_subdomains === true;
1103
+ const lines = [
1104
+ '## Attack Surface Report',
1105
+ '',
1106
+ `| Field | Value |`,
1107
+ `|-------|-------|`,
1108
+ `| Target | ${domain} |`,
1109
+ `| Scan type | Passive reconnaissance |`,
1110
+ `| Date | ${new Date().toISOString()} |`,
1111
+ '',
1112
+ ];
1113
+ let overallScore = 0;
1114
+ let maxScore = 0;
1115
+ // ── DNS Records ──
1116
+ lines.push('### DNS Records', '');
1117
+ const dns = await enumerateDns(domain);
1118
+ const dnsEntries = [
1119
+ ['A (IPv4)', dns.A],
1120
+ ['AAAA (IPv6)', dns.AAAA],
1121
+ ['MX (Mail)', dns.MX],
1122
+ ['NS (Nameservers)', dns.NS],
1123
+ ['TXT', dns.TXT],
1124
+ ['CNAME', dns.CNAME],
1125
+ ['SOA', dns.SOA],
1126
+ ];
1127
+ for (const [label, records] of dnsEntries) {
1128
+ if (records.length > 0) {
1129
+ lines.push(`**${label}**:`);
1130
+ for (const r of records)
1131
+ lines.push(`- \`${r}\``);
1132
+ lines.push('');
1133
+ }
1134
+ }
1135
+ // Check for SPF, DKIM, DMARC in TXT records
1136
+ const txtJoined = dns.TXT.join(' ').toLowerCase();
1137
+ const hasSPF = txtJoined.includes('v=spf1');
1138
+ const hasDMARC = dns.TXT.some(t => t.toLowerCase().includes('v=dmarc'));
1139
+ lines.push('**Email Security**:');
1140
+ lines.push(`- SPF: ${hasSPF ? 'Present' : 'MISSING'}`);
1141
+ lines.push(`- DMARC: ${hasDMARC ? 'Present' : 'MISSING'}`);
1142
+ if (hasSPF) {
1143
+ overallScore += 10;
1144
+ maxScore += 10;
1145
+ }
1146
+ else {
1147
+ maxScore += 10;
1148
+ }
1149
+ if (hasDMARC) {
1150
+ overallScore += 10;
1151
+ maxScore += 10;
1152
+ }
1153
+ else {
1154
+ maxScore += 10;
1155
+ }
1156
+ // Check for DMARC via _dmarc subdomain
1157
+ if (!hasDMARC) {
1158
+ const resolver = new Resolver();
1159
+ resolver.setServers(['1.1.1.1']);
1160
+ const dmarcRecords = await dnsResolve(resolver, `_dmarc.${domain}`, 'TXT');
1161
+ if (dmarcRecords.some(r => r.toLowerCase().includes('v=dmarc'))) {
1162
+ lines.push(`- DMARC (via _dmarc.${domain}): Present`);
1163
+ overallScore += 10; // Adjust score since we found it
1164
+ }
1165
+ }
1166
+ lines.push('');
1167
+ // ── HTTP Security Headers ──
1168
+ lines.push('### HTTP Security Headers', '');
1169
+ const httpsUrl = `https://${domain}`;
1170
+ const headerCheck = await checkHttpHeaders(httpsUrl);
1171
+ if (headerCheck.present.length === 0 && headerCheck.missing.length === 0) {
1172
+ lines.push('*Could not connect to the target over HTTPS.*', '');
1173
+ // Try HTTP
1174
+ const httpCheck = await checkHttpHeaders(`http://${domain}`);
1175
+ if (httpCheck.present.length > 0 || httpCheck.missing.length > 0) {
1176
+ lines.push('> **WARNING**: Site only accessible over HTTP (no HTTPS). This is a critical security issue.', '');
1177
+ overallScore -= 20;
1178
+ }
1179
+ }
1180
+ else {
1181
+ overallScore += headerCheck.score * 0.4; // Headers worth 40% of score
1182
+ maxScore += 40;
1183
+ if (headerCheck.server) {
1184
+ lines.push(`> **Server header exposed**: \`${headerCheck.server}\` — consider removing to reduce information leakage.`, '');
1185
+ }
1186
+ if (headerCheck.poweredBy) {
1187
+ lines.push(`> **X-Powered-By exposed**: \`${headerCheck.poweredBy}\` — remove this header.`, '');
1188
+ }
1189
+ lines.push('| Header | Status | Purpose |', '|--------|--------|---------|');
1190
+ for (const h of headerCheck.present) {
1191
+ const desc = EXPECTED_HEADERS[h]?.description || '';
1192
+ lines.push(`| ${h} | Present | ${desc} |`);
1193
+ }
1194
+ for (const h of headerCheck.missing) {
1195
+ const desc = EXPECTED_HEADERS[h]?.description || '';
1196
+ lines.push(`| ${h} | **MISSING** | ${desc} |`);
1197
+ }
1198
+ lines.push('');
1199
+ lines.push(`**Header Score**: ${headerCheck.score}/100`, '');
1200
+ }
1201
+ // ── SSL/TLS Certificate ──
1202
+ lines.push('### SSL/TLS Certificate', '');
1203
+ const certInfo = await getTlsCertInfo(domain);
1204
+ if (!certInfo) {
1205
+ lines.push('*Could not retrieve SSL/TLS certificate.*', '');
1206
+ maxScore += 20;
1207
+ }
1208
+ else {
1209
+ overallScore += 20;
1210
+ maxScore += 20;
1211
+ const validTo = new Date(String(certInfo.valid_to));
1212
+ const daysLeft = Math.ceil((validTo.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
1213
+ const subject = certInfo.subject;
1214
+ const issuer = certInfo.issuer;
1215
+ lines.push(`| Property | Value |`, `|----------|-------|`);
1216
+ lines.push(`| Subject | ${subject?.CN || subject?.O || 'Unknown'} |`);
1217
+ lines.push(`| Issuer | ${issuer?.O || issuer?.CN || 'Unknown'} |`);
1218
+ lines.push(`| Valid From | ${certInfo.valid_from} |`);
1219
+ lines.push(`| Valid To | ${certInfo.valid_to} |`);
1220
+ lines.push(`| Days Remaining | ${daysLeft} |`);
1221
+ lines.push(`| Key Size | ${certInfo.bits || 'Unknown'} bits |`);
1222
+ if (certInfo.subjectaltname) {
1223
+ const sans = String(certInfo.subjectaltname).split(',').map(s => s.trim()).slice(0, 10);
1224
+ lines.push(`| SANs | ${sans.join(', ')} |`);
1225
+ }
1226
+ lines.push('');
1227
+ if (daysLeft < 0) {
1228
+ lines.push('> **CRITICAL**: Certificate has EXPIRED!', '');
1229
+ overallScore -= 20;
1230
+ }
1231
+ else if (daysLeft < 30) {
1232
+ lines.push(`> **WARNING**: Certificate expires in ${daysLeft} days. Renew soon.`, '');
1233
+ }
1234
+ }
1235
+ // ── Subdomain Check ──
1236
+ if (checkSubs) {
1237
+ lines.push('### Subdomain Enumeration (DNS brute-force)', '');
1238
+ const resolver = new Resolver();
1239
+ resolver.setServers(['1.1.1.1', '8.8.8.8']);
1240
+ const commonSubs = [
1241
+ 'www', 'mail', 'ftp', 'api', 'dev', 'staging', 'test', 'admin',
1242
+ 'blog', 'shop', 'portal', 'app', 'cdn', 'static', 'docs',
1243
+ 'vpn', 'remote', 'intranet', 'git', 'ci', 'monitor', 'status',
1244
+ ];
1245
+ const found = [];
1246
+ const subChecks = commonSubs.map(async (sub) => {
1247
+ const fqdn = `${sub}.${domain}`;
1248
+ const ips = await dnsResolve(resolver, fqdn, 'A');
1249
+ if (ips.length > 0) {
1250
+ found.push({ subdomain: fqdn, ip: ips[0] });
1251
+ }
1252
+ });
1253
+ await Promise.all(subChecks);
1254
+ if (found.length > 0) {
1255
+ lines.push('| Subdomain | IP |', '|-----------|-----|');
1256
+ for (const f of found.sort((a, b) => a.subdomain.localeCompare(b.subdomain))) {
1257
+ lines.push(`| ${f.subdomain} | ${f.ip} |`);
1258
+ }
1259
+ lines.push('');
1260
+ lines.push(`Found **${found.length}** subdomains out of ${commonSubs.length} checked.`, '');
1261
+ }
1262
+ else {
1263
+ lines.push('*No common subdomains found via DNS.*', '');
1264
+ }
1265
+ }
1266
+ // ── Overall Score ──
1267
+ const finalScore = maxScore > 0 ? Math.max(0, Math.min(100, Math.round((overallScore / maxScore) * 100))) : 0;
1268
+ const grade = finalScore >= 90 ? 'A' :
1269
+ finalScore >= 80 ? 'B' :
1270
+ finalScore >= 70 ? 'C' :
1271
+ finalScore >= 50 ? 'D' : 'F';
1272
+ lines.push('### Overall Security Posture', '');
1273
+ lines.push(`| Metric | Value |`, `|--------|-------|`);
1274
+ lines.push(`| Score | ${finalScore}/100 |`);
1275
+ lines.push(`| Grade | **${grade}** |`);
1276
+ lines.push('');
1277
+ if (grade === 'D' || grade === 'F') {
1278
+ lines.push('> **Action required**: This domain has significant security gaps. Address missing headers and certificate issues immediately.', '');
1279
+ }
1280
+ lines.push('---', '');
1281
+ lines.push('*This is a passive scan only. No active probing or exploitation was performed. For deeper assessment, use `pentest_start` or `security_hunt`.*');
1282
+ return lines.join('\n');
1283
+ },
1284
+ });
1285
+ // ─── 4. incident_response ───────────────────────────────────────────────────
1286
+ registerTool({
1287
+ name: 'incident_response',
1288
+ description: 'Generate an incident response playbook for a security incident. Given an incident type and description, produces a structured playbook with containment steps, eradication plan, recovery checklist, lessons learned template, and IOC indicators. Uses local Ollama for contextual AI analysis when available. Incident types: ransomware, data_breach, ddos, insider_threat, supply_chain.',
1289
+ parameters: {
1290
+ incident_type: { type: 'string', description: 'Type of incident: ransomware, data_breach, ddos, insider_threat, supply_chain', required: true },
1291
+ description: { type: 'string', description: 'Description of the incident — what happened, when, what systems are affected', required: true },
1292
+ severity: { type: 'string', description: 'Incident severity: critical, high, medium, low (default: high)' },
1293
+ affected_systems: { type: 'string', description: 'Comma-separated list of affected systems or services' },
1294
+ },
1295
+ tier: 'free',
1296
+ timeout: 120_000,
1297
+ maxResultSize: 50_000,
1298
+ async execute(args) {
1299
+ const incidentType = String(args.incident_type || '').toLowerCase().replace(/\s+/g, '_');
1300
+ const description = String(args.description || '');
1301
+ const severity = String(args.severity || 'high').toUpperCase();
1302
+ const affectedSystems = args.affected_systems
1303
+ ? String(args.affected_systems).split(',').map(s => s.trim()).filter(Boolean)
1304
+ : [];
1305
+ if (!description) {
1306
+ return 'Error: Provide an incident description (what happened, when, what is affected).';
1307
+ }
1308
+ const template = INCIDENT_TYPES[incidentType];
1309
+ if (!template) {
1310
+ const validTypes = Object.keys(INCIDENT_TYPES).join(', ');
1311
+ return `Error: Unknown incident type "${incidentType}". Valid types: ${validTypes}`;
1312
+ }
1313
+ const lines = [
1314
+ '# Incident Response Playbook',
1315
+ '',
1316
+ `| Field | Value |`,
1317
+ `|-------|-------|`,
1318
+ `| Incident Type | ${incidentType.replace(/_/g, ' ').toUpperCase()} |`,
1319
+ `| Severity | **${severity}** |`,
1320
+ `| Generated | ${new Date().toISOString()} |`,
1321
+ `| Incident ID | IR-${Date.now().toString(36).toUpperCase()} |`,
1322
+ '',
1323
+ ];
1324
+ if (affectedSystems.length > 0) {
1325
+ lines.push(`**Affected Systems**: ${affectedSystems.join(', ')}`, '');
1326
+ }
1327
+ lines.push(`**Incident Description**: ${description}`, '');
1328
+ // ── Phase 1: Detection & Analysis ──
1329
+ lines.push('## Phase 1: Detection & Analysis', '');
1330
+ lines.push('### Known Indicators for this Incident Type', '');
1331
+ for (const indicator of template.indicators) {
1332
+ lines.push(`- [ ] ${indicator}`);
1333
+ }
1334
+ lines.push('');
1335
+ // ── Phase 2: Containment ──
1336
+ lines.push('## Phase 2: Containment', '');
1337
+ lines.push('*Execute immediately to limit damage. Track time spent on each step.*', '');
1338
+ for (let i = 0; i < template.containment.length; i++) {
1339
+ lines.push(`${i + 1}. [ ] ${template.containment[i]}`);
1340
+ }
1341
+ lines.push('');
1342
+ // ── Phase 3: Eradication ──
1343
+ lines.push('## Phase 3: Eradication', '');
1344
+ lines.push('*Remove the threat completely. Do not rush — incomplete eradication leads to reinfection.*', '');
1345
+ for (let i = 0; i < template.eradication.length; i++) {
1346
+ lines.push(`${i + 1}. [ ] ${template.eradication[i]}`);
1347
+ }
1348
+ lines.push('');
1349
+ // ── Phase 4: Recovery ──
1350
+ lines.push('## Phase 4: Recovery', '');
1351
+ lines.push('*Restore normal operations. Monitor closely for signs of recurrence.*', '');
1352
+ for (let i = 0; i < template.recovery.length; i++) {
1353
+ lines.push(`${i + 1}. [ ] ${template.recovery[i]}`);
1354
+ }
1355
+ lines.push('');
1356
+ // ── AI Analysis (Ollama) ──
1357
+ const ollamaAvailable = await isOllamaAvailable();
1358
+ if (ollamaAvailable) {
1359
+ lines.push('## AI-Guided Contextual Analysis', '');
1360
+ const prompt = [
1361
+ `You are a senior incident response analyst. Analyze this security incident and provide specific, actionable guidance.`,
1362
+ ``,
1363
+ `Incident Type: ${incidentType.replace(/_/g, ' ')}`,
1364
+ `Severity: ${severity}`,
1365
+ `Description: ${description}`,
1366
+ affectedSystems.length > 0 ? `Affected Systems: ${affectedSystems.join(', ')}` : '',
1367
+ ``,
1368
+ `Provide:`,
1369
+ `1. IMMEDIATE ACTIONS — the 3 most critical things to do right now`,
1370
+ `2. THREAT ANALYSIS — likely attack vector and adversary profile based on the description`,
1371
+ `3. EVIDENCE COLLECTION — what forensic evidence to preserve and how`,
1372
+ `4. COMMUNICATION PLAN — who to notify and when (legal, executives, customers, regulators)`,
1373
+ `5. TIMELINE ESTIMATION — how long each phase should take`,
1374
+ ``,
1375
+ `Be specific to this incident. No generic advice.`,
1376
+ ].filter(Boolean).join('\n');
1377
+ const analysis = await ollamaGenerate(prompt);
1378
+ if (analysis) {
1379
+ lines.push(analysis, '');
1380
+ }
1381
+ else {
1382
+ lines.push('*Ollama analysis unavailable — model may not be loaded. Run `ollama pull kernel-coder:latest`.*', '');
1383
+ }
1384
+ }
1385
+ else {
1386
+ lines.push('> *Ollama not running. Start Ollama for AI-guided contextual analysis specific to your incident.*', '');
1387
+ }
1388
+ // ── Phase 5: Lessons Learned ──
1389
+ lines.push('## Phase 5: Post-Incident Review (Lessons Learned)', '');
1390
+ lines.push('*Conduct within 72 hours of incident closure. All responders should attend.*', '');
1391
+ lines.push('');
1392
+ lines.push('### Review Template', '');
1393
+ lines.push('| Question | Answer |', '|----------|--------|');
1394
+ lines.push('| What happened? | |');
1395
+ lines.push('| When was it detected? | |');
1396
+ lines.push('| How was it detected? | |');
1397
+ lines.push('| What was the root cause? | |');
1398
+ lines.push('| What went well in the response? | |');
1399
+ lines.push('| What could be improved? | |');
1400
+ lines.push('| What process changes are needed? | |');
1401
+ lines.push('| What tools/capabilities were missing? | |');
1402
+ lines.push('| Total time to detect (TTD)? | |');
1403
+ lines.push('| Total time to contain (TTC)? | |');
1404
+ lines.push('| Total time to recover (TTR)? | |');
1405
+ lines.push('| Business impact estimate? | |');
1406
+ lines.push('');
1407
+ // ── Communication Templates ──
1408
+ lines.push('## Communication Templates', '');
1409
+ lines.push('### Internal Notification (Executive)', '');
1410
+ lines.push('```');
1411
+ lines.push(`Subject: Security Incident - ${incidentType.replace(/_/g, ' ').toUpperCase()} - Severity: ${severity}`);
1412
+ lines.push('');
1413
+ lines.push(`At [TIME], our security team detected a ${incidentType.replace(/_/g, ' ')} incident`);
1414
+ lines.push(`affecting [SYSTEMS]. We have activated our incident response plan and`);
1415
+ lines.push(`containment measures are in progress. Current status: [CONTAINING/ERADICATING/RECOVERING].`);
1416
+ lines.push('');
1417
+ lines.push(`Next update: [TIME]`);
1418
+ lines.push('```');
1419
+ lines.push('');
1420
+ lines.push('---', '');
1421
+ lines.push('*Generated by kbot threat-intel. This is a starting framework — adapt to your organization\'s IR procedures.*');
1422
+ return lines.join('\n');
1423
+ },
1424
+ });
1425
+ // ─── 5. threat_model ────────────────────────────────────────────────────────
1426
+ registerTool({
1427
+ name: 'threat_model',
1428
+ description: 'Generate a STRIDE threat model for a system. Given a system description (tech stack, architecture, data flows), analyze Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege threats. Prioritizes by likelihood and impact. Uses local Ollama for contextual AI analysis when available.',
1429
+ parameters: {
1430
+ system: { type: 'string', description: 'System description: tech stack, architecture overview, key components', required: true },
1431
+ data_flows: { type: 'string', description: 'Key data flows (e.g., "user -> API -> database, API -> payment gateway")' },
1432
+ trust_boundaries: { type: 'string', description: 'Trust boundaries (e.g., "internet/DMZ, DMZ/internal, app/database")' },
1433
+ assets: { type: 'string', description: 'High-value assets to protect (e.g., "user PII, payment data, API keys")' },
1434
+ },
1435
+ tier: 'free',
1436
+ timeout: 120_000,
1437
+ maxResultSize: 50_000,
1438
+ async execute(args) {
1439
+ const system = String(args.system || '');
1440
+ if (!system) {
1441
+ return 'Error: Provide a system description (tech stack, architecture, key components).';
1442
+ }
1443
+ const dataFlows = args.data_flows ? String(args.data_flows).split(',').map(s => s.trim()) : [];
1444
+ const trustBoundaries = args.trust_boundaries ? String(args.trust_boundaries).split(',').map(s => s.trim()) : [];
1445
+ const assets = args.assets ? String(args.assets).split(',').map(s => s.trim()) : [];
1446
+ const lines = [
1447
+ '# STRIDE Threat Model',
1448
+ '',
1449
+ `| Field | Value |`,
1450
+ `|-------|-------|`,
1451
+ `| Generated | ${new Date().toISOString()} |`,
1452
+ `| Model ID | TM-${Date.now().toString(36).toUpperCase()} |`,
1453
+ '',
1454
+ '## System Description',
1455
+ '',
1456
+ system,
1457
+ '',
1458
+ ];
1459
+ if (dataFlows.length > 0) {
1460
+ lines.push('### Data Flows', '');
1461
+ for (const flow of dataFlows)
1462
+ lines.push(`- ${flow}`);
1463
+ lines.push('');
1464
+ }
1465
+ if (trustBoundaries.length > 0) {
1466
+ lines.push('### Trust Boundaries', '');
1467
+ for (const boundary of trustBoundaries)
1468
+ lines.push(`- ${boundary}`);
1469
+ lines.push('');
1470
+ }
1471
+ if (assets.length > 0) {
1472
+ lines.push('### High-Value Assets', '');
1473
+ for (const asset of assets)
1474
+ lines.push(`- ${asset}`);
1475
+ lines.push('');
1476
+ }
1477
+ // ── STRIDE Analysis ──
1478
+ lines.push('## STRIDE Analysis', '');
1479
+ // Detect common tech from system description for relevance scoring
1480
+ const sysLower = system.toLowerCase();
1481
+ const hasWeb = /\b(web|http|api|rest|graphql|html|browser|spa|react|angular|vue)\b/.test(sysLower);
1482
+ const hasDb = /\b(database|db|sql|postgres|mysql|mongo|redis|dynamo|supabase)\b/.test(sysLower);
1483
+ const hasAuth = /\b(auth|login|jwt|oauth|session|token|password|credential)\b/.test(sysLower);
1484
+ const hasCloud = /\b(aws|gcp|azure|cloud|s3|lambda|kubernetes|docker|container)\b/.test(sysLower);
1485
+ const hasPayment = /\b(payment|stripe|paypal|credit card|billing|financial)\b/.test(sysLower);
1486
+ const hasApi = /\b(api|endpoint|microservice|grpc|webhook)\b/.test(sysLower);
1487
+ const threats = [];
1488
+ for (const stride of STRIDE_CATEGORIES) {
1489
+ lines.push(`### ${stride.id} - ${stride.name}`, '');
1490
+ lines.push(`> *${stride.question}*`, '');
1491
+ // Select relevant threats based on system characteristics
1492
+ const relevantExamples = [];
1493
+ const relevantMitigations = [];
1494
+ for (const ex of stride.examples) {
1495
+ const exLower = ex.toLowerCase();
1496
+ let relevant = true;
1497
+ // Filter by relevance
1498
+ if (exLower.includes('sql injection') && !hasDb)
1499
+ relevant = false;
1500
+ if (exLower.includes('jwt') && !hasAuth)
1501
+ relevant = false;
1502
+ if (exLower.includes('container') && !hasCloud)
1503
+ relevant = false;
1504
+ if (relevant)
1505
+ relevantExamples.push(ex);
1506
+ }
1507
+ // Always include at least 3 examples
1508
+ const examples = relevantExamples.length >= 3
1509
+ ? relevantExamples
1510
+ : stride.examples.slice(0, Math.max(3, relevantExamples.length));
1511
+ lines.push('**Potential Threats**:', '');
1512
+ for (const ex of examples) {
1513
+ // Score likelihood based on system characteristics
1514
+ let likelihood = 'Medium';
1515
+ let impact = 'Medium';
1516
+ const exLower = ex.toLowerCase();
1517
+ if (exLower.includes('authentication') && hasAuth) {
1518
+ likelihood = 'High';
1519
+ impact = 'High';
1520
+ }
1521
+ if (exLower.includes('injection') && hasDb) {
1522
+ likelihood = 'High';
1523
+ impact = 'High';
1524
+ }
1525
+ if (exLower.includes('xss') && hasWeb) {
1526
+ likelihood = 'High';
1527
+ impact = 'Medium';
1528
+ }
1529
+ if (exLower.includes('ddos')) {
1530
+ likelihood = hasApi ? 'High' : 'Medium';
1531
+ impact = 'High';
1532
+ }
1533
+ if (exLower.includes('privilege') && hasAuth) {
1534
+ likelihood = 'Medium';
1535
+ impact = 'High';
1536
+ }
1537
+ if (exLower.includes('credential') && hasAuth) {
1538
+ likelihood = 'High';
1539
+ impact = 'High';
1540
+ }
1541
+ if (exLower.includes('payment') || exLower.includes('financial')) {
1542
+ impact = hasPayment ? 'High' : 'Low';
1543
+ }
1544
+ const priority = (likelihood === 'High' ? 3 : likelihood === 'Medium' ? 2 : 1) *
1545
+ (impact === 'High' ? 3 : impact === 'Medium' ? 2 : 1);
1546
+ lines.push(`- **${ex}** — Likelihood: ${likelihood}, Impact: ${impact}`);
1547
+ threats.push({
1548
+ category: stride.name,
1549
+ threat: ex,
1550
+ likelihood,
1551
+ impact,
1552
+ priority,
1553
+ mitigation: stride.mitigations[examples.indexOf(ex)] || stride.mitigations[0],
1554
+ });
1555
+ }
1556
+ lines.push('');
1557
+ lines.push('**Recommended Mitigations**:', '');
1558
+ for (const mit of stride.mitigations) {
1559
+ lines.push(`- ${mit}`);
1560
+ }
1561
+ lines.push('');
1562
+ }
1563
+ // ── Priority Matrix ──
1564
+ lines.push('## Priority Matrix', '');
1565
+ const sorted = threats.sort((a, b) => b.priority - a.priority);
1566
+ lines.push('| Priority | Category | Threat | Likelihood | Impact |', '|----------|----------|--------|------------|--------|');
1567
+ for (const t of sorted.slice(0, 15)) {
1568
+ const pLabel = t.priority >= 9 ? 'CRITICAL' : t.priority >= 6 ? 'HIGH' : t.priority >= 4 ? 'MEDIUM' : 'LOW';
1569
+ lines.push(`| ${pLabel} | ${t.category} | ${t.threat.slice(0, 50)} | ${t.likelihood} | ${t.impact} |`);
1570
+ }
1571
+ lines.push('');
1572
+ // ── AI Analysis ──
1573
+ const ollamaAvailable = await isOllamaAvailable();
1574
+ if (ollamaAvailable) {
1575
+ lines.push('## AI Threat Analysis', '');
1576
+ const prompt = [
1577
+ `You are a senior security architect performing a STRIDE threat model review.`,
1578
+ ``,
1579
+ `System: ${system}`,
1580
+ dataFlows.length > 0 ? `Data Flows: ${dataFlows.join('; ')}` : '',
1581
+ trustBoundaries.length > 0 ? `Trust Boundaries: ${trustBoundaries.join('; ')}` : '',
1582
+ assets.length > 0 ? `Critical Assets: ${assets.join('; ')}` : '',
1583
+ ``,
1584
+ `Based on this system, provide:`,
1585
+ `1. TOP 3 ATTACK SCENARIOS — realistic multi-step attack chains an adversary would use`,
1586
+ `2. BLIND SPOTS — threats that a standard STRIDE analysis might miss for this specific system`,
1587
+ `3. QUICK WINS — the 5 highest-impact mitigations that can be implemented this week`,
1588
+ `4. ARCHITECTURE RECOMMENDATIONS — structural changes to improve security posture`,
1589
+ ``,
1590
+ `Be specific to this system. Reference actual technologies mentioned.`,
1591
+ ].filter(Boolean).join('\n');
1592
+ const analysis = await ollamaGenerate(prompt);
1593
+ if (analysis) {
1594
+ lines.push(analysis, '');
1595
+ }
1596
+ else {
1597
+ lines.push('*Ollama analysis unavailable — model may not be loaded.*', '');
1598
+ }
1599
+ }
1600
+ else {
1601
+ lines.push('> *Start Ollama for AI-enhanced threat analysis specific to your system architecture.*', '');
1602
+ }
1603
+ // ── Summary ──
1604
+ const criticalCount = sorted.filter(t => t.priority >= 9).length;
1605
+ const highCount = sorted.filter(t => t.priority >= 6 && t.priority < 9).length;
1606
+ lines.push('## Summary', '');
1607
+ lines.push(`| Metric | Value |`, `|--------|-------|`);
1608
+ lines.push(`| Total threats identified | ${threats.length} |`);
1609
+ lines.push(`| Critical priority | ${criticalCount} |`);
1610
+ lines.push(`| High priority | ${highCount} |`);
1611
+ lines.push(`| STRIDE categories covered | ${STRIDE_CATEGORIES.length}/6 |`);
1612
+ lines.push('');
1613
+ lines.push('---', '');
1614
+ lines.push('*Generated by kbot threat-intel. Review with your security team and update as the system evolves.*');
1615
+ return lines.join('\n');
1616
+ },
1617
+ });
1618
+ }
1619
+ //# sourceMappingURL=threat-intel.js.map