blitzstrike 1.0.23 → 1.0.25

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 (2) hide show
  1. package/dist/index.js +549 -68
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __create = Object.create;
4
3
  var __getProtoOf = Object.getPrototypeOf;
5
4
  var __defProp = Object.defineProperty;
@@ -44,7 +43,6 @@ var __esm = (fn, res, err) => () => {
44
43
  throw err[0];
45
44
  return res;
46
45
  };
47
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
46
 
49
47
  // node_modules/ajv/dist/compile/codegen/code.js
50
48
  var require_code = __commonJS(function(exports) {
@@ -14822,10 +14820,10 @@ var require_src = __commonJS(function(exports, module) {
14822
14820
  });
14823
14821
 
14824
14822
  // src/sync.ts
14825
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
14826
- import { join as join8 } from "node:path";
14823
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync2 } from "node:fs";
14824
+ import { join as join9 } from "node:path";
14827
14825
  import { homedir as homedir4 } from "node:os";
14828
- import { fileURLToPath as fileURLToPath6 } from "node:url";
14826
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
14829
14827
  import { spawnSync as spawnSync3 } from "node:child_process";
14830
14828
  function dirExists(p) {
14831
14829
  try {
@@ -14838,9 +14836,9 @@ function countFiles(dir) {
14838
14836
  try {
14839
14837
  let n = 0;
14840
14838
  const walk = (d) => {
14841
- for (const e of readdirSync4(d, { withFileTypes: true })) {
14839
+ for (const e of readdirSync5(d, { withFileTypes: true })) {
14842
14840
  if (e.isDirectory())
14843
- walk(join8(d, e.name));
14841
+ walk(join9(d, e.name));
14844
14842
  else
14845
14843
  n++;
14846
14844
  }
@@ -14854,8 +14852,8 @@ function countFiles(dir) {
14854
14852
  function syncData() {
14855
14853
  if (!dirExists(DATA_ROOT2))
14856
14854
  mkdirSync3(DATA_ROOT2, { recursive: true });
14857
- const payloadsPath = join8(DATA_ROOT2, "payloads");
14858
- const templatesPath = join8(DATA_ROOT2, "templates");
14855
+ const payloadsPath = join9(DATA_ROOT2, "payloads");
14856
+ const templatesPath = join9(DATA_ROOT2, "templates");
14859
14857
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
14860
14858
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
14861
14859
  if (hasPayloads && hasTemplates) {
@@ -14865,8 +14863,8 @@ function syncData() {
14865
14863
  data_root: DATA_ROOT2
14866
14864
  };
14867
14865
  }
14868
- const localPayloads = join8(ROOT5, "payloads");
14869
- const localTemplates = join8(ROOT5, "templates");
14866
+ const localPayloads = join9(ROOT6, "payloads");
14867
+ const localTemplates = join9(ROOT6, "templates");
14870
14868
  let payloadsSynced = false;
14871
14869
  let templatesSynced = false;
14872
14870
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -14879,23 +14877,23 @@ function syncData() {
14879
14877
  }
14880
14878
  const gitOk = spawnSync3("git", ["--version"]).status === 0;
14881
14879
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
14882
- const tmp = join8(DATA_ROOT2, ".tmp-clone");
14880
+ const tmp = join9(DATA_ROOT2, ".tmp-clone");
14883
14881
  if (dirExists(tmp))
14884
- rmSync(tmp, { recursive: true, force: true });
14882
+ rmSync2(tmp, { recursive: true, force: true });
14885
14883
  const r = spawnSync3("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
14886
14884
  if (r.status === 0) {
14887
14885
  const sparse = spawnSync3("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
14888
14886
  if (sparse.status === 0) {
14889
14887
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
14890
- copyDir(join8(tmp, "payloads"), payloadsPath);
14888
+ copyDir(join9(tmp, "payloads"), payloadsPath);
14891
14889
  payloadsSynced = true;
14892
14890
  }
14893
14891
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14894
- copyDir(join8(tmp, "templates"), templatesPath);
14892
+ copyDir(join9(tmp, "templates"), templatesPath);
14895
14893
  templatesSynced = true;
14896
14894
  }
14897
14895
  }
14898
- rmSync(tmp, { recursive: true, force: true });
14896
+ rmSync2(tmp, { recursive: true, force: true });
14899
14897
  }
14900
14898
  }
14901
14899
  return {
@@ -14912,19 +14910,19 @@ function copyDir(src, dst) {
14912
14910
  }
14913
14911
  function manualCopy(src, dst) {
14914
14912
  mkdirSync3(dst, { recursive: true });
14915
- for (const e of readdirSync4(src, { withFileTypes: true })) {
14916
- const s = join8(src, e.name);
14917
- const d = join8(dst, e.name);
14913
+ for (const e of readdirSync5(src, { withFileTypes: true })) {
14914
+ const s = join9(src, e.name);
14915
+ const d = join9(dst, e.name);
14918
14916
  if (e.isDirectory())
14919
14917
  manualCopy(s, d);
14920
14918
  else
14921
- writeFileSync2(d, readFileSync10(s));
14919
+ writeFileSync4(d, readFileSync11(s));
14922
14920
  }
14923
14921
  }
14924
- var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
14922
+ var ROOT6, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
14925
14923
  var init_sync = __esm(() => {
14926
- ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
14927
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join8(homedir4(), ".blitzstrike", "data");
14924
+ ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
14925
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join9(homedir4(), ".blitzstrike", "data");
14928
14926
  });
14929
14927
 
14930
14928
  // node_modules/zod/v3/helpers/util.js
@@ -29649,7 +29647,7 @@ import { join as join4 } from "node:path";
29649
29647
  import { fileURLToPath as fileURLToPath2 } from "node:url";
29650
29648
 
29651
29649
  // src/memory.ts
29652
- import { readFileSync as readFileSync2, appendFileSync, mkdirSync, existsSync } from "node:fs";
29650
+ import { readFileSync as readFileSync2, appendFileSync, mkdirSync, existsSync, writeFileSync } from "node:fs";
29653
29651
  import { join as join2 } from "node:path";
29654
29652
  import { createHash } from "node:crypto";
29655
29653
  import { homedir } from "node:os";
@@ -29762,7 +29760,6 @@ function listMemory() {
29762
29760
  };
29763
29761
  }
29764
29762
  function forget(id) {
29765
- const { writeFileSync } = __require("node:fs");
29766
29763
  const entries = loadMemory();
29767
29764
  const kept = entries.filter((e) => e.id !== id);
29768
29765
  if (kept.length === entries.length) {
@@ -50392,6 +50389,423 @@ function resolveFinding(finding, verdict) {
50392
50389
  }
50393
50390
  }
50394
50391
 
50392
+ // src/cvss.ts
50393
+ var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
50394
+ var AC_WEIGHT = { L: 0.77, H: 0.44 };
50395
+ var PR_WEIGHT = {
50396
+ U: { N: 0.85, L: 0.62, H: 0.27 },
50397
+ C: { N: 0.85, L: 0.68, H: 0.5 }
50398
+ };
50399
+ var UI_WEIGHT = { N: 0.85, R: 0.62 };
50400
+ var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
50401
+ function roundup(value) {
50402
+ return Math.ceil(value * 10) / 10;
50403
+ }
50404
+ function cvssBaseScore(input) {
50405
+ const AV = input.AV ?? "N";
50406
+ const AC = input.AC ?? "L";
50407
+ const PR = input.PR ?? "N";
50408
+ const UI = input.UI ?? "N";
50409
+ const S = input.S ?? "U";
50410
+ const C = input.C ?? "N";
50411
+ const I = input.I ?? "N";
50412
+ const A = input.A ?? "N";
50413
+ const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
50414
+ let impact;
50415
+ if (S === "U") {
50416
+ impact = 6.42 * iscBase;
50417
+ } else {
50418
+ impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
50419
+ }
50420
+ const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
50421
+ if (impact <= 0)
50422
+ return 0;
50423
+ if (S === "U") {
50424
+ return roundup(Math.min(impact + exploitability, 10));
50425
+ }
50426
+ return roundup(Math.min(1.08 * (impact + exploitability), 10));
50427
+ }
50428
+ function cvssVector(input) {
50429
+ const order = [
50430
+ ["AV", input.AV ?? "N"],
50431
+ ["AC", input.AC ?? "L"],
50432
+ ["PR", input.PR ?? "N"],
50433
+ ["UI", input.UI ?? "N"],
50434
+ ["S", input.S ?? "U"],
50435
+ ["C", input.C ?? "N"],
50436
+ ["I", input.I ?? "N"],
50437
+ ["A", input.A ?? "N"]
50438
+ ];
50439
+ return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
50440
+ }
50441
+ function cvssSeverity(score) {
50442
+ if (score === 0)
50443
+ return "none";
50444
+ if (score < 4)
50445
+ return "low";
50446
+ if (score < 7)
50447
+ return "medium";
50448
+ if (score < 9)
50449
+ return "high";
50450
+ return "critical";
50451
+ }
50452
+ function cvssAssess(input) {
50453
+ const score = cvssBaseScore(input);
50454
+ return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
50455
+ }
50456
+
50457
+ // src/dedup.ts
50458
+ function rootCauseSignature(finding) {
50459
+ const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
50460
+ const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
50461
+ const cwe = (finding.classification?.cwe ?? "").toUpperCase();
50462
+ return [sinkType, sourceType, cwe].filter(Boolean).join("|");
50463
+ }
50464
+ function dedupFindings(findings) {
50465
+ const groups = new Map;
50466
+ for (const f of findings) {
50467
+ const sig = rootCauseSignature(f);
50468
+ const existing = groups.get(sig);
50469
+ if (existing) {
50470
+ existing.count += 1;
50471
+ existing.findings.push(f);
50472
+ } else {
50473
+ groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
50474
+ }
50475
+ }
50476
+ return [...groups.values()];
50477
+ }
50478
+ function uniqueFindings(findings) {
50479
+ return dedupFindings(findings).map((g) => g.representative);
50480
+ }
50481
+
50482
+ // src/report.ts
50483
+ function summarize(findings) {
50484
+ const byStatus = {};
50485
+ const bySeverity = {};
50486
+ let evidenceArtifacts = 0;
50487
+ for (const f of findings) {
50488
+ byStatus[f.status] = (byStatus[f.status] ?? 0) + 1;
50489
+ const sev = f.classification?.severity ?? "informational";
50490
+ bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
50491
+ evidenceArtifacts += f.evidence?.reduce((n, e) => n + (e.artifacts?.length ?? 0), 0) ?? 0;
50492
+ }
50493
+ const groups = dedupFindings(findings);
50494
+ return {
50495
+ total_findings: findings.length,
50496
+ unique_root_causes: groups.length,
50497
+ by_status: byStatus,
50498
+ by_severity: bySeverity,
50499
+ confirmed: byStatus["confirmed"] ?? 0,
50500
+ false_positives: byStatus["false_positive"] ?? 0,
50501
+ evidence_artifacts: evidenceArtifacts
50502
+ };
50503
+ }
50504
+ function severityLabel(sev) {
50505
+ const s = sev.toUpperCase();
50506
+ if (s === "CRITICAL")
50507
+ return "\uD83D\uDD34 CRITICAL";
50508
+ if (s === "HIGH")
50509
+ return "\uD83D\uDFE0 HIGH";
50510
+ if (s === "MEDIUM")
50511
+ return "\uD83D\uDFE1 MEDIUM";
50512
+ if (s === "LOW")
50513
+ return "\uD83D\uDD35 LOW";
50514
+ return "⚪ INFORMATIONAL";
50515
+ }
50516
+ function reportMarkdown(findings, opts = {}) {
50517
+ const summary = summarize(findings);
50518
+ const lines = [];
50519
+ lines.push(`# ${opts.title ?? "Blitz Strike — Security Assessment Report"}`);
50520
+ lines.push("");
50521
+ lines.push(`- Scope: ${opts.scope ?? "(unset)"}`);
50522
+ lines.push(`- Blitz Strike version: ${opts.version ?? "unknown"}`);
50523
+ if (opts.include_timestamp)
50524
+ lines.push(`- Generated: ${new Date().toISOString()}`);
50525
+ lines.push("");
50526
+ lines.push("## Summary");
50527
+ lines.push("");
50528
+ lines.push(`| Metric | Value |`);
50529
+ lines.push(`|--------|-------|`);
50530
+ lines.push(`| Total findings | ${summary.total_findings} |`);
50531
+ lines.push(`| Unique root causes | ${summary.unique_root_causes} |`);
50532
+ lines.push(`| Confirmed | ${summary.confirmed} |`);
50533
+ lines.push(`| False positives | ${summary.false_positives} |`);
50534
+ lines.push(`| Evidence artifacts | ${summary.evidence_artifacts} |`);
50535
+ lines.push("");
50536
+ lines.push("### By status");
50537
+ lines.push("");
50538
+ for (const [status, n] of Object.entries(summary.by_status).sort()) {
50539
+ lines.push(`- ${status}: ${n}`);
50540
+ }
50541
+ lines.push("");
50542
+ lines.push("### By severity");
50543
+ lines.push("");
50544
+ for (const [sev, n] of Object.entries(summary.by_severity).sort()) {
50545
+ lines.push(`- ${sev}: ${n}`);
50546
+ }
50547
+ lines.push("");
50548
+ lines.push("## Findings");
50549
+ lines.push("");
50550
+ if (findings.length === 0) {
50551
+ lines.push("_No findings._");
50552
+ return lines.join(`
50553
+ `) + `
50554
+ `;
50555
+ }
50556
+ for (const f of findings) {
50557
+ lines.push(`### ${f.id} — ${f.title} [${severityLabel(f.classification?.severity ?? "informational")}]`);
50558
+ lines.push("");
50559
+ lines.push(`- **Status**: \`${f.status}\``);
50560
+ lines.push(`- **Confidence**: ${f.confidence} (${f.confidence_level})`);
50561
+ if (f.classification?.cwe)
50562
+ lines.push(`- **CWE**: ${f.classification.cwe}`);
50563
+ if (f.classification?.cvss_score != null)
50564
+ lines.push(`- **CVSS**: ${f.classification.cvss_score} (${f.classification.cvss_vector ?? ""}) — ${cvssSeverity(f.classification.cvss_score)}`);
50565
+ lines.push(`- **Source**: ${f.source?.type} \`${f.source?.name ?? ""}\``);
50566
+ lines.push(`- **Sink**: ${f.sink?.type}${f.sink?.symbol ? ` \`${f.sink.symbol}\`` : ""}`);
50567
+ if (f.target?.host || f.target?.endpoint)
50568
+ lines.push(`- **Target**: ${[f.target.host, f.target.endpoint].filter(Boolean).join("")}`);
50569
+ lines.push(`- **Evidence**: ${f.evidence?.length ?? 0} record(s)`);
50570
+ if (f.remediation && Object.keys(f.remediation).length) {
50571
+ lines.push(`- **Note**: ${JSON.stringify(f.remediation)}`);
50572
+ }
50573
+ lines.push("");
50574
+ }
50575
+ return lines.join(`
50576
+ `) + `
50577
+ `;
50578
+ }
50579
+ function reportJson(findings, opts = {}) {
50580
+ const summary = summarize(findings);
50581
+ const payload = {
50582
+ title: opts.title ?? "Blitz Strike — Security Assessment Report",
50583
+ scope: opts.scope ?? null,
50584
+ version: opts.version ?? null,
50585
+ summary,
50586
+ findings,
50587
+ integrity: sha256(JSON.stringify({ summary, findings }))
50588
+ };
50589
+ return JSON.stringify(payload, null, 2);
50590
+ }
50591
+
50592
+ // src/benchmark.ts
50593
+ import { readFileSync as readFileSync9, readdirSync as readdirSync4, writeFileSync as writeFileSync2, rmSync } from "node:fs";
50594
+ import { join as join7 } from "node:path";
50595
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
50596
+ var ROOT4 = join7(fileURLToPath5(new URL(".", import.meta.url)), "..");
50597
+ var CORPUS = join7(ROOT4, "bench", "corpus");
50598
+ function parseCorpusFile(path) {
50599
+ let text;
50600
+ try {
50601
+ text = readFileSync9(path, "utf8");
50602
+ } catch {
50603
+ return null;
50604
+ }
50605
+ const lines = text.split(`
50606
+ `);
50607
+ let id = "";
50608
+ let language = "php";
50609
+ let vulnerable = true;
50610
+ let sinkType;
50611
+ let code = "";
50612
+ let inCode = false;
50613
+ let codeLang = "";
50614
+ for (const line of lines) {
50615
+ const m = line.match(/^\s*-\s*(id|language|vulnerable|sink_type)\s*:\s*(.+)$/i);
50616
+ if (!inCode && m) {
50617
+ const key = m[1].toLowerCase();
50618
+ const val = m[2].trim();
50619
+ if (key === "id")
50620
+ id = val;
50621
+ else if (key === "language")
50622
+ language = val.toLowerCase();
50623
+ else if (key === "vulnerable")
50624
+ vulnerable = val === "true";
50625
+ else if (key === "sink_type")
50626
+ sinkType = val;
50627
+ continue;
50628
+ }
50629
+ const start = line.match(/^```(\w*)/);
50630
+ if (start && !inCode) {
50631
+ inCode = true;
50632
+ codeLang = start[1] || "php";
50633
+ continue;
50634
+ }
50635
+ if (inCode && line.trim() === "```")
50636
+ break;
50637
+ if (inCode)
50638
+ code += line + `
50639
+ `;
50640
+ }
50641
+ if (!id)
50642
+ id = path.split("/").pop().replace(/\.md$/, "");
50643
+ return { id, language: language || codeLang || "php", vulnerable, sink_type: sinkType, code: code.trim() };
50644
+ }
50645
+ function loadCorpus() {
50646
+ let entries = [];
50647
+ try {
50648
+ for (const f of readdirSync4(CORPUS)) {
50649
+ if (!f.endsWith(".md"))
50650
+ continue;
50651
+ const e = parseCorpusFile(join7(CORPUS, f));
50652
+ if (e)
50653
+ entries.push(e);
50654
+ }
50655
+ } catch {
50656
+ entries = [];
50657
+ }
50658
+ return entries;
50659
+ }
50660
+ function detect(entry) {
50661
+ const lang = entry.language;
50662
+ if (lang === "php") {
50663
+ const tmp = join7(ROOT4, "bench", ".tmp.php");
50664
+ writeFileSync2(tmp, entry.code);
50665
+ const r = analyzeTaint(tmp);
50666
+ rmSync(tmp, { force: true });
50667
+ if (r.findings.length > 0)
50668
+ return { detected: true, sinkKind: r.findings[0].sink };
50669
+ return { detected: false };
50670
+ }
50671
+ const ext = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" }[lang] ?? ".php";
50672
+ const r = analyzeTaintUniversal(entry.code, "bench." + ext);
50673
+ if (r.findings.length > 0)
50674
+ return { detected: true, sinkKind: r.findings[0].sink };
50675
+ return { detected: false };
50676
+ }
50677
+ function runBenchmark() {
50678
+ const entries = loadCorpus();
50679
+ const results = entries.map((entry) => {
50680
+ const { detected, sinkKind } = detect(entry);
50681
+ const outcome = entry.vulnerable ? detected ? "tp" : "fn" : detected ? "fp" : "tn";
50682
+ return { entry, detected, sink_kind: sinkKind, outcome };
50683
+ });
50684
+ const tp = results.filter((r) => r.outcome === "tp").length;
50685
+ const tn = results.filter((r) => r.outcome === "tn").length;
50686
+ const fp = results.filter((r) => r.outcome === "fp").length;
50687
+ const fn = results.filter((r) => r.outcome === "fn").length;
50688
+ const total = results.length;
50689
+ const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
50690
+ const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
50691
+ const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
50692
+ return {
50693
+ total_cases: total,
50694
+ tp,
50695
+ tn,
50696
+ fp,
50697
+ fn,
50698
+ detection_rate: Number(detectionRate.toFixed(3)),
50699
+ false_positive_rate: Number(falsePositiveRate.toFixed(3)),
50700
+ precision: Number(precision.toFixed(3)),
50701
+ results: results.map((r) => ({ id: r.entry.id, language: r.entry.language, outcome: r.outcome, sink: r.sink_kind }))
50702
+ };
50703
+ }
50704
+
50705
+ // src/coverage.ts
50706
+ var SINK_CLASSES = [
50707
+ "sql_execution",
50708
+ "code_execution",
50709
+ "command_execution",
50710
+ "file_operations",
50711
+ "file_inclusion",
50712
+ "deserialization",
50713
+ "http_request",
50714
+ "redirect",
50715
+ "html_render",
50716
+ "xml_processing",
50717
+ "archive_extraction",
50718
+ "path_traversal"
50719
+ ];
50720
+ var PHP_SINKS2 = {
50721
+ sql_execution: true,
50722
+ code_execution: true,
50723
+ command_execution: true,
50724
+ file_operations: true,
50725
+ file_inclusion: true,
50726
+ deserialization: true,
50727
+ http_request: true,
50728
+ redirect: true,
50729
+ html_render: true,
50730
+ xml_processing: false,
50731
+ archive_extraction: false,
50732
+ path_traversal: true
50733
+ };
50734
+ var JS_SINKS2 = {
50735
+ sql_execution: true,
50736
+ code_execution: true,
50737
+ command_execution: true,
50738
+ file_operations: true,
50739
+ file_inclusion: true,
50740
+ deserialization: false,
50741
+ http_request: true,
50742
+ redirect: true,
50743
+ html_render: true,
50744
+ xml_processing: false,
50745
+ archive_extraction: false,
50746
+ path_traversal: true
50747
+ };
50748
+ var PY_SINKS2 = {
50749
+ sql_execution: true,
50750
+ code_execution: true,
50751
+ command_execution: true,
50752
+ file_operations: true,
50753
+ file_inclusion: false,
50754
+ deserialization: true,
50755
+ http_request: false,
50756
+ redirect: true,
50757
+ html_render: true,
50758
+ xml_processing: false,
50759
+ archive_extraction: false,
50760
+ path_traversal: true
50761
+ };
50762
+ var JAVA_SINKS2 = {
50763
+ sql_execution: true,
50764
+ code_execution: false,
50765
+ command_execution: true,
50766
+ file_operations: true,
50767
+ file_inclusion: false,
50768
+ deserialization: true,
50769
+ http_request: true,
50770
+ redirect: true,
50771
+ html_render: true,
50772
+ xml_processing: false,
50773
+ archive_extraction: false,
50774
+ path_traversal: true
50775
+ };
50776
+ var SINK_BY_LANG = {
50777
+ php: PHP_SINKS2,
50778
+ javascript: JS_SINKS2,
50779
+ python: PY_SINKS2,
50780
+ java: JAVA_SINKS2
50781
+ };
50782
+ function coverageMatrix() {
50783
+ const langs = listLanguages();
50784
+ const sinkCoverage = SINK_CLASSES.map((sink) => {
50785
+ const covered = langs.map((l) => l.language).filter((lang) => SINK_BY_LANG[lang]?.[sink] === true);
50786
+ return { sink, languages: covered };
50787
+ });
50788
+ let coveredPairs = 0;
50789
+ for (const lang of langs) {
50790
+ for (const sink of SINK_CLASSES) {
50791
+ if (SINK_BY_LANG[lang.language]?.[sink])
50792
+ coveredPairs += 1;
50793
+ }
50794
+ }
50795
+ const totalPairs = langs.length * SINK_CLASSES.length;
50796
+ return {
50797
+ languages: langs,
50798
+ sink_coverage: sinkCoverage,
50799
+ counts: {
50800
+ languages: langs.length,
50801
+ sink_classes: SINK_CLASSES.length,
50802
+ covered_pairs: coveredPairs,
50803
+ total_pairs: totalPairs
50804
+ },
50805
+ coverage_ratio: Number((coveredPairs / totalPairs).toFixed(3))
50806
+ };
50807
+ }
50808
+
50395
50809
  // src/server.ts
50396
50810
  function createServer() {
50397
50811
  const server = new McpServer({
@@ -51084,6 +51498,73 @@ function createServer() {
51084
51498
  });
51085
51499
  return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
51086
51500
  });
51501
+ server.registerTool("cvss_score", {
51502
+ title: "Compute CVSS v3.1 base score",
51503
+ description: "CVSS: compute a deterministic CVSS v3.1 base score + vector + severity from metric values (AV/AC/PR/UI/S/C/I/A). Self-computed, not read from NVD.",
51504
+ inputSchema: {
51505
+ AV: string2().optional().describe("Attack Vector: N/A/L/P"),
51506
+ AC: string2().optional().describe("Attack Complexity: L/H"),
51507
+ PR: string2().optional().describe("Privileges Required: N/L/H"),
51508
+ UI: string2().optional().describe("User Interaction: N/R"),
51509
+ S: string2().optional().describe("Scope: U/C"),
51510
+ C: string2().optional().describe("Confidentiality: H/L/N"),
51511
+ I: string2().optional().describe("Integrity: H/L/N"),
51512
+ A: string2().optional().describe("Availability: H/L/N")
51513
+ }
51514
+ }, async (input) => {
51515
+ return { content: [{ type: "text", text: JSON.stringify(cvssAssess(input)) }] };
51516
+ });
51517
+ server.registerTool("dedup_findings", {
51518
+ title: "Deduplicate findings by root cause",
51519
+ description: "FINDINGS: collapse a list of findings that share a root cause (sink type + source type + CWE) into one group per root cause.",
51520
+ inputSchema: {
51521
+ findings: string2().describe("JSON array of Finding objects")
51522
+ }
51523
+ }, async ({ findings }) => {
51524
+ let list;
51525
+ try {
51526
+ list = JSON.parse(findings);
51527
+ } catch {
51528
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
51529
+ }
51530
+ const groups = dedupFindings(list);
51531
+ const unique = uniqueFindings(list);
51532
+ return { content: [{ type: "text", text: JSON.stringify({ groups: groups.map((g) => ({ signature: g.signature, count: g.count, representative: g.representative.id })), unique_findings: unique.map((f) => f.id) }) }] };
51533
+ });
51534
+ server.registerTool("generate_report", {
51535
+ title: "Generate a reproducible report",
51536
+ description: "REPORT: emit a deterministic markdown (or JSON) report from canonical Findings, with summary + integrity hash. Reproducible: same findings -> byte-identical output.",
51537
+ inputSchema: {
51538
+ findings: string2().describe("JSON array of Finding objects"),
51539
+ format: _enum(["markdown", "json"]).optional().describe("Report format"),
51540
+ title: string2().optional().describe("Report title"),
51541
+ scope: string2().optional().describe("Scope description")
51542
+ }
51543
+ }, async ({ findings, format, title, scope }) => {
51544
+ let list;
51545
+ try {
51546
+ list = JSON.parse(findings);
51547
+ } catch {
51548
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
51549
+ }
51550
+ const opts = { title, scope, version: "1.0.23" };
51551
+ const out = (format ?? "markdown") === "json" ? reportJson(list, opts) : reportMarkdown(list, opts);
51552
+ return { content: [{ type: "text", text: out }] };
51553
+ });
51554
+ server.registerTool("run_benchmark", {
51555
+ title: "Run the benchmark suite",
51556
+ description: "BENCHMARK: run the labelled corpus and report detection rate, false-positive rate, and precision. The quality metric, not the tool count.",
51557
+ inputSchema: {}
51558
+ }, async () => {
51559
+ return { content: [{ type: "text", text: JSON.stringify(runBenchmark()) }] };
51560
+ });
51561
+ server.registerTool("coverage_matrix", {
51562
+ title: "Report coverage matrix",
51563
+ description: "BENCHMARK: enumerate language × sink-class coverage (which languages detect which sink types), plus the coverage ratio.",
51564
+ inputSchema: {}
51565
+ }, async () => {
51566
+ return { content: [{ type: "text", text: JSON.stringify(coverageMatrix()) }] };
51567
+ });
51087
51568
  return server;
51088
51569
  }
51089
51570
  async function serve() {
@@ -51093,12 +51574,12 @@ async function serve() {
51093
51574
  }
51094
51575
 
51095
51576
  // src/cli.ts
51096
- import { readFileSync as readFileSync9, existsSync as existsSync5, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
51097
- import { join as join7, dirname } from "node:path";
51577
+ import { readFileSync as readFileSync10, existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "node:fs";
51578
+ import { join as join8, dirname } from "node:path";
51098
51579
  import { homedir as homedir3 } from "node:os";
51099
- import { fileURLToPath as fileURLToPath5 } from "node:url";
51580
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
51100
51581
  import { spawnSync as spawnSync2 } from "node:child_process";
51101
- var ROOT4 = join7(fileURLToPath5(new URL(".", import.meta.url)), "..");
51582
+ var ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
51102
51583
  function which(cmd) {
51103
51584
  return spawnSync2(`command -v ${cmd}`, { shell: true }).status === 0;
51104
51585
  }
@@ -51133,7 +51614,7 @@ function runDoctor() {
51133
51614
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
51134
51615
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
51135
51616
  });
51136
- const dataOk = existsSync5(join7(ROOT4, "chains.json")) && existsSync5(join7(ROOT4, "tools-catalog.json"));
51617
+ const dataOk = existsSync5(join8(ROOT5, "chains.json")) && existsSync5(join8(ROOT5, "tools-catalog.json"));
51137
51618
  issues.push({
51138
51619
  name: "Data layers (chains + tools-catalog)",
51139
51620
  status: dataOk ? "ok" : "fail",
@@ -51161,21 +51642,21 @@ BlitzStrike — ${verdict}`);
51161
51642
  function resolveCommand() {
51162
51643
  if (which("blitzstrike"))
51163
51644
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
51164
- const src = join7(ROOT4, "src", "index.ts");
51645
+ const src = join8(ROOT5, "src", "index.ts");
51165
51646
  if (which("bun") && existsSync5(src))
51166
51647
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
51167
51648
  if (which("npx"))
51168
51649
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
51169
51650
  if (which("bunx"))
51170
51651
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
51171
- const dist = join7(ROOT4, "dist", "index.js");
51652
+ const dist = join8(ROOT5, "dist", "index.js");
51172
51653
  if (existsSync5(dist))
51173
51654
  return { command: "bun", args: [dist, "serve", "--mcp"] };
51174
51655
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
51175
51656
  }
51176
51657
  function readJson(p) {
51177
51658
  try {
51178
- return JSON.parse(readFileSync9(p, "utf8"));
51659
+ return JSON.parse(readFileSync10(p, "utf8"));
51179
51660
  } catch {
51180
51661
  return null;
51181
51662
  }
@@ -51190,7 +51671,7 @@ function jsonMcpServersWrite(p) {
51190
51671
  ...existing.mcpServers ?? {},
51191
51672
  blitzstrike: { command, args }
51192
51673
  };
51193
- writeFileSync(p, JSON.stringify(existing, null, 2));
51674
+ writeFileSync3(p, JSON.stringify(existing, null, 2));
51194
51675
  };
51195
51676
  }
51196
51677
  function opencodeWrite(p) {
@@ -51203,22 +51684,22 @@ function opencodeWrite(p) {
51203
51684
  ...existing.mcp ?? {},
51204
51685
  blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
51205
51686
  };
51206
- writeFileSync(p, JSON.stringify(existing, null, 2));
51687
+ writeFileSync3(p, JSON.stringify(existing, null, 2));
51207
51688
  copyOpenCodeAgents();
51208
51689
  };
51209
51690
  }
51210
51691
  function copyOpenCodeAgents() {
51211
- const srcDir = join7(ROOT4, "opencode-agents");
51212
- const dstDir = join7(homedir3(), ".config", "opencode", "agents");
51692
+ const srcDir = join8(ROOT5, "opencode-agents");
51693
+ const dstDir = join8(homedir3(), ".config", "opencode", "agents");
51213
51694
  if (!existsSync5(srcDir))
51214
51695
  return;
51215
51696
  if (!existsSync5(dstDir))
51216
51697
  mkdirSync2(dstDir, { recursive: true });
51217
51698
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
51218
- const src = join7(srcDir, f);
51219
- const dst = join7(dstDir, f);
51699
+ const src = join8(srcDir, f);
51700
+ const dst = join8(dstDir, f);
51220
51701
  if (existsSync5(src)) {
51221
- writeFileSync(dst, readFileSync9(src, "utf8"));
51702
+ writeFileSync3(dst, readFileSync10(src, "utf8"));
51222
51703
  }
51223
51704
  }
51224
51705
  }
@@ -51227,7 +51708,7 @@ function codexWrite(p) {
51227
51708
  const dir = dirname(p);
51228
51709
  if (!existsSync5(dir))
51229
51710
  mkdirSync2(dir, { recursive: true });
51230
- let existing = existsSync5(p) ? readFileSync9(p, "utf8") : "";
51711
+ let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
51231
51712
  if (!existing.trimEnd().endsWith(`
51232
51713
  `))
51233
51714
  existing += `
@@ -51238,7 +51719,7 @@ function codexWrite(p) {
51238
51719
  command = "${command}"
51239
51720
  args = ${JSON.stringify(args)}
51240
51721
  `;
51241
- writeFileSync(p, existing);
51722
+ writeFileSync3(p, existing);
51242
51723
  };
51243
51724
  }
51244
51725
  function hermesWrite(p) {
@@ -51246,7 +51727,7 @@ function hermesWrite(p) {
51246
51727
  const dir = dirname(p);
51247
51728
  if (!existsSync5(dir))
51248
51729
  mkdirSync2(dir, { recursive: true });
51249
- let existing = existsSync5(p) ? readFileSync9(p, "utf8") : "";
51730
+ let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
51250
51731
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
51251
51732
  if (!existing.trimEnd().endsWith(`
51252
51733
  `))
@@ -51267,80 +51748,80 @@ mcp_servers:
51267
51748
  args: ${JSON.stringify(args)}
51268
51749
  `;
51269
51750
  }
51270
- writeFileSync(p, existing);
51751
+ writeFileSync3(p, existing);
51271
51752
  };
51272
51753
  }
51273
51754
  function detectAgents() {
51274
51755
  const home = homedir3();
51275
51756
  const agents = [];
51276
- const claudeJson = join7(home, ".claude.json");
51757
+ const claudeJson = join8(home, ".claude.json");
51277
51758
  agents.push({
51278
51759
  name: "Claude Code",
51279
51760
  detectPath: claudeJson,
51280
51761
  installed: () => existsSync5(claudeJson) || which("claude"),
51281
51762
  write: jsonMcpServersWrite(claudeJson)
51282
51763
  });
51283
- const claudeDesktop = join7(home, ".config", "Claude", "claude_desktop_config.json");
51764
+ const claudeDesktop = join8(home, ".config", "Claude", "claude_desktop_config.json");
51284
51765
  agents.push({
51285
51766
  name: "Claude Desktop",
51286
51767
  detectPath: claudeDesktop,
51287
51768
  installed: () => existsSync5(claudeDesktop),
51288
51769
  write: jsonMcpServersWrite(claudeDesktop)
51289
51770
  });
51290
- const cursor = join7(home, ".cursor", "mcp.json");
51771
+ const cursor = join8(home, ".cursor", "mcp.json");
51291
51772
  agents.push({
51292
51773
  name: "Cursor",
51293
51774
  detectPath: cursor,
51294
- installed: () => existsSync5(join7(home, ".cursor")) || which("cursor"),
51775
+ installed: () => existsSync5(join8(home, ".cursor")) || which("cursor"),
51295
51776
  write: jsonMcpServersWrite(cursor)
51296
51777
  });
51297
- const opencode = join7(home, ".config", "opencode", "opencode.jsonc");
51778
+ const opencode = join8(home, ".config", "opencode", "opencode.jsonc");
51298
51779
  agents.push({
51299
51780
  name: "OpenCode",
51300
51781
  detectPath: opencode,
51301
- installed: () => existsSync5(join7(home, ".config", "opencode")) || which("opencode"),
51782
+ installed: () => existsSync5(join8(home, ".config", "opencode")) || which("opencode"),
51302
51783
  write: opencodeWrite(opencode)
51303
51784
  });
51304
- const codex = join7(home, ".codex", "config.toml");
51785
+ const codex = join8(home, ".codex", "config.toml");
51305
51786
  agents.push({
51306
51787
  name: "Codex",
51307
51788
  detectPath: codex,
51308
- installed: () => existsSync5(join7(home, ".codex")) || which("codex"),
51789
+ installed: () => existsSync5(join8(home, ".codex")) || which("codex"),
51309
51790
  write: codexWrite(codex)
51310
51791
  });
51311
- const hermes = join7(home, ".hermes", "config.yaml");
51792
+ const hermes = join8(home, ".hermes", "config.yaml");
51312
51793
  agents.push({
51313
51794
  name: "Hermes",
51314
51795
  detectPath: hermes,
51315
- installed: () => existsSync5(join7(home, ".hermes")) || which("hermes"),
51796
+ installed: () => existsSync5(join8(home, ".hermes")) || which("hermes"),
51316
51797
  write: hermesWrite(hermes)
51317
51798
  });
51318
- const gemini = join7(home, ".gemini", "settings.json");
51799
+ const gemini = join8(home, ".gemini", "settings.json");
51319
51800
  agents.push({
51320
51801
  name: "Gemini CLI",
51321
51802
  detectPath: gemini,
51322
- installed: () => existsSync5(join7(home, ".gemini")) || which("gemini"),
51803
+ installed: () => existsSync5(join8(home, ".gemini")) || which("gemini"),
51323
51804
  write: jsonMcpServersWrite(gemini)
51324
51805
  });
51325
- const windsurf = join7(home, ".codeium", "windsurf", "mcp_config.json");
51806
+ const windsurf = join8(home, ".codeium", "windsurf", "mcp_config.json");
51326
51807
  agents.push({
51327
51808
  name: "Windsurf",
51328
51809
  detectPath: windsurf,
51329
- installed: () => existsSync5(join7(home, ".codeium")) || which("windsurf"),
51810
+ installed: () => existsSync5(join8(home, ".codeium")) || which("windsurf"),
51330
51811
  write: jsonMcpServersWrite(windsurf)
51331
51812
  });
51332
- const copilot = join7(home, ".copilot", "mcp.json");
51813
+ const copilot = join8(home, ".copilot", "mcp.json");
51333
51814
  agents.push({
51334
51815
  name: "Copilot",
51335
51816
  detectPath: copilot,
51336
- installed: () => existsSync5(join7(home, ".copilot")),
51817
+ installed: () => existsSync5(join8(home, ".copilot")),
51337
51818
  write: jsonMcpServersWrite(copilot)
51338
51819
  });
51339
- const cline = join7(home, ".cline", "mcp_settings.json");
51820
+ const cline = join8(home, ".cline", "mcp_settings.json");
51340
51821
  agents.push({
51341
51822
  name: "Cline",
51342
51823
  detectPath: cline,
51343
- installed: () => existsSync5(join7(home, ".cline")),
51824
+ installed: () => existsSync5(join8(home, ".cline")),
51344
51825
  write: jsonMcpServersWrite(cline)
51345
51826
  });
51346
51827
  return agents;
@@ -51384,15 +51865,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
51384
51865
  }
51385
51866
 
51386
51867
  // src/index.ts
51387
- import { readFileSync as readFileSync11, existsSync as existsSync6 } from "node:fs";
51388
- import { join as join9 } from "node:path";
51389
- import { fileURLToPath as fileURLToPath7 } from "node:url";
51390
- var ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
51868
+ import { readFileSync as readFileSync12, existsSync as existsSync6 } from "node:fs";
51869
+ import { join as join10 } from "node:path";
51870
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
51871
+ var ROOT7 = join10(fileURLToPath8(new URL(".", import.meta.url)), "..");
51391
51872
  function readVersion() {
51392
51873
  try {
51393
- const p = join9(ROOT6, "package.json");
51874
+ const p = join10(ROOT7, "package.json");
51394
51875
  if (existsSync6(p))
51395
- return JSON.parse(readFileSync11(p, "utf8")).version ?? "1.0.0";
51876
+ return JSON.parse(readFileSync12(p, "utf8")).version ?? "1.0.0";
51396
51877
  } catch {}
51397
51878
  return "1.0.0";
51398
51879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,8 +29,9 @@
29
29
  "serve": "bun run src/index.ts serve",
30
30
  "doctor": "bun run src/index.ts doctor",
31
31
  "compile": "bun build src/index.ts --compile --outfile dist/blitzstrike",
32
- "test": "bun test",
33
- "prepublishOnly": "bun run typecheck && bun run build"
32
+ "test": "bun run test/verify.ts",
33
+ "prepublishOnly": "bun run typecheck && bun run build",
34
+ "bench": "bun run test/verify.ts"
34
35
  },
35
36
  "keywords": [
36
37
  "mcp",