blitzstrike 1.0.43 → 1.0.44

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 (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.js +1193 -1069
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30319,654 +30319,1115 @@ function computeGuidance(state, scopeAllowed) {
30319
30319
  };
30320
30320
  }
30321
30321
 
30322
- // src/orchestrator.ts
30323
- var _chainsCache = null;
30324
- function loadChains() {
30325
- if (_chainsCache)
30326
- return _chainsCache;
30327
- try {
30328
- const dir = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
30329
- const path = join6(dir, "chains.json");
30330
- const raw = JSON.parse(readFileSync6(path, "utf8"));
30331
- _chainsCache = raw.chains ?? [];
30332
- return _chainsCache;
30333
- } catch {
30334
- _chainsCache = [];
30335
- return [];
30336
- }
30322
+ // src/cvss.ts
30323
+ var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
30324
+ var AC_WEIGHT = { L: 0.77, H: 0.44 };
30325
+ var PR_WEIGHT = {
30326
+ U: { N: 0.85, L: 0.62, H: 0.27 },
30327
+ C: { N: 0.85, L: 0.68, H: 0.5 }
30328
+ };
30329
+ var UI_WEIGHT = { N: 0.85, R: 0.62 };
30330
+ var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
30331
+ function roundup(value) {
30332
+ return Math.ceil(value * 10) / 10;
30337
30333
  }
30338
- function scopeCheck(target, scope = "", mode = "bug-bounty") {
30339
- let result;
30340
- if (mode === "ctf" || mode === "reverse-engineering" || mode === "offensive") {
30341
- result = { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
30342
- } else if (!scope.trim()) {
30343
- result = { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
30334
+ function cvssBaseScore(input) {
30335
+ const AV = input.AV ?? "N";
30336
+ const AC = input.AC ?? "L";
30337
+ const PR = input.PR ?? "N";
30338
+ const UI = input.UI ?? "N";
30339
+ const S = input.S ?? "U";
30340
+ const C = input.C ?? "N";
30341
+ const I = input.I ?? "N";
30342
+ const A = input.A ?? "N";
30343
+ const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
30344
+ let impact;
30345
+ if (S === "U") {
30346
+ impact = 6.42 * iscBase;
30344
30347
  } else {
30345
- const inScope = [];
30346
- const outScope = [];
30347
- for (const raw of scope.split(/[,\n]/)) {
30348
- const line = raw.trim();
30349
- if (!line)
30350
- continue;
30351
- if (line.startsWith("-"))
30352
- outScope.push(line.slice(1).trim());
30353
- else
30354
- inScope.push(line);
30355
- }
30356
- let host = target;
30357
- host = host.replace(/^https?:\/\//, "");
30358
- host = host.split("/")[0].split(":")[0];
30359
- let decided = false;
30360
- result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
30361
- for (const exc of outScope) {
30362
- if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
30363
- result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
30364
- decided = true;
30365
- break;
30366
- }
30367
- }
30368
- if (!decided) {
30369
- for (const allowed of inScope) {
30370
- const a = allowed.replace(/\*/g, "");
30371
- if (!a)
30372
- continue;
30373
- if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
30374
- result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
30375
- decided = true;
30376
- break;
30377
- }
30378
- }
30379
- }
30348
+ impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
30380
30349
  }
30381
- recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
30382
- return result;
30350
+ const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
30351
+ if (impact <= 0)
30352
+ return 0;
30353
+ if (S === "U") {
30354
+ return roundup(Math.min(impact + exploitability, 10));
30355
+ }
30356
+ return roundup(Math.min(1.08 * (impact + exploitability), 10));
30383
30357
  }
30384
- function classifySink(sink) {
30385
- if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
30386
- return "upload_weak_validation";
30387
- if (/unserialize/.test(sink))
30388
- return "unserialize_user_input";
30389
- if (/(exec\(|system\(|shell_exec|passthru|popen|proc_open)/.test(sink))
30390
- return "command_injection";
30391
- if (/(include|require)/.test(sink))
30392
- return "lfi_unvalidated_include";
30393
- if (/extract\(/.test(sink))
30394
- return "extract_without_exstr_skip";
30395
- if (/(\$wpdb|->query)/.test(sink))
30396
- return "sql_injection";
30397
- if (/(wp_remote_get|wp_remote_post|file_get_contents)/.test(sink))
30398
- return "ssrf_unvalidated_url";
30399
- if (/(eval\(|assert\(|create_function)/.test(sink))
30400
- return "unsafe_template_render";
30401
- return "";
30358
+ function cvssVector(input) {
30359
+ const order = [
30360
+ ["AV", input.AV ?? "N"],
30361
+ ["AC", input.AC ?? "L"],
30362
+ ["PR", input.PR ?? "N"],
30363
+ ["UI", input.UI ?? "N"],
30364
+ ["S", input.S ?? "U"],
30365
+ ["C", input.C ?? "N"],
30366
+ ["I", input.I ?? "N"],
30367
+ ["A", input.A ?? "N"]
30368
+ ];
30369
+ return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
30402
30370
  }
30403
- function enrichScan(path, maxFiles = 2000) {
30404
- const expanded = path.replace(/^~/, process.env.HOME ?? "~");
30405
- const files = iterSourceFiles(expanded, maxFiles);
30406
- const hits = [];
30407
- const sinkKinds = new Set;
30408
- for (const p of files) {
30409
- const r = scanFile(p);
30410
- for (const e of r.endpoints) {
30411
- hits.push({ type: "endpoint", file: r.file, line: e.line, detail: e.hook });
30412
- }
30413
- for (const s of r.sinks) {
30414
- hits.push({ type: "sink", file: r.file, line: s.line, detail: `${s.sink} -> ${s.class}` });
30415
- const kind = classifySink(s.sink);
30416
- if (kind)
30417
- sinkKinds.add(kind);
30371
+ function cvssSeverity(score) {
30372
+ if (score === 0)
30373
+ return "none";
30374
+ if (score < 4)
30375
+ return "low";
30376
+ if (score < 7)
30377
+ return "medium";
30378
+ if (score < 9)
30379
+ return "high";
30380
+ return "critical";
30381
+ }
30382
+ function cvssAssess(input) {
30383
+ const score = cvssBaseScore(input);
30384
+ return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
30385
+ }
30386
+
30387
+ // src/dedup.ts
30388
+ function rootCauseSignature(finding) {
30389
+ const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
30390
+ const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
30391
+ const cwe = (finding.classification?.cwe ?? "").toUpperCase();
30392
+ return [sinkType, sourceType, cwe].filter(Boolean).join("|");
30393
+ }
30394
+ function dedupFindings(findings) {
30395
+ const groups = new Map;
30396
+ for (const f of findings) {
30397
+ const sig = rootCauseSignature(f);
30398
+ const existing = groups.get(sig);
30399
+ if (existing) {
30400
+ existing.count += 1;
30401
+ existing.findings.push(f);
30402
+ } else {
30403
+ groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
30418
30404
  }
30419
30405
  }
30420
- const chains = loadChains();
30421
- const matched = [];
30422
- for (const c of chains) {
30423
- if ((c.required_findings ?? []).some((rf) => sinkKinds.has(rf))) {
30424
- matched.push({ chain_id: c.id, name: c.name, severity: c.severity, steps: c.steps.length });
30425
- }
30406
+ return [...groups.values()];
30407
+ }
30408
+ function uniqueFindings(findings) {
30409
+ return dedupFindings(findings).map((g) => g.representative);
30410
+ }
30411
+
30412
+ // src/report.ts
30413
+ function summarize(findings) {
30414
+ const byStatus = {};
30415
+ const bySeverity = {};
30416
+ let evidenceArtifacts = 0;
30417
+ for (const f of findings) {
30418
+ byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
30419
+ const sev = f.classification?.severity ?? "informational";
30420
+ bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
30421
+ evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
30426
30422
  }
30423
+ const groups = dedupFindings(findings);
30427
30424
  return {
30428
- root: expanded,
30429
- files_scanned: files.length,
30430
- hits: hits.slice(0, 2000),
30431
- sink_signals: [...sinkKinds].sort(),
30432
- matched_chains: matched
30425
+ total_findings: findings.length,
30426
+ unique_root_causes: groups.length,
30427
+ by_status: byStatus,
30428
+ by_severity: bySeverity,
30429
+ confirmed: byStatus["confirmed"] ?? 0,
30430
+ false_positives: byStatus["false_positive"] ?? 0,
30431
+ evidence_artifacts: evidenceArtifacts
30433
30432
  };
30434
30433
  }
30435
- function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000, remember = true) {
30436
- const report = {
30437
- target,
30438
- mode,
30439
- tiers: {},
30440
- findings: [],
30441
- scope: null,
30442
- memory: { captured: 0 }
30443
- };
30444
- const isPath = (() => {
30445
- try {
30446
- return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
30447
- } catch {
30448
- return false;
30449
- }
30450
- })();
30451
- if (isPath) {
30452
- report.scope = { target, allowed: true, enforcement: "source-audit", reason: "local source path — no network scope gate" };
30453
- } else {
30454
- const sc = scopeCheck(target, scope, mode);
30455
- report.scope = sc;
30456
- if (!sc.allowed) {
30457
- report.status = "BLOCKED";
30458
- report.reason = sc.reason;
30459
- return report;
30460
- }
30461
- }
30462
- const blitz = enrichScan(target, maxFiles);
30463
- report.tiers = {
30464
- blitz: {
30465
- files_scanned: blitz.files_scanned,
30466
- hits: blitz.hits.length,
30467
- sink_signals: blitz.sink_signals,
30468
- matched_chains: blitz.matched_chains
30469
- }
30470
- };
30471
- const chains = loadChains();
30472
- const chainById = new Map(chains.map((c) => [c.id, c]));
30473
- const eagle = [];
30474
- for (const mc of blitz.matched_chains.slice(0, 10)) {
30475
- const chain = chainById.get(mc.chain_id);
30476
- if (!chain)
30477
- continue;
30478
- eagle.push({
30479
- chain_id: chain.id,
30480
- name: chain.name,
30481
- severity: chain.severity,
30482
- prerequisites: chain.required_findings,
30483
- recommended_tools: chain.tools ?? [],
30484
- tool_manuals: (chain.tools ?? []).map((t) => {
30485
- const m = findManual(t);
30486
- return m.found ? { tool: t, manual_name: m.name, preview: m.content.slice(0, 600) } : { tool: t, manual_name: null, preview: null };
30487
- }),
30488
- steps: chain.steps.map((s) => ({
30489
- order: s.order,
30490
- action: s.action,
30491
- tool_hint: s.tool_hint,
30492
- success_criteria: s.success_criteria,
30493
- invariant_check: s.invariant_check,
30494
- negative_control: s.negative_control
30495
- }))
30496
- });
30434
+ function severityLabel(sev) {
30435
+ const s = sev.toUpperCase();
30436
+ if (s === "CRITICAL")
30437
+ return "\uD83D\uDD34 CRITICAL";
30438
+ if (s === "HIGH")
30439
+ return "\uD83D\uDFE0 HIGH";
30440
+ if (s === "MEDIUM")
30441
+ return "\uD83D\uDFE1 MEDIUM";
30442
+ if (s === "LOW")
30443
+ return "\uD83D\uDD35 LOW";
30444
+ return "⚪ INFORMATIONAL";
30445
+ }
30446
+ function reportMarkdown(findings, opts = {}) {
30447
+ const summary = summarize(findings);
30448
+ const lines = [];
30449
+ lines.push(`# ${opts.title ?? "Blitz Strike — Security Assessment Report"}`);
30450
+ lines.push("");
30451
+ lines.push(`- Scope: ${opts.scope ?? "(unset)"}`);
30452
+ lines.push(`- Blitz Strike version: ${opts.version ?? "unknown"}`);
30453
+ if (opts.include_timestamp)
30454
+ lines.push(`- Generated: ${new Date().toISOString()}`);
30455
+ lines.push("");
30456
+ lines.push("## Summary");
30457
+ lines.push("");
30458
+ lines.push(`| Metric | Value |`);
30459
+ lines.push(`|--------|-------|`);
30460
+ lines.push(`| Total findings | ${summary.total_findings} |`);
30461
+ lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
30462
+ lines.push(`| Confirmed | ${summary.confirmed} |`);
30463
+ lines.push(`| False positives | ${summary.false_positives} |`);
30464
+ lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
30465
+ lines.push("");
30466
+ lines.push("### By status");
30467
+ lines.push("");
30468
+ for (const [status, n] of Object.entries(summary.by_status).sort()) {
30469
+ lines.push(`- ${status}: ${n}`);
30497
30470
  }
30498
- report.tiers = { ...report.tiers, eagle_eye: { traced_chains: eagle } };
30499
- const rank = { critical: 0, high: 1, medium: 2, low: 3 };
30500
- const matchedSorted = [...blitz.matched_chains].sort((a, b) => (rank[a.severity] ?? 99) - (rank[b.severity] ?? 99));
30501
- const findings = matchedSorted.map((f) => {
30502
- const chain = chainById.get(f.chain_id);
30503
- const severity = f.severity ?? "medium";
30504
- const finding = makeFinding({
30505
- title: f.name,
30506
- target: { type: "source", path: target },
30507
- severity,
30508
- source: { type: "static_sink_signal", name: f.chain_id },
30509
- sink: { type: f.chain_id, symbol: f.name },
30510
- chainId: f.chain_id,
30511
- chainName: f.name,
30512
- status: "hypothesis"
30513
- });
30514
- const ev = makeEvidence({
30515
- type: "sink_location",
30516
- description: `Static sink signal matched escalation chain '${f.chain_id}' (${severity}). Hypothesis only — not yet validated.`,
30517
- sink: { chain_id: f.chain_id, name: f.name, severity },
30518
- artifacts: [{ name: "chain_match", kind: "json", content: JSON.stringify({ chain_id: f.chain_id, severity, steps: chain?.steps.length ?? 0 }) }]
30519
- });
30520
- finding.evidence.push(ev);
30521
- return finding;
30522
- });
30523
- report.findings = findings.map((f) => ({
30524
- id: f.id,
30525
- status: f.status,
30526
- title: f.title,
30527
- severity: f.classification.severity,
30528
- confidence: f.confidence,
30529
- confidence_level: f.confidence_level,
30530
- chain_id: f.chain.id,
30531
- evidence_count: f.evidence.length
30532
- }));
30533
- report.status = "COMPLETE";
30534
- report.note = "All findings are HYPOTHESES until verified live with strike_verify. " + "Apply each chain's negative_control before reporting. Confidence reflects static evidence only.";
30535
- const executedState = "select_chain";
30536
- const scopeAllowed = report.scope?.allowed ?? false;
30537
- const plan = buildPlan(scopeAllowed, mode, executedState);
30538
- report.state = executedState;
30539
- report.state_description = stateDefinition(executedState).description;
30540
- report.plan = plan;
30541
- report.guidance = computeGuidance(executedState, scopeAllowed);
30542
- report.risk_policy = {
30543
- note: "High-risk actions (strike_verify, confirm_finding) require scope + are never run server-side automatically.",
30544
- gated: plan.filter((p) => !p.permitted).map((p) => p.tool)
30545
- };
30546
- if (remember) {
30547
- let captured = 0;
30548
- for (const mc of blitz.matched_chains) {
30549
- const chain = chainById.get(mc.chain_id);
30550
- const content = `Chain '${mc.chain_id}' (${mc.severity}) matched on target '${target}'. ` + `Detected sink signals: ${blitz.sink_signals.join(", ")}. ` + `Prerequisites: ${(chain?.required_findings ?? []).join(", ")}.`;
30551
- if (rememberIfAbsent(mc.chain_id, content, "pattern", [mc.severity, "chain"], "engagement", false)) {
30552
- captured++;
30553
- }
30471
+ lines.push("");
30472
+ lines.push("### By severity");
30473
+ lines.push("");
30474
+ for (const [sev, n] of Object.entries(summary.by_severity).sort()) {
30475
+ lines.push(`- ${sev}: ${n}`);
30476
+ }
30477
+ lines.push("");
30478
+ lines.push("## Findings");
30479
+ lines.push("");
30480
+ if (findings.length === 0) {
30481
+ lines.push("_No findings._");
30482
+ return lines.join(`
30483
+ `) + `
30484
+ `;
30485
+ }
30486
+ for (const f of findings) {
30487
+ lines.push(`### ${f.id} — ${f.title} [${severityLabel(f.classification?.severity ?? "informational")}]`);
30488
+ lines.push("");
30489
+ lines.push(`- **Status**: \`${f.status}\``);
30490
+ lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
30491
+ if (f.classification?.cwe)
30492
+ lines.push(`- **CWE**: ${f.classification.cwe}`);
30493
+ if (f.classification?.cvss_score != null)
30494
+ lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
30495
+ lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
30496
+ lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
30497
+ if (f.target?.host || f.target?.endpoint)
30498
+ lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
30499
+ lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
30500
+ if (f.remediation && Object.keys(f.remediation).length) {
30501
+ lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
30554
30502
  }
30555
- report.memory = { captured, deduped: blitz.matched_chains.length - captured };
30503
+ lines.push("");
30556
30504
  }
30557
- return report;
30505
+ return lines.join(`
30506
+ `) + `
30507
+ `;
30558
30508
  }
30559
- function listChains() {
30560
- const chains = loadChains();
30509
+ function reportJson(findings, opts = {}) {
30510
+ const summary = summarize(findings);
30511
+ const payload = {
30512
+ title: opts.title ?? "Blitz Strike — Security Assessment Report",
30513
+ scope: opts.scope ?? null,
30514
+ version: opts.version ?? null,
30515
+ summary,
30516
+ findings,
30517
+ integrity: sha256(JSON.stringify({ summary, findings }))
30518
+ };
30519
+ return JSON.stringify(payload, null, 2);
30520
+ }
30521
+
30522
+ // src/sarif.ts
30523
+ var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
30524
+ function sarifLevel(severity) {
30525
+ const s = severity.toLowerCase();
30526
+ if (s === "critical" || s === "high")
30527
+ return "error";
30528
+ if (s === "medium")
30529
+ return "warning";
30530
+ return "note";
30531
+ }
30532
+ function toSarif(findings, opts = {}) {
30533
+ const tool = opts.tool ?? "Blitz Strike";
30534
+ const uri = opts.uri ?? "https://github.com/shinthink/blitzstrike";
30535
+ const ruleIds = [...new Set(findings.map((f) => f.ruleId))];
30536
+ const rules = ruleIds.map((id) => ({
30537
+ id,
30538
+ name: id,
30539
+ shortDescription: { text: id }
30540
+ }));
30541
+ const results = findings.map((f) => ({
30542
+ ruleId: f.ruleId,
30543
+ level: f.level,
30544
+ message: { text: f.message },
30545
+ locations: [
30546
+ {
30547
+ physicalLocation: {
30548
+ artifactLocation: { uri: f.file },
30549
+ region: { startLine: Math.max(1, f.line) }
30550
+ }
30551
+ }
30552
+ ]
30553
+ }));
30561
30554
  return {
30562
- total: chains.length,
30563
- chains: chains.map((c) => ({
30564
- id: c.id,
30565
- name: c.name,
30566
- severity: c.severity,
30567
- steps: c.steps.length,
30568
- triggers: c.triggers
30569
- }))
30555
+ version: "2.1.0",
30556
+ $schema: SARIF_SCHEMA,
30557
+ runs: [
30558
+ {
30559
+ tool: {
30560
+ driver: {
30561
+ name: tool,
30562
+ version: VERSION,
30563
+ informationUri: uri,
30564
+ rules
30565
+ }
30566
+ },
30567
+ results
30568
+ }
30569
+ ]
30570
30570
  };
30571
30571
  }
30572
30572
 
30573
- // src/catalog.ts
30574
- import { readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
30575
- import { join as join7 } from "node:path";
30576
- import { fileURLToPath as fileURLToPath4 } from "node:url";
30577
- import { spawnSync } from "node:child_process";
30578
- var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
30579
- var _toolsCache = null;
30580
- var _skillsCache = null;
30581
- function loadTools() {
30582
- if (_toolsCache)
30583
- return _toolsCache;
30573
+ // src/strike.ts
30574
+ var DEFAULT_TIMEOUT_MS = 15000;
30575
+ async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
30576
+ const controller = new AbortController;
30577
+ const t = setTimeout(() => controller.abort(), timeoutMs);
30584
30578
  try {
30585
- const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
30586
- _toolsCache = raw.tools ?? [];
30587
- } catch {
30588
- _toolsCache = [];
30579
+ const res = await fetch(url, {
30580
+ method,
30581
+ body: method === "POST" ? data : undefined,
30582
+ headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
30583
+ signal: controller.signal,
30584
+ redirect: "follow"
30585
+ });
30586
+ const body = await res.text();
30587
+ const hdrs = {};
30588
+ res.headers.forEach((v, k) => {
30589
+ hdrs[k] = v;
30590
+ });
30591
+ return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
30592
+ } catch (e) {
30593
+ return { status: 0, body: String(e), headers: {} };
30594
+ } finally {
30595
+ clearTimeout(t);
30589
30596
  }
30590
- return _toolsCache;
30591
30597
  }
30592
- function loadSkills() {
30593
- if (_skillsCache)
30594
- return _skillsCache;
30595
- const dir = join7(ROOT2, "skills");
30596
- const out = [];
30597
- try {
30598
- for (const f of readdirSync2(dir)) {
30599
- if (!f.endsWith(".md"))
30600
- continue;
30601
- const p = join7(dir, f);
30602
- const text = readFileSync7(p, "utf8");
30603
- const descMatch = text.match(/^description:\s*(.+)$/m);
30604
- out.push({
30605
- name: f.replace(/\.md$/, ""),
30606
- file: f,
30607
- description: (descMatch?.[1] ?? "").trim().slice(0, 200),
30608
- size: text.length
30609
- });
30610
- }
30611
- } catch {}
30612
- _skillsCache = out;
30613
- return out;
30598
+ function defaultMarker() {
30599
+ return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
30614
30600
  }
30615
- function toolLookup(name) {
30616
- const tools = loadTools();
30617
- const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
30618
- if (!t) {
30619
- return {
30620
- found: false,
30621
- query: name,
30622
- suggestions: tools.filter((x) => x.name.includes(name.toLowerCase()) || name.toLowerCase().includes(x.name)).slice(0, 5).map((x) => x.name)
30623
- };
30624
- }
30625
- const manual = manualForTool(t.name);
30626
- const result = { found: true, tool: t };
30627
- if (manual && manual.content) {
30628
- result.manual = {
30629
- name: manual.name,
30630
- category: manual.category,
30631
- preview: manual.content.slice(0, 1500),
30632
- full_length: manual.content.length
30633
- };
30634
- } else {
30635
- result.manual = null;
30636
- }
30637
- return result;
30601
+ function inject(target, value, param) {
30602
+ const placeholder = "{{MARKER}}";
30603
+ if (target.includes(placeholder))
30604
+ return target.split(placeholder).join(value);
30605
+ if (target.includes("?"))
30606
+ return `${target}&${param}=${encodeURIComponent(value)}`;
30607
+ return `${target}?${param}=${encodeURIComponent(value)}`;
30638
30608
  }
30639
- function listTools() {
30640
- const tools = loadTools();
30641
- const byCat = {};
30642
- for (const t of tools) {
30643
- (byCat[t.category] ??= []).push(t.name);
30609
+ async function strikeVerify(input) {
30610
+ const method = input.method ?? "GET";
30611
+ const param = input.param ?? "q";
30612
+ const marker = input.marker ?? defaultMarker();
30613
+ const control = input.control ?? defaultMarker();
30614
+ const baseUrl = input.url;
30615
+ const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
30616
+ const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
30617
+ const markerTarget = inject(baseUrl, marker, param);
30618
+ const controlTarget = inject(baseUrl, control, param);
30619
+ const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
30620
+ const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
30621
+ const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
30622
+ if (baseline.status === 0 || markerResp.status === 0) {
30623
+ return {
30624
+ status: "blocked",
30625
+ marker_reflected: false,
30626
+ control_reflected: false,
30627
+ baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
30628
+ marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
30629
+ control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
30630
+ reason: "request failed or target unreachable — cannot validate",
30631
+ evidence: []
30632
+ };
30644
30633
  }
30645
- return { total: tools.length, categories: byCat, tools: tools.map((t) => ({ name: t.name, category: t.category, description: t.description })) };
30646
- }
30647
- function skillLookup(topic) {
30648
- const skills = loadSkills();
30649
- const q = topic.toLowerCase();
30650
- const scored = skills.map((s) => {
30651
- const hay = `${s.name} ${s.description}`.toLowerCase();
30652
- const nameHit = s.name.includes(q);
30653
- const descHit = s.description.includes(q);
30654
- const wordHit = q.split(/\s+/).filter((w) => w && hay.includes(w)).length;
30655
- return { s, score: (nameHit ? 10 : 0) + (descHit ? 5 : 0) + wordHit * 2 };
30656
- }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
30657
- if (scored.length === 0) {
30658
- return { found: false, query: topic, suggestions: skills.map((s) => s.name).slice(0, 10) };
30634
+ const markerReflected = markerResp.body.includes(marker);
30635
+ const controlReflected = controlResp.body.includes(control);
30636
+ let status;
30637
+ let reason;
30638
+ if (markerReflected && !controlReflected) {
30639
+ status = "confirmed";
30640
+ reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
30641
+ } else if (markerReflected && controlReflected) {
30642
+ status = "false_positive";
30643
+ reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
30644
+ } else if (!markerReflected && !controlReflected) {
30645
+ status = "unconfirmed";
30646
+ reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
30647
+ } else {
30648
+ status = "unconfirmed";
30649
+ reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
30659
30650
  }
30651
+ const evidence = [
30652
+ {
30653
+ type: "baseline_comparison",
30654
+ description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
30655
+ artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
30656
+ ${baseline.body}` }]
30657
+ },
30658
+ {
30659
+ type: "validation_result",
30660
+ description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
30661
+ artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
30662
+ ${markerResp.body}` }]
30663
+ },
30664
+ {
30665
+ type: "negative_control",
30666
+ description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
30667
+ artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
30668
+ ${controlResp.body}` }]
30669
+ }
30670
+ ];
30660
30671
  return {
30661
- found: true,
30662
- matches: scored.slice(0, 5).map((x) => ({ name: x.s.name, description: x.s.description, file: x.s.file }))
30672
+ status,
30673
+ marker_reflected: markerReflected,
30674
+ control_reflected: controlReflected,
30675
+ baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
30676
+ marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
30677
+ control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
30678
+ reason,
30679
+ evidence
30663
30680
  };
30664
30681
  }
30665
- function listSkills() {
30666
- const skills = loadSkills();
30667
- return { total: skills.length, skills: skills.map((s) => ({ name: s.name, description: s.description })) };
30668
- }
30669
- function readSkill(name) {
30670
- const skills = loadSkills();
30671
- const s = skills.find((x) => x.name.toLowerCase() === name.toLowerCase());
30672
- if (!s)
30673
- return { found: false, query: name };
30674
- try {
30675
- const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
30676
- return { found: true, name: s.name, content: text.slice(0, 12000) };
30677
- } catch {
30678
- return { found: false, query: name, error: "unreadable" };
30679
- }
30680
- }
30681
- function ensureTool(name) {
30682
- const tools = loadTools();
30683
- const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
30684
- if (!t) {
30685
- return { found: false, query: name, suggestions: tools.slice(0, 5).map((x) => x.name) };
30686
- }
30687
- const check = spawnSync(t.check_installed.command, { shell: true, timeout: 15000 });
30688
- if (check.status !== 127 && check.error === undefined) {
30689
- return { name: t.name, installed: true, action: "none", note: "already installed" };
30682
+ function resolveFinding(finding, verdict) {
30683
+ let f = finding;
30684
+ if (f.status === "hypothesis") {
30685
+ f = transition(f, "validating");
30690
30686
  }
30691
- const platform = process.platform;
30692
- const cmd = t.install[platform] ?? t.install.linux;
30693
- if (!cmd) {
30694
- return { name: t.name, installed: false, action: "none", note: `no install command for ${platform}` };
30687
+ switch (verdict.status) {
30688
+ case "confirmed":
30689
+ return confirmFinding(f, {
30690
+ evidence: verdict.evidence,
30691
+ negative_control: true,
30692
+ baseline: true
30693
+ });
30694
+ case "false_positive":
30695
+ return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
30696
+ case "blocked":
30697
+ return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
30698
+ case "unconfirmed":
30699
+ case "likely":
30700
+ default: {
30701
+ return {
30702
+ ...f,
30703
+ validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
30704
+ remediation: { ...f.remediation, note: verdict.reason },
30705
+ timestamps: { ...f.timestamps, updated: new Date().toISOString() }
30706
+ };
30707
+ }
30695
30708
  }
30696
- const r = spawnSync(cmd, { shell: true, timeout: 120000 });
30697
- const ok = r.status === 0;
30698
- return {
30699
- name: t.name,
30700
- installed: ok,
30701
- action: "install",
30702
- command: cmd,
30703
- output: (r.stdout?.toString() ?? "").slice(-400) || (r.stderr?.toString() ?? "").slice(-400)
30704
- };
30705
30709
  }
30706
30710
 
30707
- // src/intel.ts
30708
- import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
30709
- import { join as join8 } from "node:path";
30710
- import { fileURLToPath as fileURLToPath5 } from "node:url";
30711
- import { readdirSync as readdirSync3 } from "node:fs";
30712
- import { homedir as homedir3 } from "node:os";
30713
- var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
30714
- var INTEL = join8(ROOT3, "intelligence");
30715
- function loadJson(name) {
30716
- const p = join8(INTEL, `${name}.json`);
30717
- if (!existsSync5(p))
30718
- return null;
30711
+ // src/orchestrator.ts
30712
+ var _chainsCache = null;
30713
+ function loadChains() {
30714
+ if (_chainsCache)
30715
+ return _chainsCache;
30719
30716
  try {
30720
- return JSON.parse(readFileSync8(p, "utf8"));
30717
+ const dir = join6(fileURLToPath3(new URL(".", import.meta.url)), "..");
30718
+ const path = join6(dir, "chains.json");
30719
+ const raw = JSON.parse(readFileSync6(path, "utf8"));
30720
+ _chainsCache = raw.chains ?? [];
30721
+ return _chainsCache;
30721
30722
  } catch {
30722
- return null;
30723
+ _chainsCache = [];
30724
+ return [];
30723
30725
  }
30724
30726
  }
30725
- function detectWaf(headers, body = "") {
30726
- const sigs = loadJson("waf_signatures");
30727
- if (!sigs)
30728
- return { detected: false, wafs: [], error: "waf_signatures.json not loaded" };
30729
- const headerSigs = sigs.header_signatures ?? [];
30730
- const bodySigs = sigs.body_signatures ?? [];
30731
- const matches = [];
30732
- const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
30733
- for (const s of headerSigs) {
30734
- const h = (s.header ?? "").toLowerCase();
30735
- const pattern = (s.pattern ?? "").toLowerCase();
30736
- if (!h)
30737
- continue;
30738
- if (pattern) {
30739
- const val = (headers[h] ?? headers[s.header ?? ""] ?? "").toLowerCase();
30740
- if (val.includes(pattern)) {
30741
- matches.push({ waf: s.waf, header: s.header, pattern: s.pattern, confidence: s.confidence, via: "header-value" });
30742
- }
30743
- } else {
30744
- if (headerKeys.includes(h)) {
30745
- matches.push({ waf: s.waf, header: s.header, confidence: s.confidence, via: "header-name" });
30727
+ function scopeCheck(target, scope = "", mode = "bug-bounty") {
30728
+ let result;
30729
+ if (mode === "ctf" || mode === "reverse-engineering" || mode === "offensive") {
30730
+ result = { target, allowed: true, enforcement: "off", reason: `mode=${mode} disables scope enforcement` };
30731
+ } else if (!scope.trim()) {
30732
+ result = { target, allowed: false, enforcement: "strict", reason: "no scope provided; refusing active testing" };
30733
+ } else {
30734
+ const inScope = [];
30735
+ const outScope = [];
30736
+ for (const raw of scope.split(/[,\n]/)) {
30737
+ const line = raw.trim();
30738
+ if (!line)
30739
+ continue;
30740
+ if (line.startsWith("-"))
30741
+ outScope.push(line.slice(1).trim());
30742
+ else
30743
+ inScope.push(line);
30744
+ }
30745
+ let host = target;
30746
+ host = host.replace(/^https?:\/\//, "");
30747
+ host = host.split("/")[0].split(":")[0];
30748
+ let decided = false;
30749
+ result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' not in scope` };
30750
+ for (const exc of outScope) {
30751
+ if (exc && (host.includes(exc) || host.endsWith("." + exc.replace(/^\./, "")))) {
30752
+ result = { target, allowed: false, enforcement: "strict", reason: `target '${host}' is explicitly out of scope (${exc})` };
30753
+ decided = true;
30754
+ break;
30746
30755
  }
30747
30756
  }
30748
- }
30749
- for (const s of bodySigs) {
30750
- const pattern = (s.pattern ?? "").toLowerCase();
30751
- if (pattern && body.toLowerCase().includes(pattern)) {
30752
- matches.push({ waf: s.waf, pattern: s.pattern, confidence: s.confidence, via: "body" });
30757
+ if (!decided) {
30758
+ for (const allowed of inScope) {
30759
+ const a = allowed.replace(/\*/g, "");
30760
+ if (!a)
30761
+ continue;
30762
+ if (host.includes(a) || host.endsWith("." + a.replace(/^\./, ""))) {
30763
+ result = { target, allowed: true, enforcement: "strict", reason: `matched in-scope pattern '${allowed}'` };
30764
+ decided = true;
30765
+ break;
30766
+ }
30767
+ }
30753
30768
  }
30754
30769
  }
30755
- const byWaf = new Map;
30756
- for (const m of matches) {
30757
- const key = m.waf;
30758
- const prev = byWaf.get(key);
30759
- if (!prev || m.confidence > prev.confidence)
30760
- byWaf.set(key, m);
30770
+ recordAudit("scope_checked", { tool: "scope", target, result: result.allowed ? "allowed" : "denied" });
30771
+ return result;
30772
+ }
30773
+ function classifySink(sink) {
30774
+ if (/(move_uploaded_file|file_put_contents|fwrite)/.test(sink))
30775
+ return "upload_weak_validation";
30776
+ if (/unserialize/.test(sink))
30777
+ return "unserialize_user_input";
30778
+ if (/(exec\(|system\(|shell_exec|passthru|popen|proc_open)/.test(sink))
30779
+ return "command_injection";
30780
+ if (/(include|require)/.test(sink))
30781
+ return "lfi_unvalidated_include";
30782
+ if (/extract\(/.test(sink))
30783
+ return "extract_without_exstr_skip";
30784
+ if (/(\$wpdb|->query)/.test(sink))
30785
+ return "sql_injection";
30786
+ if (/(wp_remote_get|wp_remote_post|file_get_contents)/.test(sink))
30787
+ return "ssrf_unvalidated_url";
30788
+ if (/(eval\(|assert\(|create_function)/.test(sink))
30789
+ return "unsafe_template_render";
30790
+ return "";
30791
+ }
30792
+ function enrichScan(path, maxFiles = 2000) {
30793
+ const expanded = path.replace(/^~/, process.env.HOME ?? "~");
30794
+ const files = iterSourceFiles(expanded, maxFiles);
30795
+ const hits = [];
30796
+ const sinkKinds = new Set;
30797
+ for (const p of files) {
30798
+ const r = scanFile(p);
30799
+ for (const e of r.endpoints) {
30800
+ hits.push({ type: "endpoint", file: r.file, line: e.line, detail: e.hook });
30801
+ }
30802
+ for (const s of r.sinks) {
30803
+ hits.push({ type: "sink", file: r.file, line: s.line, detail: `${s.sink} -> ${s.class}` });
30804
+ const kind = classifySink(s.sink);
30805
+ if (kind)
30806
+ sinkKinds.add(kind);
30807
+ }
30808
+ }
30809
+ const chains = loadChains();
30810
+ const matched = [];
30811
+ for (const c of chains) {
30812
+ if ((c.required_findings ?? []).some((rf) => sinkKinds.has(rf))) {
30813
+ matched.push({ chain_id: c.id, name: c.name, severity: c.severity, steps: c.steps.length });
30814
+ }
30761
30815
  }
30762
- const detected = [...byWaf.values()].sort((a, b) => b.confidence - a.confidence);
30763
30816
  return {
30764
- detected: detected.length > 0,
30765
- wafs: detected,
30766
- block_status_codes: sigs.block_status_codes ?? []
30817
+ root: expanded,
30818
+ files_scanned: files.length,
30819
+ hits: hits.slice(0, 2000),
30820
+ sink_signals: [...sinkKinds].sort(),
30821
+ matched_chains: matched
30767
30822
  };
30768
30823
  }
30769
- function techCorrelation(tech) {
30770
- const data = loadJson("tech_correlations");
30771
- if (!data)
30772
- return { found: false, error: "tech_correlations.json not loaded" };
30773
- const key = tech.toLowerCase();
30774
- const entry = data[key];
30775
- if (!entry) {
30776
- const matches = Object.keys(data).filter((k) => k.includes(key) || key.includes(k)).slice(0, 5);
30777
- return { found: false, tech, suggestions: matches };
30824
+ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000, remember = true) {
30825
+ const report = {
30826
+ target,
30827
+ mode,
30828
+ tiers: {},
30829
+ findings: [],
30830
+ scope: null,
30831
+ memory: { captured: 0 }
30832
+ };
30833
+ const isPath = (() => {
30834
+ try {
30835
+ return existsSync4(target) && (statSync2(target).isDirectory() || statSync2(target).isFile());
30836
+ } catch {
30837
+ return false;
30838
+ }
30839
+ })();
30840
+ if (isPath) {
30841
+ report.scope = { target, allowed: true, enforcement: "source-audit", reason: "local source path — no network scope gate" };
30842
+ } else {
30843
+ const sc = scopeCheck(target, scope, mode);
30844
+ report.scope = sc;
30845
+ if (!sc.allowed) {
30846
+ report.status = "BLOCKED";
30847
+ report.reason = sc.reason;
30848
+ return report;
30849
+ }
30778
30850
  }
30779
- return { found: true, tech: key, vulns: entry.vulns ?? [], paths: entry.paths ?? [], detection: entry.detection ?? { vulns: entry.vulns, paths: entry.paths } };
30780
- }
30781
- function cveCorrelation(cveId) {
30782
- const data = loadJson("cve_correlations");
30783
- if (!data)
30784
- return { error: "cve_correlations.json not loaded" };
30785
- const key = cveId.toUpperCase();
30786
- const entry = data[key];
30787
- if (!entry)
30788
- return { found: false, cve: key, total_known: Object.keys(data).length };
30789
- return { found: true, cve: key, ...entry };
30790
- }
30791
- function portCorrelation(port) {
30792
- const data = loadJson("port_correlations");
30793
- if (!data)
30794
- return { error: "port_correlations.json not loaded" };
30795
- const key = String(port);
30796
- const entry = data[key];
30797
- if (!entry)
30798
- return { found: false, port: key, total_known: Object.keys(data).length };
30799
- return { found: true, port: key, ...entry };
30800
- }
30801
- function fuzzerPayloads(category) {
30802
- const data = loadJson("fuzzer_data");
30803
- if (!data)
30804
- return { error: "fuzzer_data.json not loaded" };
30805
- if (category) {
30806
- const c = data[category] ?? data[category.toUpperCase()];
30807
- return { category, found: Boolean(c), data: c ?? null };
30851
+ const blitz = enrichScan(target, maxFiles);
30852
+ report.tiers = {
30853
+ blitz: {
30854
+ files_scanned: blitz.files_scanned,
30855
+ hits: blitz.hits.length,
30856
+ sink_signals: blitz.sink_signals,
30857
+ matched_chains: blitz.matched_chains
30858
+ }
30859
+ };
30860
+ const chains = loadChains();
30861
+ const chainById = new Map(chains.map((c) => [c.id, c]));
30862
+ const eagle = [];
30863
+ for (const mc of blitz.matched_chains.slice(0, 10)) {
30864
+ const chain = chainById.get(mc.chain_id);
30865
+ if (!chain)
30866
+ continue;
30867
+ eagle.push({
30868
+ chain_id: chain.id,
30869
+ name: chain.name,
30870
+ severity: chain.severity,
30871
+ prerequisites: chain.required_findings,
30872
+ recommended_tools: chain.tools ?? [],
30873
+ tool_manuals: (chain.tools ?? []).map((t) => {
30874
+ const m = findManual(t);
30875
+ return m.found ? { tool: t, manual_name: m.name, preview: m.content.slice(0, 600) } : { tool: t, manual_name: null, preview: null };
30876
+ }),
30877
+ steps: chain.steps.map((s) => ({
30878
+ order: s.order,
30879
+ action: s.action,
30880
+ tool_hint: s.tool_hint,
30881
+ success_criteria: s.success_criteria,
30882
+ invariant_check: s.invariant_check,
30883
+ negative_control: s.negative_control
30884
+ }))
30885
+ });
30808
30886
  }
30809
- return { categories: Object.keys(data), fuzz_points: data.FUZZ_POINTS ?? null, payloads: data.FUZZ_PAYLOADS ?? null };
30810
- }
30811
- function intelSummary() {
30812
- const waf = loadJson("waf_signatures");
30813
- const tech = loadJson("tech_correlations");
30814
- const cve = loadJson("cve_correlations");
30815
- const port = loadJson("port_correlations");
30816
- const fuzz = loadJson("fuzzer_data");
30817
- const av = loadJson("attack_vectors");
30818
- return {
30819
- waf_signatures: waf ? (waf.header_signatures?.length ?? 0) + (waf.body_signatures?.length ?? 0) : 0,
30820
- tech_correlations: tech ? Object.keys(tech).length : 0,
30821
- cve_correlations: cve ? Object.keys(cve).length : 0,
30822
- port_correlations: port ? Object.keys(port).length : 0,
30823
- fuzzer_categories: fuzz ? Object.keys(fuzz).length : 0,
30824
- payload_categories: listPayloadCategories().length,
30825
- nuclei_templates: countTemplates(),
30826
- attack_vector_categories: av ? av.categories.length : 0,
30827
- attack_vectors: av ? av.categories.reduce((n, c) => n + c.vectors.length, 0) : 0
30887
+ report.tiers = { ...report.tiers, eagle_eye: { traced_chains: eagle } };
30888
+ const rank = { critical: 0, high: 1, medium: 2, low: 3 };
30889
+ const matchedSorted = [...blitz.matched_chains].sort((a, b) => (rank[a.severity] ?? 99) - (rank[b.severity] ?? 99));
30890
+ const findings = matchedSorted.map((f) => {
30891
+ const chain = chainById.get(f.chain_id);
30892
+ const severity = f.severity ?? "medium";
30893
+ const finding = makeFinding({
30894
+ title: f.name,
30895
+ target: { type: "source", path: target },
30896
+ severity,
30897
+ source: { type: "static_sink_signal", name: f.chain_id },
30898
+ sink: { type: f.chain_id, symbol: f.name },
30899
+ chainId: f.chain_id,
30900
+ chainName: f.name,
30901
+ status: "hypothesis"
30902
+ });
30903
+ const ev = makeEvidence({
30904
+ type: "sink_location",
30905
+ description: `Static sink signal matched escalation chain '${f.chain_id}' (${severity}). Hypothesis only — not yet validated.`,
30906
+ sink: { chain_id: f.chain_id, name: f.name, severity },
30907
+ artifacts: [{ name: "chain_match", kind: "json", content: JSON.stringify({ chain_id: f.chain_id, severity, steps: chain?.steps.length ?? 0 }) }]
30908
+ });
30909
+ finding.evidence.push(ev);
30910
+ return finding;
30911
+ });
30912
+ report.findings = findings.map((f) => ({
30913
+ id: f.id,
30914
+ status: f.status,
30915
+ title: f.title,
30916
+ severity: f.classification.severity,
30917
+ confidence: f.confidence,
30918
+ confidence_level: f.confidence_level,
30919
+ chain_id: f.chain.id,
30920
+ evidence_count: f.evidence.length
30921
+ }));
30922
+ const reportTitle = `Blitz Strike — Engagement Report (${target})`;
30923
+ const reportMarkdownText = reportMarkdown(findings, { title: reportTitle, scope: target, version: VERSION });
30924
+ const reportJsonText = reportJson(findings, { title: reportTitle, scope: target, version: VERSION });
30925
+ const sarifDoc = toSarif(findings.map((f) => ({
30926
+ ruleId: f.chain.id || f.classification.severity,
30927
+ level: sarifLevel(f.classification.severity),
30928
+ message: f.title,
30929
+ file: typeof f.target.path === "string" ? f.target.path : target,
30930
+ line: 1
30931
+ })));
30932
+ report.report = {
30933
+ summary: JSON.parse(reportJsonText).summary ?? null,
30934
+ markdown: reportMarkdownText,
30935
+ json: reportJsonText,
30936
+ sarif: sarifDoc
30937
+ };
30938
+ report.status = "COMPLETE";
30939
+ report.note = "All findings are HYPOTHESES until verified live with strike_verify. " + "Apply each chain's negative_control before reporting. Confidence reflects static evidence only.";
30940
+ const executedState = "select_chain";
30941
+ const scopeAllowed = report.scope?.allowed ?? false;
30942
+ const plan = buildPlan(scopeAllowed, mode, executedState);
30943
+ report.state = executedState;
30944
+ report.state_description = stateDefinition(executedState).description;
30945
+ report.plan = plan;
30946
+ report.guidance = computeGuidance(executedState, scopeAllowed);
30947
+ report.risk_policy = {
30948
+ note: "High-risk actions (strike_verify, confirm_finding) require scope + are never run server-side automatically.",
30949
+ gated: plan.filter((p) => !p.permitted).map((p) => p.tool)
30828
30950
  };
30951
+ if (remember) {
30952
+ let captured = 0;
30953
+ for (const mc of blitz.matched_chains) {
30954
+ const chain = chainById.get(mc.chain_id);
30955
+ const content = `Chain '${mc.chain_id}' (${mc.severity}) matched on target '${target}'. ` + `Detected sink signals: ${blitz.sink_signals.join(", ")}. ` + `Prerequisites: ${(chain?.required_findings ?? []).join(", ")}.`;
30956
+ if (rememberIfAbsent(mc.chain_id, content, "pattern", [mc.severity, "chain"], "engagement", false)) {
30957
+ captured++;
30958
+ }
30959
+ }
30960
+ report.memory = { captured, deduped: blitz.matched_chains.length - captured };
30961
+ }
30962
+ return report;
30829
30963
  }
30830
- function listAttackVectors() {
30831
- const data = loadJson("attack_vectors");
30832
- if (!data)
30833
- return { error: "attack_vectors.json not loaded" };
30964
+ function listChains() {
30965
+ const chains = loadChains();
30834
30966
  return {
30835
- total_categories: data.categories.length,
30836
- total_vectors: data.categories.reduce((n, c) => n + c.vectors.length, 0),
30837
- categories: data.categories.map((c) => ({ id: c.id, title: c.title, count: c.vectors.length }))
30967
+ total: chains.length,
30968
+ chains: chains.map((c) => ({
30969
+ id: c.id,
30970
+ name: c.name,
30971
+ severity: c.severity,
30972
+ steps: c.steps.length,
30973
+ triggers: c.triggers
30974
+ }))
30838
30975
  };
30839
30976
  }
30840
- function attackVectors(category) {
30841
- const data = loadJson("attack_vectors");
30842
- if (!data)
30843
- return { error: "attack_vectors.json not loaded" };
30844
- const q = category.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
30845
- const found = data.categories.find((c) => c.id === q || c.id.includes(q) || c.title.toLowerCase().includes(category.toLowerCase()));
30846
- if (!found) {
30847
- const matches = data.categories.filter((c) => c.title.toLowerCase().includes(category.toLowerCase()) || c.id.includes(q)).slice(0, 5).map((c) => c.id);
30848
- return { found: false, query: category, suggestions: matches };
30977
+ var LIVE_SWEEP_PARAMS = ["q", "search", "query", "s", "id", "url", "redirect", "name", "page"];
30978
+ async function runLiveEngagement(url, scope = "", mode = "bug-bounty") {
30979
+ const report = { target: url, mode, findings: [], scope: null, status: "" };
30980
+ const sc = scopeCheck(url, scope, mode);
30981
+ report.scope = sc;
30982
+ if (!sc.allowed) {
30983
+ report.status = "BLOCKED";
30984
+ report.reason = sc.reason;
30985
+ return report;
30849
30986
  }
30850
- return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
30851
- }
30852
- var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
30853
- var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
30854
- var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".blitzstrike", "data");
30855
- function payloadsDir() {
30856
- if (existsSync5(PACKAGE_PAYLOADS))
30857
- return PACKAGE_PAYLOADS;
30858
- return join8(DATA_ROOT, "payloads");
30859
- }
30860
- function templatesDir() {
30861
- if (existsSync5(PACKAGE_TEMPLATES))
30862
- return PACKAGE_TEMPLATES;
30863
- return join8(DATA_ROOT, "templates");
30987
+ const findings = [];
30988
+ for (const param of LIVE_SWEEP_PARAMS) {
30989
+ const verdict = await strikeVerify({ url, param });
30990
+ const finding = makeFinding({
30991
+ title: `Reflected input in parameter '${param}'`,
30992
+ target: { type: "web", host: url, endpoint: `?${param}=` },
30993
+ severity: "medium",
30994
+ cwe: "CWE-79",
30995
+ cweName: "Cross-site Scripting",
30996
+ source: { type: "request_parameter", name: param },
30997
+ sink: { type: "reflected_input", symbol: param },
30998
+ chainId: "reflected_input",
30999
+ chainName: "Reflected input",
31000
+ status: "hypothesis"
31001
+ });
31002
+ findings.push(resolveFinding(finding, verdict));
31003
+ }
31004
+ report.findings = findings.map((f) => ({
31005
+ id: f.id,
31006
+ status: f.status,
31007
+ title: f.title,
31008
+ severity: f.classification.severity,
31009
+ confidence: f.confidence,
31010
+ confidence_level: f.confidence_level,
31011
+ chain_id: f.chain.id,
31012
+ validation: f.validation
31013
+ }));
31014
+ const title = `Blitz Strike — Live Engagement (${url})`;
31015
+ const markdown = reportMarkdown(findings, { title, scope: url, version: VERSION });
31016
+ const json = reportJson(findings, { title, scope: url, version: VERSION });
31017
+ report.report = {
31018
+ summary: JSON.parse(json).summary ?? null,
31019
+ markdown,
31020
+ json,
31021
+ sarif: toSarif(findings.map((f) => ({
31022
+ ruleId: f.chain.id || f.classification.cwe || f.classification.severity,
31023
+ level: sarifLevel(f.classification.severity),
31024
+ message: f.title,
31025
+ file: url,
31026
+ line: 1
31027
+ })))
31028
+ };
31029
+ report.status = "COMPLETE";
31030
+ report.note = "Autonomous live marker sweep. Each parameter was validated with a marker + " + "negative control; confirmed reflections are real findings, everything else " + "is a hypothesis or false positive. Re-run with explicit strike_verify for " + "deeper per-endpoint validation.";
31031
+ return report;
30864
31032
  }
30865
- var PAYLOADS = payloadsDir();
30866
- var TEMPLATES = templatesDir();
30867
- function listPayloadCategories() {
31033
+
31034
+ // src/catalog.ts
31035
+ import { readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
31036
+ import { join as join7 } from "node:path";
31037
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
31038
+ import { spawnSync } from "node:child_process";
31039
+ var ROOT2 = join7(fileURLToPath4(new URL(".", import.meta.url)), "..");
31040
+ var _toolsCache = null;
31041
+ var _skillsCache = null;
31042
+ function loadTools() {
31043
+ if (_toolsCache)
31044
+ return _toolsCache;
30868
31045
  try {
30869
- return readdirSync3(PAYLOADS, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
31046
+ const raw = JSON.parse(readFileSync7(join7(ROOT2, "tools-catalog.json"), "utf8"));
31047
+ _toolsCache = raw.tools ?? [];
30870
31048
  } catch {
30871
- return [];
31049
+ _toolsCache = [];
30872
31050
  }
31051
+ return _toolsCache;
30873
31052
  }
30874
- function payloadLookup(topic) {
30875
- const cats = listPayloadCategories();
30876
- const key = topic.toLowerCase();
30877
- const ALIASES = {
30878
- sqli: "sql injection",
30879
- xss: "xss injection",
30880
- ssrf: "server side request forgery",
30881
- ssti: "server side template injection",
30882
- jwt: "json web token",
30883
- lfi: "file inclusion",
30884
- rfi: "file inclusion",
30885
- xxe: "xxe injection",
30886
- idor: "insecure direct object references",
30887
- csrf: "cross-site request forgery",
30888
- cmdi: "command injection",
30889
- rce: "command injection",
30890
- cors: "cors misconfiguration",
30891
- crlf: "crlf injection",
30892
- prototype: "prototype pollution",
30893
- nosql: "nosql injection",
30894
- upload: "upload insecure files"
30895
- };
30896
- const normalized = ALIASES[key] ?? key;
30897
- const exact = cats.find((c) => c.toLowerCase() === normalized);
30898
- if (exact) {
30899
- return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
31053
+ function loadSkills() {
31054
+ if (_skillsCache)
31055
+ return _skillsCache;
31056
+ const dir = join7(ROOT2, "skills");
31057
+ const out = [];
31058
+ try {
31059
+ for (const f of readdirSync2(dir)) {
31060
+ if (!f.endsWith(".md"))
31061
+ continue;
31062
+ const p = join7(dir, f);
31063
+ const text = readFileSync7(p, "utf8");
31064
+ const descMatch = text.match(/^description:\s*(.+)$/m);
31065
+ out.push({
31066
+ name: f.replace(/\.md$/, ""),
31067
+ file: f,
31068
+ description: (descMatch?.[1] ?? "").trim().slice(0, 200),
31069
+ size: text.length
31070
+ });
31071
+ }
31072
+ } catch {}
31073
+ _skillsCache = out;
31074
+ return out;
31075
+ }
31076
+ function toolLookup(name) {
31077
+ const tools = loadTools();
31078
+ const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
31079
+ if (!t) {
31080
+ return {
31081
+ found: false,
31082
+ query: name,
31083
+ suggestions: tools.filter((x) => x.name.includes(name.toLowerCase()) || name.toLowerCase().includes(x.name)).slice(0, 5).map((x) => x.name)
31084
+ };
30900
31085
  }
30901
- const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
30902
- const matches = cats.filter((c) => {
30903
- const cl = c.toLowerCase();
30904
- if (cl.includes(normalized) || normalized.includes(cl))
30905
- return true;
30906
- return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
30907
- });
30908
- if (matches.length > 0) {
30909
- return { found: true, category: matches[0], files: listFiles(join8(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
31086
+ const manual = manualForTool(t.name);
31087
+ const result = { found: true, tool: t };
31088
+ if (manual && manual.content) {
31089
+ result.manual = {
31090
+ name: manual.name,
31091
+ category: manual.category,
31092
+ preview: manual.content.slice(0, 1500),
31093
+ full_length: manual.content.length
31094
+ };
31095
+ } else {
31096
+ result.manual = null;
30910
31097
  }
30911
- return { found: false, topic, suggestions: cats.slice(0, 20) };
31098
+ return result;
30912
31099
  }
30913
- function readPayload(category, file) {
30914
- const cats = listPayloadCategories();
30915
- const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
30916
- if (!exact)
30917
- return { found: false, category, suggestions: cats.slice(0, 20) };
30918
- const dir = join8(PAYLOADS, exact);
30919
- const files = listFiles(dir);
30920
- if (!file) {
30921
- const readme = files.find((f) => f.toLowerCase() === "readme.md");
30922
- if (readme) {
30923
- return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
30924
- }
30925
- return { found: true, category: exact, files };
31100
+ function listTools() {
31101
+ const tools = loadTools();
31102
+ const byCat = {};
31103
+ for (const t of tools) {
31104
+ (byCat[t.category] ??= []).push(t.name);
30926
31105
  }
30927
- const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
30928
- if (!f)
30929
- return { found: false, category: exact, file, suggestions: files };
30930
- return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
31106
+ return { total: tools.length, categories: byCat, tools: tools.map((t) => ({ name: t.name, category: t.category, description: t.description })) };
30931
31107
  }
30932
- function listFiles(dir) {
31108
+ function skillLookup(topic) {
31109
+ const skills = loadSkills();
31110
+ const q = topic.toLowerCase();
31111
+ const scored = skills.map((s) => {
31112
+ const hay = `${s.name} ${s.description}`.toLowerCase();
31113
+ const nameHit = s.name.includes(q);
31114
+ const descHit = s.description.includes(q);
31115
+ const wordHit = q.split(/\s+/).filter((w) => w && hay.includes(w)).length;
31116
+ return { s, score: (nameHit ? 10 : 0) + (descHit ? 5 : 0) + wordHit * 2 };
31117
+ }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
31118
+ if (scored.length === 0) {
31119
+ return { found: false, query: topic, suggestions: skills.map((s) => s.name).slice(0, 10) };
31120
+ }
31121
+ return {
31122
+ found: true,
31123
+ matches: scored.slice(0, 5).map((x) => ({ name: x.s.name, description: x.s.description, file: x.s.file }))
31124
+ };
31125
+ }
31126
+ function listSkills() {
31127
+ const skills = loadSkills();
31128
+ return { total: skills.length, skills: skills.map((s) => ({ name: s.name, description: s.description })) };
31129
+ }
31130
+ function readSkill(name) {
31131
+ const skills = loadSkills();
31132
+ const s = skills.find((x) => x.name.toLowerCase() === name.toLowerCase());
31133
+ if (!s)
31134
+ return { found: false, query: name };
30933
31135
  try {
30934
- return readdirSync3(dir).filter((f) => f.endsWith(".md") || f.endsWith(".txt") || f.endsWith(".yaml")).sort();
31136
+ const text = readFileSync7(join7(ROOT2, "skills", s.file), "utf8");
31137
+ return { found: true, name: s.name, content: text.slice(0, 12000) };
30935
31138
  } catch {
30936
- return [];
31139
+ return { found: false, query: name, error: "unreadable" };
30937
31140
  }
30938
31141
  }
30939
- function countTemplates() {
31142
+ function ensureTool(name) {
31143
+ const tools = loadTools();
31144
+ const t = tools.find((x) => x.name.toLowerCase() === name.toLowerCase());
31145
+ if (!t) {
31146
+ return { found: false, query: name, suggestions: tools.slice(0, 5).map((x) => x.name) };
31147
+ }
31148
+ const check = spawnSync(t.check_installed.command, { shell: true, timeout: 15000 });
31149
+ if (check.status !== 127 && check.error === undefined) {
31150
+ return { name: t.name, installed: true, action: "none", note: "already installed" };
31151
+ }
31152
+ const platform = process.platform;
31153
+ const cmd = t.install[platform] ?? t.install.linux;
31154
+ if (!cmd) {
31155
+ return { name: t.name, installed: false, action: "none", note: `no install command for ${platform}` };
31156
+ }
31157
+ const r = spawnSync(cmd, { shell: true, timeout: 120000 });
31158
+ const ok = r.status === 0;
31159
+ return {
31160
+ name: t.name,
31161
+ installed: ok,
31162
+ action: "install",
31163
+ command: cmd,
31164
+ output: (r.stdout?.toString() ?? "").slice(-400) || (r.stderr?.toString() ?? "").slice(-400)
31165
+ };
31166
+ }
31167
+
31168
+ // src/intel.ts
31169
+ import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
31170
+ import { join as join8 } from "node:path";
31171
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
31172
+ import { readdirSync as readdirSync3 } from "node:fs";
31173
+ import { homedir as homedir3 } from "node:os";
31174
+ var ROOT3 = join8(fileURLToPath5(new URL(".", import.meta.url)), "..");
31175
+ var INTEL = join8(ROOT3, "intelligence");
31176
+ function loadJson(name) {
31177
+ const p = join8(INTEL, `${name}.json`);
31178
+ if (!existsSync5(p))
31179
+ return null;
30940
31180
  try {
30941
- let n = 0;
30942
- const walk = (dir) => {
30943
- for (const e of readdirSync3(dir, { withFileTypes: true })) {
30944
- if (e.isDirectory())
30945
- walk(join8(dir, e.name));
30946
- else if (e.name.endsWith(".yaml"))
30947
- n++;
30948
- }
30949
- };
30950
- walk(TEMPLATES);
30951
- return n;
31181
+ return JSON.parse(readFileSync8(p, "utf8"));
30952
31182
  } catch {
30953
- return 0;
31183
+ return null;
30954
31184
  }
30955
31185
  }
30956
- function templateLookup(topic, limit = 10) {
30957
- const key = topic.toLowerCase();
30958
- const hits = [];
30959
- const walk = (dir, depth = 0) => {
30960
- if (depth > 6 || hits.length >= limit * 3)
30961
- return;
30962
- let entries;
30963
- try {
30964
- entries = readdirSync3(dir, { withFileTypes: true });
30965
- } catch {
30966
- return;
30967
- }
30968
- for (const e of entries) {
30969
- if (e.isDirectory())
31186
+ function detectWaf(headers, body = "") {
31187
+ const sigs = loadJson("waf_signatures");
31188
+ if (!sigs)
31189
+ return { detected: false, wafs: [], error: "waf_signatures.json not loaded" };
31190
+ const headerSigs = sigs.header_signatures ?? [];
31191
+ const bodySigs = sigs.body_signatures ?? [];
31192
+ const matches = [];
31193
+ const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
31194
+ for (const s of headerSigs) {
31195
+ const h = (s.header ?? "").toLowerCase();
31196
+ const pattern = (s.pattern ?? "").toLowerCase();
31197
+ if (!h)
31198
+ continue;
31199
+ if (pattern) {
31200
+ const val = (headers[h] ?? headers[s.header ?? ""] ?? "").toLowerCase();
31201
+ if (val.includes(pattern)) {
31202
+ matches.push({ waf: s.waf, header: s.header, pattern: s.pattern, confidence: s.confidence, via: "header-value" });
31203
+ }
31204
+ } else {
31205
+ if (headerKeys.includes(h)) {
31206
+ matches.push({ waf: s.waf, header: s.header, confidence: s.confidence, via: "header-name" });
31207
+ }
31208
+ }
31209
+ }
31210
+ for (const s of bodySigs) {
31211
+ const pattern = (s.pattern ?? "").toLowerCase();
31212
+ if (pattern && body.toLowerCase().includes(pattern)) {
31213
+ matches.push({ waf: s.waf, pattern: s.pattern, confidence: s.confidence, via: "body" });
31214
+ }
31215
+ }
31216
+ const byWaf = new Map;
31217
+ for (const m of matches) {
31218
+ const key = m.waf;
31219
+ const prev = byWaf.get(key);
31220
+ if (!prev || m.confidence > prev.confidence)
31221
+ byWaf.set(key, m);
31222
+ }
31223
+ const detected = [...byWaf.values()].sort((a, b) => b.confidence - a.confidence);
31224
+ return {
31225
+ detected: detected.length > 0,
31226
+ wafs: detected,
31227
+ block_status_codes: sigs.block_status_codes ?? []
31228
+ };
31229
+ }
31230
+ function techCorrelation(tech) {
31231
+ const data = loadJson("tech_correlations");
31232
+ if (!data)
31233
+ return { found: false, error: "tech_correlations.json not loaded" };
31234
+ const key = tech.toLowerCase();
31235
+ const entry = data[key];
31236
+ if (!entry) {
31237
+ const matches = Object.keys(data).filter((k) => k.includes(key) || key.includes(k)).slice(0, 5);
31238
+ return { found: false, tech, suggestions: matches };
31239
+ }
31240
+ return { found: true, tech: key, vulns: entry.vulns ?? [], paths: entry.paths ?? [], detection: entry.detection ?? { vulns: entry.vulns, paths: entry.paths } };
31241
+ }
31242
+ function cveCorrelation(cveId) {
31243
+ const data = loadJson("cve_correlations");
31244
+ if (!data)
31245
+ return { error: "cve_correlations.json not loaded" };
31246
+ const key = cveId.toUpperCase();
31247
+ const entry = data[key];
31248
+ if (!entry)
31249
+ return { found: false, cve: key, total_known: Object.keys(data).length };
31250
+ return { found: true, cve: key, ...entry };
31251
+ }
31252
+ function portCorrelation(port) {
31253
+ const data = loadJson("port_correlations");
31254
+ if (!data)
31255
+ return { error: "port_correlations.json not loaded" };
31256
+ const key = String(port);
31257
+ const entry = data[key];
31258
+ if (!entry)
31259
+ return { found: false, port: key, total_known: Object.keys(data).length };
31260
+ return { found: true, port: key, ...entry };
31261
+ }
31262
+ function fuzzerPayloads(category) {
31263
+ const data = loadJson("fuzzer_data");
31264
+ if (!data)
31265
+ return { error: "fuzzer_data.json not loaded" };
31266
+ if (category) {
31267
+ const c = data[category] ?? data[category.toUpperCase()];
31268
+ return { category, found: Boolean(c), data: c ?? null };
31269
+ }
31270
+ return { categories: Object.keys(data), fuzz_points: data.FUZZ_POINTS ?? null, payloads: data.FUZZ_PAYLOADS ?? null };
31271
+ }
31272
+ function intelSummary() {
31273
+ const waf = loadJson("waf_signatures");
31274
+ const tech = loadJson("tech_correlations");
31275
+ const cve = loadJson("cve_correlations");
31276
+ const port = loadJson("port_correlations");
31277
+ const fuzz = loadJson("fuzzer_data");
31278
+ const av = loadJson("attack_vectors");
31279
+ return {
31280
+ waf_signatures: waf ? (waf.header_signatures?.length ?? 0) + (waf.body_signatures?.length ?? 0) : 0,
31281
+ tech_correlations: tech ? Object.keys(tech).length : 0,
31282
+ cve_correlations: cve ? Object.keys(cve).length : 0,
31283
+ port_correlations: port ? Object.keys(port).length : 0,
31284
+ fuzzer_categories: fuzz ? Object.keys(fuzz).length : 0,
31285
+ payload_categories: listPayloadCategories().length,
31286
+ nuclei_templates: countTemplates(),
31287
+ attack_vector_categories: av ? av.categories.length : 0,
31288
+ attack_vectors: av ? av.categories.reduce((n, c) => n + c.vectors.length, 0) : 0
31289
+ };
31290
+ }
31291
+ function listAttackVectors() {
31292
+ const data = loadJson("attack_vectors");
31293
+ if (!data)
31294
+ return { error: "attack_vectors.json not loaded" };
31295
+ return {
31296
+ total_categories: data.categories.length,
31297
+ total_vectors: data.categories.reduce((n, c) => n + c.vectors.length, 0),
31298
+ categories: data.categories.map((c) => ({ id: c.id, title: c.title, count: c.vectors.length }))
31299
+ };
31300
+ }
31301
+ function attackVectors(category) {
31302
+ const data = loadJson("attack_vectors");
31303
+ if (!data)
31304
+ return { error: "attack_vectors.json not loaded" };
31305
+ const q = category.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
31306
+ const found = data.categories.find((c) => c.id === q || c.id.includes(q) || c.title.toLowerCase().includes(category.toLowerCase()));
31307
+ if (!found) {
31308
+ const matches = data.categories.filter((c) => c.title.toLowerCase().includes(category.toLowerCase()) || c.id.includes(q)).slice(0, 5).map((c) => c.id);
31309
+ return { found: false, query: category, suggestions: matches };
31310
+ }
31311
+ return { found: true, id: found.id, title: found.title, count: found.vectors.length, vectors: found.vectors };
31312
+ }
31313
+ var PACKAGE_PAYLOADS = join8(ROOT3, "payloads");
31314
+ var PACKAGE_TEMPLATES = join8(ROOT3, "templates");
31315
+ var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join8(homedir3(), ".blitzstrike", "data");
31316
+ function payloadsDir() {
31317
+ if (existsSync5(PACKAGE_PAYLOADS))
31318
+ return PACKAGE_PAYLOADS;
31319
+ return join8(DATA_ROOT, "payloads");
31320
+ }
31321
+ function templatesDir() {
31322
+ if (existsSync5(PACKAGE_TEMPLATES))
31323
+ return PACKAGE_TEMPLATES;
31324
+ return join8(DATA_ROOT, "templates");
31325
+ }
31326
+ var PAYLOADS = payloadsDir();
31327
+ var TEMPLATES = templatesDir();
31328
+ function listPayloadCategories() {
31329
+ try {
31330
+ return readdirSync3(PAYLOADS, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
31331
+ } catch {
31332
+ return [];
31333
+ }
31334
+ }
31335
+ function payloadLookup(topic) {
31336
+ const cats = listPayloadCategories();
31337
+ const key = topic.toLowerCase();
31338
+ const ALIASES = {
31339
+ sqli: "sql injection",
31340
+ xss: "xss injection",
31341
+ ssrf: "server side request forgery",
31342
+ ssti: "server side template injection",
31343
+ jwt: "json web token",
31344
+ lfi: "file inclusion",
31345
+ rfi: "file inclusion",
31346
+ xxe: "xxe injection",
31347
+ idor: "insecure direct object references",
31348
+ csrf: "cross-site request forgery",
31349
+ cmdi: "command injection",
31350
+ rce: "command injection",
31351
+ cors: "cors misconfiguration",
31352
+ crlf: "crlf injection",
31353
+ prototype: "prototype pollution",
31354
+ nosql: "nosql injection",
31355
+ upload: "upload insecure files"
31356
+ };
31357
+ const normalized = ALIASES[key] ?? key;
31358
+ const exact = cats.find((c) => c.toLowerCase() === normalized);
31359
+ if (exact) {
31360
+ return { found: true, category: exact, files: listFiles(join8(PAYLOADS, exact)) };
31361
+ }
31362
+ const keyTokens = normalized.split(/[^a-z0-9]+/).filter((t) => t.length >= 3);
31363
+ const matches = cats.filter((c) => {
31364
+ const cl = c.toLowerCase();
31365
+ if (cl.includes(normalized) || normalized.includes(cl))
31366
+ return true;
31367
+ return keyTokens.some((tok) => new RegExp(`(^|[^a-z0-9])${tok}`).test(cl));
31368
+ });
31369
+ if (matches.length > 0) {
31370
+ return { found: true, category: matches[0], files: listFiles(join8(PAYLOADS, matches[0])), alternatives: matches.slice(0, 10) };
31371
+ }
31372
+ return { found: false, topic, suggestions: cats.slice(0, 20) };
31373
+ }
31374
+ function readPayload(category, file) {
31375
+ const cats = listPayloadCategories();
31376
+ const exact = cats.find((c) => c.toLowerCase() === category.toLowerCase());
31377
+ if (!exact)
31378
+ return { found: false, category, suggestions: cats.slice(0, 20) };
31379
+ const dir = join8(PAYLOADS, exact);
31380
+ const files = listFiles(dir);
31381
+ if (!file) {
31382
+ const readme = files.find((f) => f.toLowerCase() === "readme.md");
31383
+ if (readme) {
31384
+ return { found: true, category: exact, file: readme, content: readFileSync8(join8(dir, readme), "utf8").slice(0, 20000) };
31385
+ }
31386
+ return { found: true, category: exact, files };
31387
+ }
31388
+ const f = files.find((x) => x.toLowerCase() === file.toLowerCase());
31389
+ if (!f)
31390
+ return { found: false, category: exact, file, suggestions: files };
31391
+ return { found: true, category: exact, file: f, content: readFileSync8(join8(dir, f), "utf8").slice(0, 20000) };
31392
+ }
31393
+ function listFiles(dir) {
31394
+ try {
31395
+ return readdirSync3(dir).filter((f) => f.endsWith(".md") || f.endsWith(".txt") || f.endsWith(".yaml")).sort();
31396
+ } catch {
31397
+ return [];
31398
+ }
31399
+ }
31400
+ function countTemplates() {
31401
+ try {
31402
+ let n = 0;
31403
+ const walk = (dir) => {
31404
+ for (const e of readdirSync3(dir, { withFileTypes: true })) {
31405
+ if (e.isDirectory())
31406
+ walk(join8(dir, e.name));
31407
+ else if (e.name.endsWith(".yaml"))
31408
+ n++;
31409
+ }
31410
+ };
31411
+ walk(TEMPLATES);
31412
+ return n;
31413
+ } catch {
31414
+ return 0;
31415
+ }
31416
+ }
31417
+ function templateLookup(topic, limit = 10) {
31418
+ const key = topic.toLowerCase();
31419
+ const hits = [];
31420
+ const walk = (dir, depth = 0) => {
31421
+ if (depth > 6 || hits.length >= limit * 3)
31422
+ return;
31423
+ let entries;
31424
+ try {
31425
+ entries = readdirSync3(dir, { withFileTypes: true });
31426
+ } catch {
31427
+ return;
31428
+ }
31429
+ for (const e of entries) {
31430
+ if (e.isDirectory())
30970
31431
  walk(join8(dir, e.name), depth + 1);
30971
31432
  else if (e.name.endsWith(".yaml")) {
30972
31433
  const name = e.name.toLowerCase();
@@ -51632,506 +52093,168 @@ var API_PATHS = [
51632
52093
  "/api-docs",
51633
52094
  "/v2/api-docs",
51634
52095
  "/v3/api-docs",
51635
- "/graphql",
51636
- "/graphiql",
51637
- "/actuator",
51638
- "/actuator/health",
51639
- "/actuator/env",
51640
- "/api/swagger.json",
51641
- "/docs",
51642
- "/redoc"
51643
- ];
51644
- async function discoverApi(base) {
51645
- const out = [];
51646
- for (const p of API_PATHS) {
51647
- const r = await httpGet(`${base}${p}`);
51648
- if (r.status >= 200 && r.status < 400) {
51649
- out.push({ path: p, status: r.status, body_size: r.body.length });
51650
- }
51651
- }
51652
- return out;
51653
- }
51654
- function extractVersion(headers, body) {
51655
- const out = [];
51656
- const server = headers["server"] ?? headers["x-powered-by"] ?? "";
51657
- const serverVer = /^(nginx|apache|microsoft-iis|openresty|liteSpeed|caddy)\/?([0-9][0-9.\-]*)?/i.exec(server);
51658
- if (serverVer) {
51659
- out.push({ product: serverVer[1].toLowerCase(), version: serverVer[2] ?? "unknown", evidence: server });
51660
- }
51661
- const phpVer = /php\/([0-9][0-9.\-]*)/i.exec(server);
51662
- if (phpVer)
51663
- out.push({ product: "php", version: phpVer[1], evidence: server });
51664
- const wpVer = /content="WordPress\s+([0-9][0-9.]*)/i.exec(body);
51665
- if (wpVer)
51666
- out.push({ product: "wordpress", version: wpVer[1], evidence: `meta generator: WordPress ${wpVer[1]}` });
51667
- const joomlaVer = /Joomla!?\s*([0-9][0-9.]*)/i.exec(body);
51668
- if (joomlaVer)
51669
- out.push({ product: "joomla", version: joomlaVer[1], evidence: `body: Joomla ${joomlaVer[1]}` });
51670
- const drupalVer = /Drupal\s+([0-9][0-9.]*)/i.exec(body);
51671
- if (drupalVer)
51672
- out.push({ product: "drupal", version: drupalVer[1], evidence: `body: Drupal ${drupalVer[1]}` });
51673
- const powered = headers["x-powered-by"] ?? "";
51674
- if (powered && !out.some((o) => powered.includes(o.product))) {
51675
- out.push({ product: powered.split("/")[0].toLowerCase(), version: powered.split("/")[1] ?? "unknown", evidence: powered });
51676
- }
51677
- return out;
51678
- }
51679
- function correlateIntel(techs, versionList, portList) {
51680
- const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
51681
- const versions = versionList.map((v) => ({
51682
- product: v.product,
51683
- version: v.version,
51684
- correlation: techCorrelation(v.product),
51685
- payload_lookup: payloadLookup(v.product),
51686
- template_lookup: templateLookup(v.product, 5)
51687
- }));
51688
- const ports = portList.map((p) => ({ port: p.port, service: p.service, ...portCorrelation(p.port) }));
51689
- return { tech, versions, ports };
51690
- }
51691
- async function liveRecon(target, includeActive = false) {
51692
- const base = normalizeTarget2(target);
51693
- const host = (() => {
51694
- try {
51695
- return new URL(base).hostname;
51696
- } catch {
51697
- return target;
51698
- }
51699
- })();
51700
- const root = await httpGet(base);
51701
- const interestingHeaders = {};
51702
- for (const k of ["server", "x-powered-by", "x-aspnet-version", "x-drupal-cache", "x-generator", "via", "x-cache", "cf-ray"]) {
51703
- if (root.headers[k])
51704
- interestingHeaders[k] = root.headers[k];
51705
- }
51706
- const fingerprint = {
51707
- status: root.status,
51708
- title: (root.body.match(/<title[^>]*>([^<]+)<\/title>/i) ?? [])[1]?.trim().slice(0, 200) ?? "",
51709
- server: root.headers["server"] ?? root.headers["x-powered-by"] ?? "",
51710
- headers: interestingHeaders,
51711
- body_size: root.body.length
51712
- };
51713
- const waf = detectWaf(root.headers, root.body);
51714
- const techSig = [
51715
- [/wp-content|wp-includes|wp-json/i, "wordpress"],
51716
- [/wp-login\.php|wp-admin/i, "wordpress"],
51717
- [/powered by joomla|com_content/i, "joomla"],
51718
- [/laravel|_token|csrf-token/i, "laravel"],
51719
- [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
51720
- [/angular|ng-version/i, "angular"],
51721
- [/vue|__vue__|v-data/i, "vue"],
51722
- [/django|csrftoken|__debug__/i, "django"],
51723
- [/ruby on rails|rails/i, "rails"],
51724
- [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
51725
- [/phpBB|phpbb/i, "phpbb"],
51726
- [/mybb|mybb/i, "mybb"],
51727
- [/drupal|Drupal\.settings/i, "drupal"],
51728
- [/magento|Mage\./i, "magento"],
51729
- [/shopify|cdn\.shopify/i, "shopify"],
51730
- [/grafana|kibana|elastic/i, "grafana-kibana"],
51731
- [/node\.js|express/i, "nodejs"],
51732
- [/flask|werkzeug/i, "flask"],
51733
- [/spring|actuator/i, "spring"]
51734
- ];
51735
- const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
51736
- const versions = extractVersion(root.headers, root.body);
51737
- const { links, scripts } = crawlLinks(base, root.body);
51738
- const { robots, sitemap } = await fetchRobotsAndSitemap(base);
51739
- const allUrls = [...links, ...scripts, ...robots, ...sitemap];
51740
- const crawl = { links, scripts, endpoints: [...new Set(allUrls)], robots, sitemap, count: allUrls.length };
51741
- const params = extractParams(allUrls);
51742
- const { subdomains } = await subdomainEnum(host);
51743
- const { found: dnsFindings } = await dnsBrute(host);
51744
- const { urls: wayback } = await waybackUrls(host);
51745
- const apiEndpoints = (await discoverApi(base)).map(({ path, status }) => ({ path, status }));
51746
- let openPorts = [];
51747
- if (includeActive && root.status > 0) {
51748
- const scan = await scanPorts(host);
51749
- openPorts = scan.map(({ port, service }) => ({ port, service }));
51750
- }
51751
- const intel = correlateIntel(techs, versions, openPorts);
51752
- const nextSteps = [];
51753
- if (waf.detected)
51754
- nextSteps.push(`WAF detected (${waf.wafs?.map((w) => w.waf).join(", ")}) — consult detect_waf + WAF bypass playbook before exploitation.`);
51755
- for (const t of techs) {
51756
- const c = techCorrelation(t);
51757
- if (c.found)
51758
- nextSteps.push(`Tech '${t}' maps to known vuln classes — read tech_correlation('${t}') and fetch nuclei templates with template_lookup('${t}').`);
51759
- }
51760
- for (const v of versions) {
51761
- nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
51762
- }
51763
- if (crawl.endpoints.length > 0)
51764
- nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
51765
- if (params.length > 0)
51766
- nextSteps.push(`Discovered input params: ${params.join(", ")} — these are attack-surface entry points; fuzz them with payload_lookup after scope confirms.`);
51767
- if (subdomains.length > 0)
51768
- nextSteps.push(`Found ${subdomains.length} subdomains — each is a separate scope surface; enumerate further with fofa_search.`);
51769
- if (dnsFindings.length > 0)
51770
- nextSteps.push(`DNS-resolved ${dnsFindings.length} hosts (${dnsFindings.map((d) => d.host).slice(0, 8).join(", ")}${dnsFindings.length > 8 ? "…" : ""}) — each is a live subdomain to recon.`);
51771
- if (wayback.length > 0)
51772
- nextSteps.push(`Wayback Machine surfaced ${wayback.length} historical URLs — mine them for hidden endpoints, old params, and forgotten pages; feed to taint_file/blitz_scan.`);
51773
- if (openPorts.length > 0)
51774
- nextSteps.push(`Open ports: ${openPorts.map((p) => `${p.port}/${p.service}`).join(", ")} — map services via port_correlation and probe each with the relevant tool manual.`);
51775
- if (apiEndpoints.length > 0)
51776
- nextSteps.push(`API endpoints exposed (${apiEndpoints.map((e) => e.path).join(", ")}) — audit them with api-security playbook; check for BOLA/IDOR/auth gaps.`);
51777
- if (nextSteps.length === 0)
51778
- nextSteps.push("Nothing exposed on the surface — try subdomain enumeration, port scan (active), or request source access for EAGLE-EYE analysis.");
51779
- nextSteps.push("Every confirmed finding: record with finding_create, attach confidence_score, redact secrets. A hit is a hypothesis until verified.");
51780
- return {
51781
- target: base,
51782
- host,
51783
- fingerprint,
51784
- waf,
51785
- tech: { detected: techs, server: fingerprint.server },
51786
- versions,
51787
- crawl,
51788
- params,
51789
- subdomains,
51790
- dns_findings: dnsFindings,
51791
- wayback_urls: wayback,
51792
- api_endpoints: apiEndpoints,
51793
- open_ports: openPorts,
51794
- intel,
51795
- next_steps: nextSteps
51796
- };
51797
- }
51798
-
51799
- // src/strike.ts
51800
- var DEFAULT_TIMEOUT_MS = 15000;
51801
- async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
51802
- const controller = new AbortController;
51803
- const t = setTimeout(() => controller.abort(), timeoutMs);
51804
- try {
51805
- const res = await fetch(url, {
51806
- method,
51807
- body: method === "POST" ? data : undefined,
51808
- headers: { "User-Agent": "blitzstrike/1.0", ...headers ?? {} },
51809
- signal: controller.signal,
51810
- redirect: "follow"
51811
- });
51812
- const body = await res.text();
51813
- const hdrs = {};
51814
- res.headers.forEach((v, k) => {
51815
- hdrs[k] = v;
51816
- });
51817
- return { status: res.status, body: body.slice(0, 20000), headers: hdrs };
51818
- } catch (e) {
51819
- return { status: 0, body: String(e), headers: {} };
51820
- } finally {
51821
- clearTimeout(t);
51822
- }
51823
- }
51824
- function defaultMarker() {
51825
- return `BS${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
51826
- }
51827
- function inject(target, value, param) {
51828
- const placeholder = "{{MARKER}}";
51829
- if (target.includes(placeholder))
51830
- return target.split(placeholder).join(value);
51831
- if (target.includes("?"))
51832
- return `${target}&${param}=${encodeURIComponent(value)}`;
51833
- return `${target}?${param}=${encodeURIComponent(value)}`;
51834
- }
51835
- async function strikeVerify(input) {
51836
- const method = input.method ?? "GET";
51837
- const param = input.param ?? "q";
51838
- const marker = input.marker ?? defaultMarker();
51839
- const control = input.control ?? defaultMarker();
51840
- const baseUrl = input.url;
51841
- const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
51842
- const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
51843
- const markerTarget = inject(baseUrl, marker, param);
51844
- const controlTarget = inject(baseUrl, control, param);
51845
- const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
51846
- const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
51847
- const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
51848
- if (baseline.status === 0 || markerResp.status === 0) {
51849
- return {
51850
- status: "blocked",
51851
- marker_reflected: false,
51852
- control_reflected: false,
51853
- baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
51854
- marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
51855
- control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
51856
- reason: "request failed or target unreachable — cannot validate",
51857
- evidence: []
51858
- };
51859
- }
51860
- const markerReflected = markerResp.body.includes(marker);
51861
- const controlReflected = controlResp.body.includes(control);
51862
- let status;
51863
- let reason;
51864
- if (markerReflected && !controlReflected) {
51865
- status = "confirmed";
51866
- reason = "marker reflected; negative control did not — the sink reflects attacker-controlled input (real finding)";
51867
- } else if (markerReflected && controlReflected) {
51868
- status = "false_positive";
51869
- reason = "marker AND negative control both reflected — behaviour is indistinguishable from benign reflection (likely false positive)";
51870
- } else if (!markerReflected && !controlReflected) {
51871
- status = "unconfirmed";
51872
- reason = "marker not reflected — reachability/exploitability could not be demonstrated live";
51873
- } else {
51874
- status = "unconfirmed";
51875
- reason = "negative control reflected but marker did not — inconsistent, requires deeper analysis";
51876
- }
51877
- const evidence = [
51878
- {
51879
- type: "baseline_comparison",
51880
- description: `Baseline response (status ${baseline.status}) for ${redactSecrets(baselineTarget)}`,
51881
- artifacts: [{ name: "baseline", kind: "http_response", content: `${baseline.status}
51882
- ${baseline.body}` }]
51883
- },
51884
- {
51885
- type: "validation_result",
51886
- description: `Marker '${redactSecrets(marker)}' reflected=${markerReflected}; control reflected=${controlReflected}`,
51887
- artifacts: [{ name: "marker_response", kind: "http_response", content: `${markerResp.status}
51888
- ${markerResp.body}` }]
51889
- },
51890
- {
51891
- type: "negative_control",
51892
- description: `Negative control '${redactSecrets(control)}' reflected=${controlReflected}`,
51893
- artifacts: [{ name: "control_response", kind: "http_response", content: `${controlResp.status}
51894
- ${controlResp.body}` }]
51895
- }
51896
- ];
51897
- return {
51898
- status,
51899
- marker_reflected: markerReflected,
51900
- control_reflected: controlReflected,
51901
- baseline: { status: baseline.status, body_preview: redactSecrets(baseline.body.slice(0, 400)), body_hash: sha256(baseline.body) },
51902
- marker_response: { status: markerResp.status, body_preview: redactSecrets(markerResp.body.slice(0, 400)), body_hash: sha256(markerResp.body) },
51903
- control_response: { status: controlResp.status, body_preview: redactSecrets(controlResp.body.slice(0, 400)), body_hash: sha256(controlResp.body) },
51904
- reason,
51905
- evidence
51906
- };
51907
- }
51908
- function resolveFinding(finding, verdict) {
51909
- let f = finding;
51910
- if (f.status === "hypothesis") {
51911
- f = transition(f, "validating");
51912
- }
51913
- switch (verdict.status) {
51914
- case "confirmed":
51915
- return confirmFinding(f, {
51916
- evidence: verdict.evidence,
51917
- negative_control: true,
51918
- baseline: true
51919
- });
51920
- case "false_positive":
51921
- return rejectFinding(f, "false_positive", verdict.reason, verdict.evidence);
51922
- case "blocked":
51923
- return rejectFinding(f, "blocked", verdict.reason, verdict.evidence);
51924
- case "unconfirmed":
51925
- case "likely":
51926
- default: {
51927
- return {
51928
- ...f,
51929
- validation: { performed: true, status: verdict.status === "likely" ? "likely" : "unconfirmed", negative_control: verdict.control_reflected === false, baseline: true },
51930
- remediation: { ...f.remediation, note: verdict.reason },
51931
- timestamps: { ...f.timestamps, updated: new Date().toISOString() }
51932
- };
51933
- }
51934
- }
51935
- }
51936
-
51937
- // src/cvss.ts
51938
- var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
51939
- var AC_WEIGHT = { L: 0.77, H: 0.44 };
51940
- var PR_WEIGHT = {
51941
- U: { N: 0.85, L: 0.62, H: 0.27 },
51942
- C: { N: 0.85, L: 0.68, H: 0.5 }
51943
- };
51944
- var UI_WEIGHT = { N: 0.85, R: 0.62 };
51945
- var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
51946
- function roundup(value) {
51947
- return Math.ceil(value * 10) / 10;
51948
- }
51949
- function cvssBaseScore(input) {
51950
- const AV = input.AV ?? "N";
51951
- const AC = input.AC ?? "L";
51952
- const PR = input.PR ?? "N";
51953
- const UI = input.UI ?? "N";
51954
- const S = input.S ?? "U";
51955
- const C = input.C ?? "N";
51956
- const I = input.I ?? "N";
51957
- const A = input.A ?? "N";
51958
- const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
51959
- let impact;
51960
- if (S === "U") {
51961
- impact = 6.42 * iscBase;
51962
- } else {
51963
- impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
51964
- }
51965
- const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
51966
- if (impact <= 0)
51967
- return 0;
51968
- if (S === "U") {
51969
- return roundup(Math.min(impact + exploitability, 10));
51970
- }
51971
- return roundup(Math.min(1.08 * (impact + exploitability), 10));
51972
- }
51973
- function cvssVector(input) {
51974
- const order = [
51975
- ["AV", input.AV ?? "N"],
51976
- ["AC", input.AC ?? "L"],
51977
- ["PR", input.PR ?? "N"],
51978
- ["UI", input.UI ?? "N"],
51979
- ["S", input.S ?? "U"],
51980
- ["C", input.C ?? "N"],
51981
- ["I", input.I ?? "N"],
51982
- ["A", input.A ?? "N"]
51983
- ];
51984
- return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
51985
- }
51986
- function cvssSeverity(score) {
51987
- if (score === 0)
51988
- return "none";
51989
- if (score < 4)
51990
- return "low";
51991
- if (score < 7)
51992
- return "medium";
51993
- if (score < 9)
51994
- return "high";
51995
- return "critical";
51996
- }
51997
- function cvssAssess(input) {
51998
- const score = cvssBaseScore(input);
51999
- return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
52000
- }
52001
-
52002
- // src/dedup.ts
52003
- function rootCauseSignature(finding) {
52004
- const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
52005
- const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
52006
- const cwe = (finding.classification?.cwe ?? "").toUpperCase();
52007
- return [sinkType, sourceType, cwe].filter(Boolean).join("|");
52008
- }
52009
- function dedupFindings(findings) {
52010
- const groups = new Map;
52011
- for (const f of findings) {
52012
- const sig = rootCauseSignature(f);
52013
- const existing = groups.get(sig);
52014
- if (existing) {
52015
- existing.count += 1;
52016
- existing.findings.push(f);
52017
- } else {
52018
- groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
52096
+ "/graphql",
52097
+ "/graphiql",
52098
+ "/actuator",
52099
+ "/actuator/health",
52100
+ "/actuator/env",
52101
+ "/api/swagger.json",
52102
+ "/docs",
52103
+ "/redoc"
52104
+ ];
52105
+ async function discoverApi(base) {
52106
+ const out = [];
52107
+ for (const p of API_PATHS) {
52108
+ const r = await httpGet(`${base}${p}`);
52109
+ if (r.status >= 200 && r.status < 400) {
52110
+ out.push({ path: p, status: r.status, body_size: r.body.length });
52019
52111
  }
52020
52112
  }
52021
- return [...groups.values()];
52022
- }
52023
- function uniqueFindings(findings) {
52024
- return dedupFindings(findings).map((g) => g.representative);
52113
+ return out;
52025
52114
  }
52026
-
52027
- // src/report.ts
52028
- function summarize(findings) {
52029
- const byStatus = {};
52030
- const bySeverity = {};
52031
- let evidenceArtifacts = 0;
52032
- for (const f of findings) {
52033
- byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
52034
- const sev = f.classification?.severity ?? "informational";
52035
- bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
52036
- evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
52115
+ function extractVersion(headers, body) {
52116
+ const out = [];
52117
+ const server = headers["server"] ?? headers["x-powered-by"] ?? "";
52118
+ const serverVer = /^(nginx|apache|microsoft-iis|openresty|liteSpeed|caddy)\/?([0-9][0-9.\-]*)?/i.exec(server);
52119
+ if (serverVer) {
52120
+ out.push({ product: serverVer[1].toLowerCase(), version: serverVer[2] ?? "unknown", evidence: server });
52037
52121
  }
52038
- const groups = dedupFindings(findings);
52039
- return {
52040
- total_findings: findings.length,
52041
- unique_root_causes: groups.length,
52042
- by_status: byStatus,
52043
- by_severity: bySeverity,
52044
- confirmed: byStatus["confirmed"] ?? 0,
52045
- false_positives: byStatus["false_positive"] ?? 0,
52046
- evidence_artifacts: evidenceArtifacts
52047
- };
52122
+ const phpVer = /php\/([0-9][0-9.\-]*)/i.exec(server);
52123
+ if (phpVer)
52124
+ out.push({ product: "php", version: phpVer[1], evidence: server });
52125
+ const wpVer = /content="WordPress\s+([0-9][0-9.]*)/i.exec(body);
52126
+ if (wpVer)
52127
+ out.push({ product: "wordpress", version: wpVer[1], evidence: `meta generator: WordPress ${wpVer[1]}` });
52128
+ const joomlaVer = /Joomla!?\s*([0-9][0-9.]*)/i.exec(body);
52129
+ if (joomlaVer)
52130
+ out.push({ product: "joomla", version: joomlaVer[1], evidence: `body: Joomla ${joomlaVer[1]}` });
52131
+ const drupalVer = /Drupal\s+([0-9][0-9.]*)/i.exec(body);
52132
+ if (drupalVer)
52133
+ out.push({ product: "drupal", version: drupalVer[1], evidence: `body: Drupal ${drupalVer[1]}` });
52134
+ const powered = headers["x-powered-by"] ?? "";
52135
+ if (powered && !out.some((o) => powered.includes(o.product))) {
52136
+ out.push({ product: powered.split("/")[0].toLowerCase(), version: powered.split("/")[1] ?? "unknown", evidence: powered });
52137
+ }
52138
+ return out;
52048
52139
  }
52049
- function severityLabel(sev) {
52050
- const s = sev.toUpperCase();
52051
- if (s === "CRITICAL")
52052
- return "\uD83D\uDD34 CRITICAL";
52053
- if (s === "HIGH")
52054
- return "\uD83D\uDFE0 HIGH";
52055
- if (s === "MEDIUM")
52056
- return "\uD83D\uDFE1 MEDIUM";
52057
- if (s === "LOW")
52058
- return "\uD83D\uDD35 LOW";
52059
- return "⚪ INFORMATIONAL";
52140
+ function correlateIntel(techs, versionList, portList) {
52141
+ const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
52142
+ const versions = versionList.map((v) => ({
52143
+ product: v.product,
52144
+ version: v.version,
52145
+ correlation: techCorrelation(v.product),
52146
+ payload_lookup: payloadLookup(v.product),
52147
+ template_lookup: templateLookup(v.product, 5)
52148
+ }));
52149
+ const ports = portList.map((p) => ({ port: p.port, service: p.service, ...portCorrelation(p.port) }));
52150
+ return { tech, versions, ports };
52060
52151
  }
52061
- function reportMarkdown(findings, opts = {}) {
52062
- const summary = summarize(findings);
52063
- const lines = [];
52064
- lines.push(`# ${opts.title ?? "Blitz Strike — Security Assessment Report"}`);
52065
- lines.push("");
52066
- lines.push(`- Scope: ${opts.scope ?? "(unset)"}`);
52067
- lines.push(`- Blitz Strike version: ${opts.version ?? "unknown"}`);
52068
- if (opts.include_timestamp)
52069
- lines.push(`- Generated: ${new Date().toISOString()}`);
52070
- lines.push("");
52071
- lines.push("## Summary");
52072
- lines.push("");
52073
- lines.push(`| Metric | Value |`);
52074
- lines.push(`|--------|-------|`);
52075
- lines.push(`| Total findings | ${summary.total_findings} |`);
52076
- lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
52077
- lines.push(`| Confirmed | ${summary.confirmed} |`);
52078
- lines.push(`| False positives | ${summary.false_positives} |`);
52079
- lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
52080
- lines.push("");
52081
- lines.push("### By status");
52082
- lines.push("");
52083
- for (const [status, n] of Object.entries(summary.by_status).sort()) {
52084
- lines.push(`- ${status}: ${n}`);
52152
+ async function liveRecon(target, includeActive = false) {
52153
+ const base = normalizeTarget2(target);
52154
+ const host = (() => {
52155
+ try {
52156
+ return new URL(base).hostname;
52157
+ } catch {
52158
+ return target;
52159
+ }
52160
+ })();
52161
+ const root = await httpGet(base);
52162
+ const interestingHeaders = {};
52163
+ for (const k of ["server", "x-powered-by", "x-aspnet-version", "x-drupal-cache", "x-generator", "via", "x-cache", "cf-ray"]) {
52164
+ if (root.headers[k])
52165
+ interestingHeaders[k] = root.headers[k];
52085
52166
  }
52086
- lines.push("");
52087
- lines.push("### By severity");
52088
- lines.push("");
52089
- for (const [sev, n] of Object.entries(summary.by_severity).sort()) {
52090
- lines.push(`- ${sev}: ${n}`);
52167
+ const fingerprint = {
52168
+ status: root.status,
52169
+ title: (root.body.match(/<title[^>]*>([^<]+)<\/title>/i) ?? [])[1]?.trim().slice(0, 200) ?? "",
52170
+ server: root.headers["server"] ?? root.headers["x-powered-by"] ?? "",
52171
+ headers: interestingHeaders,
52172
+ body_size: root.body.length
52173
+ };
52174
+ const waf = detectWaf(root.headers, root.body);
52175
+ const techSig = [
52176
+ [/wp-content|wp-includes|wp-json/i, "wordpress"],
52177
+ [/wp-login\.php|wp-admin/i, "wordpress"],
52178
+ [/powered by joomla|com_content/i, "joomla"],
52179
+ [/laravel|_token|csrf-token/i, "laravel"],
52180
+ [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
52181
+ [/angular|ng-version/i, "angular"],
52182
+ [/vue|__vue__|v-data/i, "vue"],
52183
+ [/django|csrftoken|__debug__/i, "django"],
52184
+ [/ruby on rails|rails/i, "rails"],
52185
+ [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
52186
+ [/phpBB|phpbb/i, "phpbb"],
52187
+ [/mybb|mybb/i, "mybb"],
52188
+ [/drupal|Drupal\.settings/i, "drupal"],
52189
+ [/magento|Mage\./i, "magento"],
52190
+ [/shopify|cdn\.shopify/i, "shopify"],
52191
+ [/grafana|kibana|elastic/i, "grafana-kibana"],
52192
+ [/node\.js|express/i, "nodejs"],
52193
+ [/flask|werkzeug/i, "flask"],
52194
+ [/spring|actuator/i, "spring"]
52195
+ ];
52196
+ const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
52197
+ const versions = extractVersion(root.headers, root.body);
52198
+ const { links, scripts } = crawlLinks(base, root.body);
52199
+ const { robots, sitemap } = await fetchRobotsAndSitemap(base);
52200
+ const allUrls = [...links, ...scripts, ...robots, ...sitemap];
52201
+ const crawl = { links, scripts, endpoints: [...new Set(allUrls)], robots, sitemap, count: allUrls.length };
52202
+ const params = extractParams(allUrls);
52203
+ const { subdomains } = await subdomainEnum(host);
52204
+ const { found: dnsFindings } = await dnsBrute(host);
52205
+ const { urls: wayback } = await waybackUrls(host);
52206
+ const apiEndpoints = (await discoverApi(base)).map(({ path, status }) => ({ path, status }));
52207
+ let openPorts = [];
52208
+ if (includeActive && root.status > 0) {
52209
+ const scan = await scanPorts(host);
52210
+ openPorts = scan.map(({ port, service }) => ({ port, service }));
52091
52211
  }
52092
- lines.push("");
52093
- lines.push("## Findings");
52094
- lines.push("");
52095
- if (findings.length === 0) {
52096
- lines.push("_No findings._");
52097
- return lines.join(`
52098
- `) + `
52099
- `;
52212
+ const intel = correlateIntel(techs, versions, openPorts);
52213
+ const nextSteps = [];
52214
+ if (waf.detected)
52215
+ nextSteps.push(`WAF detected (${waf.wafs?.map((w) => w.waf).join(", ")}) — consult detect_waf + WAF bypass playbook before exploitation.`);
52216
+ for (const t of techs) {
52217
+ const c = techCorrelation(t);
52218
+ if (c.found)
52219
+ nextSteps.push(`Tech '${t}' maps to known vuln classes — read tech_correlation('${t}') and fetch nuclei templates with template_lookup('${t}').`);
52100
52220
  }
52101
- for (const f of findings) {
52102
- lines.push(`### ${f.id} — ${f.title} [${severityLabel(f.classification?.severity ?? "informational")}]`);
52103
- lines.push("");
52104
- lines.push(`- **Status**: \`${f.status}\``);
52105
- lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
52106
- if (f.classification?.cwe)
52107
- lines.push(`- **CWE**: ${f.classification.cwe}`);
52108
- if (f.classification?.cvss_score != null)
52109
- lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
52110
- lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
52111
- lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
52112
- if (f.target?.host || f.target?.endpoint)
52113
- lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
52114
- lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
52115
- if (f.remediation && Object.keys(f.remediation).length) {
52116
- lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
52117
- }
52118
- lines.push("");
52221
+ for (const v of versions) {
52222
+ nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
52119
52223
  }
52120
- return lines.join(`
52121
- `) + `
52122
- `;
52123
- }
52124
- function reportJson(findings, opts = {}) {
52125
- const summary = summarize(findings);
52126
- const payload = {
52127
- title: opts.title ?? "Blitz Strike — Security Assessment Report",
52128
- scope: opts.scope ?? null,
52129
- version: opts.version ?? null,
52130
- summary,
52131
- findings,
52132
- integrity: sha256(JSON.stringify({ summary, findings }))
52224
+ if (crawl.endpoints.length > 0)
52225
+ nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
52226
+ if (params.length > 0)
52227
+ nextSteps.push(`Discovered input params: ${params.join(", ")} — these are attack-surface entry points; fuzz them with payload_lookup after scope confirms.`);
52228
+ if (subdomains.length > 0)
52229
+ nextSteps.push(`Found ${subdomains.length} subdomains — each is a separate scope surface; enumerate further with fofa_search.`);
52230
+ if (dnsFindings.length > 0)
52231
+ nextSteps.push(`DNS-resolved ${dnsFindings.length} hosts (${dnsFindings.map((d) => d.host).slice(0, 8).join(", ")}${dnsFindings.length > 8 ? "…" : ""}) — each is a live subdomain to recon.`);
52232
+ if (wayback.length > 0)
52233
+ nextSteps.push(`Wayback Machine surfaced ${wayback.length} historical URLs — mine them for hidden endpoints, old params, and forgotten pages; feed to taint_file/blitz_scan.`);
52234
+ if (openPorts.length > 0)
52235
+ nextSteps.push(`Open ports: ${openPorts.map((p) => `${p.port}/${p.service}`).join(", ")} — map services via port_correlation and probe each with the relevant tool manual.`);
52236
+ if (apiEndpoints.length > 0)
52237
+ nextSteps.push(`API endpoints exposed (${apiEndpoints.map((e) => e.path).join(", ")}) — audit them with api-security playbook; check for BOLA/IDOR/auth gaps.`);
52238
+ if (nextSteps.length === 0)
52239
+ nextSteps.push("Nothing exposed on the surface — try subdomain enumeration, port scan (active), or request source access for EAGLE-EYE analysis.");
52240
+ nextSteps.push("Every confirmed finding: record with finding_create, attach confidence_score, redact secrets. A hit is a hypothesis until verified.");
52241
+ return {
52242
+ target: base,
52243
+ host,
52244
+ fingerprint,
52245
+ waf,
52246
+ tech: { detected: techs, server: fingerprint.server },
52247
+ versions,
52248
+ crawl,
52249
+ params,
52250
+ subdomains,
52251
+ dns_findings: dnsFindings,
52252
+ wayback_urls: wayback,
52253
+ api_endpoints: apiEndpoints,
52254
+ open_ports: openPorts,
52255
+ intel,
52256
+ next_steps: nextSteps
52133
52257
  };
52134
- return JSON.stringify(payload, null, 2);
52135
52258
  }
52136
52259
 
52137
52260
  // src/benchmark.ts
@@ -52374,7 +52497,7 @@ function createServer() {
52374
52497
  name: "blitzstrike",
52375
52498
  version: "1.0.0"
52376
52499
  }, {
52377
- instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates).\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: full one-call audit (scope gate -> triage -> chain enrichment " + " -> findings with tool manuals attached).\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- skill_lookup / read_skill / list_skills: 85 playbooks (WP/PHP 0-day, AD, " + " malware, mobile, cloud, etc.).\\n" + "- read_playbook / read_tool_manual / list_manuals: 270 tool manuals + engagement " + " playbooks.\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "IRON RULES:\\n" + "- Scope first; authorized targets only. Refuse out-of-scope or destructive/DoS work.\\n" + "- A hit is a hypothesis; a verified exploit (or proven data-flow) is the finding.\\n" + "- Prefer the one-call run_engagement path; fall back to granular tools when you " + " need finer control."
52500
+ instructions: "Blitz Strike is a universal penetration-testing toolbelt with three tiers — " + "BLITZ (reconnaissance / attack-surface mapping), EAGLE-EYE (source-to-sink " + "analysis), STRIKE (live validation) — plus a canonical evidence-first finding " + "engine and a multi-language taint engine.\\n\\n" + "CORE MENTAL MODEL:\\n" + "- A scanner hit is a HYPOTHESIS, not a finding. Live verification (or data-flow " + " proof) is the verdict. Never report an unverified hit as a vulnerability.\\n" + "- Distinguish REACHABLE from mere PRESENT: a sink is only a finding when tainted " + " input reaches it AND it is not neutralized by a sanitizer and not guarded by an " + " auth gate.\\n\\n" + "HOW TO RUN AN AUDIT (start here):\\n" + "1. SCOPE FIRST. Call scope_check(target, scope, mode) before ANY active testing. " + " Passive analysis (reading source, fingerprinting public pages) needs no gate; " + " active probing (exploitation, fuzzing, path brute-force) is blocked until scope " + " authorizes it.\\n" + "2. MAP THE SURFACE (BLITZ). For a source tree use blitz_scan (tree) or blitz_file " + " (single file) then enrich_scan to match sinks to escalation chains. For a live " + " URL use live_recon for a full pass (fingerprint/WAF/tech/version/crawler/params/" + " subdomains/API endpoints/intel correlation) — it returns next_steps. active_scan " + " is the lighter gated alternative. Both re-check scope internally.\\n" + "3. TRACE REACHABILITY (EAGLE-EYE). For every sink, determine whether attacker " + " input actually reaches it:\\n" + " - PHP/JS/TS/Python/Java source: taint_file (auto-detects language) or " + " taint_scan/taint_tree (PHP AST). list_languages shows what's supported.\\n" + " - Any language: trace_data_flow (window heuristic) and eagle_eye/eagle_grep " + " (function-scoped view of sinks + auth gates).\\n" + " A sink counts only if tainted input reaches it unsanitized and unguarded.\\n" + "4. VALIDATE (STRIKE). Confirm live with strike_verify using a MARKER plus a " + " NEGATIVE CONTROL plus a BASELINE. The verdict is deterministic: marker " + " reflected AND control inert = confirmed; both reflected = false_positive; " + " marker not reflected = unconfirmed. Feed the verdict to strike_resolve to " + " advance the finding lifecycle (hypothesis->validating->confirmed). Use " + " read_tool_manual for the exploit tool's manual, payload_lookup for payloads, " + " detect_waf/tech_correlation for context.\\n" + "5. RECORD + REPORT. Create a canonical finding with finding_create, advance it " + " through the lifecycle with finding_transition, attach confidence_score, and " + " redact any secrets before persisting. Only report findings that survived " + " verification.\\n\\n" + "SUPPORTING LAYERS:\\n" + "- run_engagement: ONE call does the whole audit (no babysitting). Source path -> " + " full static pipeline + inline report (markdown/JSON/SARIF). URL target -> " + " autonomous live marker sweep + confirmed findings + report. Prefer this first.\\n" + "- list_languages / taint_file: multi-language taint (PHP, JS/TS, Python, Java).\\n" + "- list_chains: 57 escalation chains (source -> sink -> impact).\\n" + "- tool_lookup / ensure_tool / list_tools: 130-tool catalog, auto-install.\\n" + "- skill_lookup / read_skill / list_skills: 85 playbooks (WP/PHP 0-day, AD, " + " malware, mobile, cloud, etc.).\\n" + "- read_playbook / read_tool_manual / list_manuals: 270 tool manuals + engagement " + " playbooks.\\n" + "- detect_waf / tech_correlation / cve_correlation / port_correlation: signature " + " + correlation intelligence.\\n" + "- nvd_lookup / fofa_search: CVE + asset reconnaissance (FOFA needs env creds).\\n" + "- remember / memory_lookup / memory_list: persist + recall verified knowledge.\\n\\n" + "IRON RULES:\\n" + "- Scope first; authorized targets only. Refuse out-of-scope or destructive/DoS work.\\n" + "- A hit is a hypothesis; a verified exploit (or proven data-flow) is the finding.\\n" + "- Prefer the one-call run_engagement path; fall back to granular tools when you " + " need finer control."
52378
52501
  });
52379
52502
  server.registerTool("blitz_scan", {
52380
52503
  title: "BLITZ scan",
@@ -52563,7 +52686,7 @@ function createServer() {
52563
52686
  });
52564
52687
  server.registerTool("run_engagement", {
52565
52688
  title: "STRIKE full engagement",
52566
- description: "STRIKE: run a full 3-tier audit server-side (scope gate -> triage -> chain enrichment -> findings). One call.",
52689
+ description: "STRIKE: run a full audit in ONE call (no babysitting). Source path -> scope gate -> triage -> chain enrichment -> findings -> inline report (markdown+JSON+SARIF). URL target -> autonomous live marker sweep (strike_verify per parameter) -> confirmed findings -> inline report. Scope-gated: URL targets require an authorized scope.",
52567
52690
  inputSchema: {
52568
52691
  target: string2().describe("Source path or URL"),
52569
52692
  scope: string2().optional().describe("Scope (for URL targets)"),
@@ -52572,7 +52695,8 @@ function createServer() {
52572
52695
  remember: boolean2().optional().describe("Auto-capture matched chains to memory (default true)")
52573
52696
  }
52574
52697
  }, async ({ target, scope, mode, max_files, remember }) => {
52575
- const r = runEngagement(target, scope ?? "", mode ?? "bug-bounty", max_files ?? 2000, remember ?? true);
52698
+ const isUrl = /^https?:\/\//i.test(target);
52699
+ const r = isUrl ? await runLiveEngagement(target, scope ?? "", mode ?? "bug-bounty") : runEngagement(target, scope ?? "", mode ?? "bug-bounty", max_files ?? 2000, remember ?? true);
52576
52700
  return {
52577
52701
  content: [{ type: "text", text: JSON.stringify(r) }]
52578
52702
  };