blitzstrike 1.0.23 → 1.0.24

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 +539 -58
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -14822,10 +14822,10 @@ var require_src = __commonJS(function(exports, module) {
14822
14822
  });
14823
14823
 
14824
14824
  // 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";
14825
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync3, rmSync } from "node:fs";
14826
+ import { join as join9 } from "node:path";
14827
14827
  import { homedir as homedir4 } from "node:os";
14828
- import { fileURLToPath as fileURLToPath6 } from "node:url";
14828
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
14829
14829
  import { spawnSync as spawnSync3 } from "node:child_process";
14830
14830
  function dirExists(p) {
14831
14831
  try {
@@ -14838,9 +14838,9 @@ function countFiles(dir) {
14838
14838
  try {
14839
14839
  let n = 0;
14840
14840
  const walk = (d) => {
14841
- for (const e of readdirSync4(d, { withFileTypes: true })) {
14841
+ for (const e of readdirSync5(d, { withFileTypes: true })) {
14842
14842
  if (e.isDirectory())
14843
- walk(join8(d, e.name));
14843
+ walk(join9(d, e.name));
14844
14844
  else
14845
14845
  n++;
14846
14846
  }
@@ -14854,8 +14854,8 @@ function countFiles(dir) {
14854
14854
  function syncData() {
14855
14855
  if (!dirExists(DATA_ROOT2))
14856
14856
  mkdirSync3(DATA_ROOT2, { recursive: true });
14857
- const payloadsPath = join8(DATA_ROOT2, "payloads");
14858
- const templatesPath = join8(DATA_ROOT2, "templates");
14857
+ const payloadsPath = join9(DATA_ROOT2, "payloads");
14858
+ const templatesPath = join9(DATA_ROOT2, "templates");
14859
14859
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
14860
14860
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
14861
14861
  if (hasPayloads && hasTemplates) {
@@ -14865,8 +14865,8 @@ function syncData() {
14865
14865
  data_root: DATA_ROOT2
14866
14866
  };
14867
14867
  }
14868
- const localPayloads = join8(ROOT5, "payloads");
14869
- const localTemplates = join8(ROOT5, "templates");
14868
+ const localPayloads = join9(ROOT6, "payloads");
14869
+ const localTemplates = join9(ROOT6, "templates");
14870
14870
  let payloadsSynced = false;
14871
14871
  let templatesSynced = false;
14872
14872
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -14879,7 +14879,7 @@ function syncData() {
14879
14879
  }
14880
14880
  const gitOk = spawnSync3("git", ["--version"]).status === 0;
14881
14881
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
14882
- const tmp = join8(DATA_ROOT2, ".tmp-clone");
14882
+ const tmp = join9(DATA_ROOT2, ".tmp-clone");
14883
14883
  if (dirExists(tmp))
14884
14884
  rmSync(tmp, { recursive: true, force: true });
14885
14885
  const r = spawnSync3("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -14887,11 +14887,11 @@ function syncData() {
14887
14887
  const sparse = spawnSync3("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
14888
14888
  if (sparse.status === 0) {
14889
14889
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
14890
- copyDir(join8(tmp, "payloads"), payloadsPath);
14890
+ copyDir(join9(tmp, "payloads"), payloadsPath);
14891
14891
  payloadsSynced = true;
14892
14892
  }
14893
14893
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14894
- copyDir(join8(tmp, "templates"), templatesPath);
14894
+ copyDir(join9(tmp, "templates"), templatesPath);
14895
14895
  templatesSynced = true;
14896
14896
  }
14897
14897
  }
@@ -14912,19 +14912,19 @@ function copyDir(src, dst) {
14912
14912
  }
14913
14913
  function manualCopy(src, dst) {
14914
14914
  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);
14915
+ for (const e of readdirSync5(src, { withFileTypes: true })) {
14916
+ const s = join9(src, e.name);
14917
+ const d = join9(dst, e.name);
14918
14918
  if (e.isDirectory())
14919
14919
  manualCopy(s, d);
14920
14920
  else
14921
- writeFileSync2(d, readFileSync10(s));
14921
+ writeFileSync2(d, readFileSync11(s));
14922
14922
  }
14923
14923
  }
14924
- var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
14924
+ var ROOT6, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
14925
14925
  var init_sync = __esm(() => {
14926
- ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
14927
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join8(homedir4(), ".blitzstrike", "data");
14926
+ ROOT6 = join9(fileURLToPath7(new URL(".", import.meta.url)), "..");
14927
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join9(homedir4(), ".blitzstrike", "data");
14928
14928
  });
14929
14929
 
14930
14930
  // node_modules/zod/v3/helpers/util.js
@@ -50392,6 +50392,421 @@ function resolveFinding(finding, verdict) {
50392
50392
  }
50393
50393
  }
50394
50394
 
50395
+ // src/cvss.ts
50396
+ var AV_WEIGHT = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
50397
+ var AC_WEIGHT = { L: 0.77, H: 0.44 };
50398
+ var PR_WEIGHT = {
50399
+ U: { N: 0.85, L: 0.62, H: 0.27 },
50400
+ C: { N: 0.85, L: 0.68, H: 0.5 }
50401
+ };
50402
+ var UI_WEIGHT = { N: 0.85, R: 0.62 };
50403
+ var CIA_WEIGHT = { H: 0.56, L: 0.22, N: 0 };
50404
+ function roundup(value) {
50405
+ return Math.ceil(value * 10) / 10;
50406
+ }
50407
+ function cvssBaseScore(input) {
50408
+ const AV = input.AV ?? "N";
50409
+ const AC = input.AC ?? "L";
50410
+ const PR = input.PR ?? "N";
50411
+ const UI = input.UI ?? "N";
50412
+ const S = input.S ?? "U";
50413
+ const C = input.C ?? "N";
50414
+ const I = input.I ?? "N";
50415
+ const A = input.A ?? "N";
50416
+ const iscBase = 1 - (1 - CIA_WEIGHT[C]) * (1 - CIA_WEIGHT[I]) * (1 - CIA_WEIGHT[A]);
50417
+ let impact;
50418
+ if (S === "U") {
50419
+ impact = 6.42 * iscBase;
50420
+ } else {
50421
+ impact = 7.52 * (iscBase - 0.029) - 3.25 * Math.pow(iscBase - 0.02, 15);
50422
+ }
50423
+ const exploitability = 8.22 * AV_WEIGHT[AV] * AC_WEIGHT[AC] * PR_WEIGHT[S][PR] * UI_WEIGHT[UI];
50424
+ if (impact <= 0)
50425
+ return 0;
50426
+ if (S === "U") {
50427
+ return roundup(Math.min(impact + exploitability, 10));
50428
+ }
50429
+ return roundup(Math.min(1.08 * (impact + exploitability), 10));
50430
+ }
50431
+ function cvssVector(input) {
50432
+ const order = [
50433
+ ["AV", input.AV ?? "N"],
50434
+ ["AC", input.AC ?? "L"],
50435
+ ["PR", input.PR ?? "N"],
50436
+ ["UI", input.UI ?? "N"],
50437
+ ["S", input.S ?? "U"],
50438
+ ["C", input.C ?? "N"],
50439
+ ["I", input.I ?? "N"],
50440
+ ["A", input.A ?? "N"]
50441
+ ];
50442
+ return "CVSS:3.1/" + order.map(([k, v]) => `${k}:${v}`).join("/");
50443
+ }
50444
+ function cvssSeverity(score) {
50445
+ if (score === 0)
50446
+ return "none";
50447
+ if (score < 4)
50448
+ return "low";
50449
+ if (score < 7)
50450
+ return "medium";
50451
+ if (score < 9)
50452
+ return "high";
50453
+ return "critical";
50454
+ }
50455
+ function cvssAssess(input) {
50456
+ const score = cvssBaseScore(input);
50457
+ return { score, vector: cvssVector(input), severity: cvssSeverity(score) };
50458
+ }
50459
+
50460
+ // src/dedup.ts
50461
+ function rootCauseSignature(finding) {
50462
+ const sinkType = (finding.sink?.type ?? "unknown").toLowerCase();
50463
+ const sourceType = (finding.source?.type ?? "unknown").toLowerCase();
50464
+ const cwe = (finding.classification?.cwe ?? "").toUpperCase();
50465
+ return [sinkType, sourceType, cwe].filter(Boolean).join("|");
50466
+ }
50467
+ function dedupFindings(findings) {
50468
+ const groups = new Map;
50469
+ for (const f of findings) {
50470
+ const sig = rootCauseSignature(f);
50471
+ const existing = groups.get(sig);
50472
+ if (existing) {
50473
+ existing.count += 1;
50474
+ existing.findings.push(f);
50475
+ } else {
50476
+ groups.set(sig, { signature: sig, count: 1, representative: f, findings: [f] });
50477
+ }
50478
+ }
50479
+ return [...groups.values()];
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 } 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
+ const { writeFileSync, rmSync } = __require("node:fs");
50665
+ writeFileSync(tmp, entry.code);
50666
+ const r = analyzeTaint(tmp);
50667
+ rmSync(tmp, { force: true });
50668
+ if (r.findings.length > 0)
50669
+ return { detected: true, sinkKind: r.findings[0].sink };
50670
+ return { detected: false };
50671
+ }
50672
+ const ext = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" }[lang] ?? ".php";
50673
+ const r = analyzeTaintUniversal(entry.code, "bench." + ext);
50674
+ if (r.findings.length > 0)
50675
+ return { detected: true, sinkKind: r.findings[0].sink };
50676
+ return { detected: false };
50677
+ }
50678
+ function runBenchmark() {
50679
+ const entries = loadCorpus();
50680
+ const results = entries.map((entry) => {
50681
+ const { detected, sinkKind } = detect(entry);
50682
+ const outcome = entry.vulnerable ? detected ? "tp" : "fn" : detected ? "fp" : "tn";
50683
+ return { entry, detected, sink_kind: sinkKind, outcome };
50684
+ });
50685
+ const tp = results.filter((r) => r.outcome === "tp").length;
50686
+ const tn = results.filter((r) => r.outcome === "tn").length;
50687
+ const fp = results.filter((r) => r.outcome === "fp").length;
50688
+ const fn = results.filter((r) => r.outcome === "fn").length;
50689
+ const total = results.length;
50690
+ const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
50691
+ const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
50692
+ const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
50693
+ return {
50694
+ total_cases: total,
50695
+ tp,
50696
+ tn,
50697
+ fp,
50698
+ fn,
50699
+ detection_rate: Number(detectionRate.toFixed(3)),
50700
+ false_positive_rate: Number(falsePositiveRate.toFixed(3)),
50701
+ precision: Number(precision.toFixed(3)),
50702
+ results: results.map((r) => ({ id: r.entry.id, language: r.entry.language, outcome: r.outcome, sink: r.sink_kind }))
50703
+ };
50704
+ }
50705
+
50706
+ // src/coverage.ts
50707
+ var SINK_CLASSES = [
50708
+ "sql_execution",
50709
+ "code_execution",
50710
+ "command_execution",
50711
+ "file_operations",
50712
+ "file_inclusion",
50713
+ "deserialization",
50714
+ "http_request",
50715
+ "redirect",
50716
+ "html_render",
50717
+ "xml_processing",
50718
+ "archive_extraction",
50719
+ "path_traversal"
50720
+ ];
50721
+ var PHP_SINKS2 = {
50722
+ sql_execution: true,
50723
+ code_execution: true,
50724
+ command_execution: true,
50725
+ file_operations: true,
50726
+ file_inclusion: true,
50727
+ deserialization: true,
50728
+ http_request: true,
50729
+ redirect: true,
50730
+ html_render: true,
50731
+ xml_processing: false,
50732
+ archive_extraction: false,
50733
+ path_traversal: true
50734
+ };
50735
+ var JS_SINKS2 = {
50736
+ sql_execution: true,
50737
+ code_execution: true,
50738
+ command_execution: true,
50739
+ file_operations: true,
50740
+ file_inclusion: true,
50741
+ deserialization: false,
50742
+ http_request: true,
50743
+ redirect: true,
50744
+ html_render: true,
50745
+ xml_processing: false,
50746
+ archive_extraction: false,
50747
+ path_traversal: true
50748
+ };
50749
+ var PY_SINKS2 = {
50750
+ sql_execution: true,
50751
+ code_execution: true,
50752
+ command_execution: true,
50753
+ file_operations: true,
50754
+ file_inclusion: false,
50755
+ deserialization: true,
50756
+ http_request: false,
50757
+ redirect: true,
50758
+ html_render: true,
50759
+ xml_processing: false,
50760
+ archive_extraction: false,
50761
+ path_traversal: true
50762
+ };
50763
+ var JAVA_SINKS2 = {
50764
+ sql_execution: true,
50765
+ code_execution: false,
50766
+ command_execution: true,
50767
+ file_operations: true,
50768
+ file_inclusion: false,
50769
+ deserialization: true,
50770
+ http_request: true,
50771
+ redirect: true,
50772
+ html_render: true,
50773
+ xml_processing: false,
50774
+ archive_extraction: false,
50775
+ path_traversal: true
50776
+ };
50777
+ var SINK_BY_LANG = {
50778
+ php: PHP_SINKS2,
50779
+ javascript: JS_SINKS2,
50780
+ python: PY_SINKS2,
50781
+ java: JAVA_SINKS2
50782
+ };
50783
+ function coverageMatrix() {
50784
+ const langs = listLanguages();
50785
+ const sinkCoverage = SINK_CLASSES.map((sink) => {
50786
+ const covered = langs.map((l) => l.language).filter((lang) => SINK_BY_LANG[lang]?.[sink] === true);
50787
+ return { sink, languages: covered };
50788
+ });
50789
+ let coveredPairs = 0;
50790
+ for (const lang of langs) {
50791
+ for (const sink of SINK_CLASSES) {
50792
+ if (SINK_BY_LANG[lang.language]?.[sink])
50793
+ coveredPairs += 1;
50794
+ }
50795
+ }
50796
+ const totalPairs = langs.length * SINK_CLASSES.length;
50797
+ return {
50798
+ languages: langs,
50799
+ sink_coverage: sinkCoverage,
50800
+ counts: {
50801
+ languages: langs.length,
50802
+ sink_classes: SINK_CLASSES.length,
50803
+ covered_pairs: coveredPairs,
50804
+ total_pairs: totalPairs
50805
+ },
50806
+ coverage_ratio: Number((coveredPairs / totalPairs).toFixed(3))
50807
+ };
50808
+ }
50809
+
50395
50810
  // src/server.ts
50396
50811
  function createServer() {
50397
50812
  const server = new McpServer({
@@ -51084,6 +51499,72 @@ function createServer() {
51084
51499
  });
51085
51500
  return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
51086
51501
  });
51502
+ server.registerTool("cvss_score", {
51503
+ title: "Compute CVSS v3.1 base score",
51504
+ 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.",
51505
+ inputSchema: {
51506
+ AV: string2().optional().describe("Attack Vector: N/A/L/P"),
51507
+ AC: string2().optional().describe("Attack Complexity: L/H"),
51508
+ PR: string2().optional().describe("Privileges Required: N/L/H"),
51509
+ UI: string2().optional().describe("User Interaction: N/R"),
51510
+ S: string2().optional().describe("Scope: U/C"),
51511
+ C: string2().optional().describe("Confidentiality: H/L/N"),
51512
+ I: string2().optional().describe("Integrity: H/L/N"),
51513
+ A: string2().optional().describe("Availability: H/L/N")
51514
+ }
51515
+ }, async (input) => {
51516
+ return { content: [{ type: "text", text: JSON.stringify(cvssAssess(input)) }] };
51517
+ });
51518
+ server.registerTool("dedup_findings", {
51519
+ title: "Deduplicate findings by root cause",
51520
+ description: "FINDINGS: collapse a list of findings that share a root cause (sink type + source type + CWE) into one group per root cause.",
51521
+ inputSchema: {
51522
+ findings: string2().describe("JSON array of Finding objects")
51523
+ }
51524
+ }, async ({ findings }) => {
51525
+ let list;
51526
+ try {
51527
+ list = JSON.parse(findings);
51528
+ } catch {
51529
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
51530
+ }
51531
+ const groups = dedupFindings(list);
51532
+ return { content: [{ type: "text", text: JSON.stringify(groups.map((g) => ({ signature: g.signature, count: g.count, representative: g.representative.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, 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
  }
@@ -51208,17 +51689,17 @@ function opencodeWrite(p) {
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
+ writeFileSync(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 += `
@@ -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
  `))
@@ -51273,74 +51754,74 @@ mcp_servers:
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.24",
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",