blitzstrike 1.0.22 → 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 +639 -87
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  var __create = Object.create;
3
4
  var __getProtoOf = Object.getPrototypeOf;
4
5
  var __defProp = Object.defineProperty;
@@ -43,6 +44,7 @@ var __esm = (fn, res, err) => () => {
43
44
  throw err[0];
44
45
  return res;
45
46
  };
47
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
46
48
 
47
49
  // node_modules/ajv/dist/compile/codegen/code.js
48
50
  var require_code = __commonJS(function(exports) {
@@ -14820,10 +14822,10 @@ var require_src = __commonJS(function(exports, module) {
14820
14822
  });
14821
14823
 
14822
14824
  // src/sync.ts
14823
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync3, rmSync } from "node:fs";
14824
- 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";
14825
14827
  import { homedir as homedir4 } from "node:os";
14826
- import { fileURLToPath as fileURLToPath6 } from "node:url";
14828
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
14827
14829
  import { spawnSync as spawnSync3 } from "node:child_process";
14828
14830
  function dirExists(p) {
14829
14831
  try {
@@ -14836,9 +14838,9 @@ function countFiles(dir) {
14836
14838
  try {
14837
14839
  let n = 0;
14838
14840
  const walk = (d) => {
14839
- for (const e of readdirSync4(d, { withFileTypes: true })) {
14841
+ for (const e of readdirSync5(d, { withFileTypes: true })) {
14840
14842
  if (e.isDirectory())
14841
- walk(join8(d, e.name));
14843
+ walk(join9(d, e.name));
14842
14844
  else
14843
14845
  n++;
14844
14846
  }
@@ -14852,8 +14854,8 @@ function countFiles(dir) {
14852
14854
  function syncData() {
14853
14855
  if (!dirExists(DATA_ROOT2))
14854
14856
  mkdirSync3(DATA_ROOT2, { recursive: true });
14855
- const payloadsPath = join8(DATA_ROOT2, "payloads");
14856
- const templatesPath = join8(DATA_ROOT2, "templates");
14857
+ const payloadsPath = join9(DATA_ROOT2, "payloads");
14858
+ const templatesPath = join9(DATA_ROOT2, "templates");
14857
14859
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
14858
14860
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
14859
14861
  if (hasPayloads && hasTemplates) {
@@ -14863,8 +14865,8 @@ function syncData() {
14863
14865
  data_root: DATA_ROOT2
14864
14866
  };
14865
14867
  }
14866
- const localPayloads = join8(ROOT5, "payloads");
14867
- const localTemplates = join8(ROOT5, "templates");
14868
+ const localPayloads = join9(ROOT6, "payloads");
14869
+ const localTemplates = join9(ROOT6, "templates");
14868
14870
  let payloadsSynced = false;
14869
14871
  let templatesSynced = false;
14870
14872
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -14877,7 +14879,7 @@ function syncData() {
14877
14879
  }
14878
14880
  const gitOk = spawnSync3("git", ["--version"]).status === 0;
14879
14881
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
14880
- const tmp = join8(DATA_ROOT2, ".tmp-clone");
14882
+ const tmp = join9(DATA_ROOT2, ".tmp-clone");
14881
14883
  if (dirExists(tmp))
14882
14884
  rmSync(tmp, { recursive: true, force: true });
14883
14885
  const r = spawnSync3("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -14885,11 +14887,11 @@ function syncData() {
14885
14887
  const sparse = spawnSync3("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
14886
14888
  if (sparse.status === 0) {
14887
14889
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
14888
- copyDir(join8(tmp, "payloads"), payloadsPath);
14890
+ copyDir(join9(tmp, "payloads"), payloadsPath);
14889
14891
  payloadsSynced = true;
14890
14892
  }
14891
14893
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
14892
- copyDir(join8(tmp, "templates"), templatesPath);
14894
+ copyDir(join9(tmp, "templates"), templatesPath);
14893
14895
  templatesSynced = true;
14894
14896
  }
14895
14897
  }
@@ -14910,19 +14912,19 @@ function copyDir(src, dst) {
14910
14912
  }
14911
14913
  function manualCopy(src, dst) {
14912
14914
  mkdirSync3(dst, { recursive: true });
14913
- for (const e of readdirSync4(src, { withFileTypes: true })) {
14914
- const s = join8(src, e.name);
14915
- 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);
14916
14918
  if (e.isDirectory())
14917
14919
  manualCopy(s, d);
14918
14920
  else
14919
- writeFileSync2(d, readFileSync10(s));
14921
+ writeFileSync2(d, readFileSync11(s));
14920
14922
  }
14921
14923
  }
14922
- 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";
14923
14925
  var init_sync = __esm(() => {
14924
- ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
14925
- 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");
14926
14928
  });
14927
14929
 
14928
14930
  // node_modules/zod/v3/helpers/util.js
@@ -29759,6 +29761,18 @@ function listMemory() {
29759
29761
  entries: entries.sort((a, b) => a.created < b.created ? 1 : -1).map((e) => ({ id: e.id, topic: e.topic, type: e.type, verified: e.verified, created: e.created }))
29760
29762
  };
29761
29763
  }
29764
+ function forget(id) {
29765
+ const { writeFileSync } = __require("node:fs");
29766
+ const entries = loadMemory();
29767
+ const kept = entries.filter((e) => e.id !== id);
29768
+ if (kept.length === entries.length) {
29769
+ return { removed: false, id, note: "not found" };
29770
+ }
29771
+ writeFileSync(MEMORY_PATH, kept.map((e) => JSON.stringify(e)).join(`
29772
+ `) + (kept.length ? `
29773
+ ` : ""));
29774
+ return { removed: true, id };
29775
+ }
29762
29776
  function rememberIfAbsent(topic, content, type, tags, source, verified) {
29763
29777
  const r = remember(topic, content, type, tags, source, verified);
29764
29778
  return r.saved === true;
@@ -29954,6 +29968,12 @@ var DEFAULT_WEIGHTS = {
29954
29968
  negative_control: 0.1
29955
29969
  };
29956
29970
  var confidenceWeights = { ...DEFAULT_WEIGHTS };
29971
+ function setConfidenceWeights(weights) {
29972
+ confidenceWeights = { ...DEFAULT_WEIGHTS, ...weights };
29973
+ }
29974
+ function getConfidenceWeights() {
29975
+ return { ...confidenceWeights };
29976
+ }
29957
29977
  function computeConfidence(factors) {
29958
29978
  let score = 0;
29959
29979
  for (const [key, weight] of Object.entries(confidenceWeights)) {
@@ -30024,6 +30044,13 @@ function transition(finding, to) {
30024
30044
  const updated = { ...finding, status: to, timestamps: { ...finding.timestamps, updated: new Date().toISOString() } };
30025
30045
  return updated;
30026
30046
  }
30047
+ function attachEvidence(finding, evidence) {
30048
+ return {
30049
+ ...finding,
30050
+ evidence: [...finding.evidence, evidence],
30051
+ timestamps: { ...finding.timestamps, updated: new Date().toISOString() }
30052
+ };
30053
+ }
30027
30054
  function confirmFinding(finding, opts) {
30028
30055
  const evidence = opts.evidence.map((e) => makeEvidence(e));
30029
30056
  const negativeControl = opts.negative_control ?? true;
@@ -30429,7 +30456,7 @@ function ensureTool(name) {
30429
30456
  }
30430
30457
 
30431
30458
  // src/intel.ts
30432
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "node:fs";
30459
+ import { readFileSync as readFileSync6, existsSync as existsSync4 } from "node:fs";
30433
30460
  import { join as join6 } from "node:path";
30434
30461
  import { fileURLToPath as fileURLToPath4 } from "node:url";
30435
30462
  import { readdirSync as readdirSync3 } from "node:fs";
@@ -30438,7 +30465,7 @@ var ROOT3 = join6(fileURLToPath4(new URL(".", import.meta.url)), "..");
30438
30465
  var INTEL = join6(ROOT3, "intelligence");
30439
30466
  function loadJson(name) {
30440
30467
  const p = join6(INTEL, `${name}.json`);
30441
- if (!existsSync5(p))
30468
+ if (!existsSync4(p))
30442
30469
  return null;
30443
30470
  try {
30444
30471
  return JSON.parse(readFileSync6(p, "utf8"));
@@ -30454,7 +30481,6 @@ function detectWaf(headers, body = "") {
30454
30481
  const bodySigs = sigs.body_signatures ?? [];
30455
30482
  const matches = [];
30456
30483
  const headerKeys = Object.keys(headers).map((k) => k.toLowerCase());
30457
- const headerVals = Object.values(headers).join(" ").toLowerCase();
30458
30484
  for (const s of headerSigs) {
30459
30485
  const h = (s.header ?? "").toLowerCase();
30460
30486
  const pattern = (s.pattern ?? "").toLowerCase();
@@ -30553,12 +30579,12 @@ var PACKAGE_PAYLOADS = join6(ROOT3, "payloads");
30553
30579
  var PACKAGE_TEMPLATES = join6(ROOT3, "templates");
30554
30580
  var DATA_ROOT = process.env.BLITZSTRIKE_DATA ?? join6(homedir2(), ".blitzstrike", "data");
30555
30581
  function payloadsDir() {
30556
- if (existsSync5(PACKAGE_PAYLOADS))
30582
+ if (existsSync4(PACKAGE_PAYLOADS))
30557
30583
  return PACKAGE_PAYLOADS;
30558
30584
  return join6(DATA_ROOT, "payloads");
30559
30585
  }
30560
30586
  function templatesDir() {
30561
- if (existsSync5(PACKAGE_TEMPLATES))
30587
+ if (existsSync4(PACKAGE_TEMPLATES))
30562
30588
  return PACKAGE_TEMPLATES;
30563
30589
  return join6(DATA_ROOT, "templates");
30564
30590
  }
@@ -31132,7 +31158,6 @@ function walkExpression(node, env, summaries, findings, suppressed, file) {
31132
31158
  const sanitizers = tainted.flatMap((t) => t.sanitizers);
31133
31159
  const inlineSan = findSanitizers(JSON.stringify(node));
31134
31160
  const san = [...sanitizers, ...inlineSan].filter((s, i, arr) => arr.indexOf(s) === i);
31135
- const authGated = false;
31136
31161
  const cls = sink.cls;
31137
31162
  if (!isSanitized(san, cls.id)) {
31138
31163
  findings.push({
@@ -49403,7 +49428,6 @@ var phpAdapter = {
49403
49428
  }
49404
49429
  }
49405
49430
  }
49406
- const superSources = new Map;
49407
49431
  for (const n of nodes) {
49408
49432
  if (n.kind === "assign") {
49409
49433
  const target = phpVar(n.left);
@@ -49476,7 +49500,7 @@ function jsWalk(node, out) {
49476
49500
  var jsAdapter = {
49477
49501
  language: "javascript",
49478
49502
  extensions: [".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"],
49479
- parse(code, file) {
49503
+ parse(code, _file) {
49480
49504
  let ast;
49481
49505
  try {
49482
49506
  ast = parse6(code, {
@@ -49585,7 +49609,7 @@ var PY_SANITIZERS = [
49585
49609
  var pythonAdapter = {
49586
49610
  language: "python",
49587
49611
  extensions: [".py", ".pyw"],
49588
- parse(code, file) {
49612
+ parse(code, _file) {
49589
49613
  const tree = parser2.parse(code);
49590
49614
  const sources = [];
49591
49615
  const sinks = [];
@@ -49703,7 +49727,7 @@ var JAVA_SANITIZERS = [
49703
49727
  var javaAdapter = {
49704
49728
  language: "java",
49705
49729
  extensions: [".java"],
49706
- parse(code, file) {
49730
+ parse(code, _file) {
49707
49731
  const sources = [];
49708
49732
  const sinks = [];
49709
49733
  const sanitizers = [];
@@ -50231,10 +50255,10 @@ async function liveRecon(target, includeActive = false) {
50231
50255
  }
50232
50256
 
50233
50257
  // src/strike.ts
50234
- var TIMEOUT_MS3 = 15000;
50235
- async function httpRequest(url, method, data, headers) {
50258
+ var DEFAULT_TIMEOUT_MS = 15000;
50259
+ async function httpRequest(url, method, data, headers, timeoutMs = DEFAULT_TIMEOUT_MS) {
50236
50260
  const controller = new AbortController;
50237
- const t = setTimeout(() => controller.abort(), TIMEOUT_MS3);
50261
+ const t = setTimeout(() => controller.abort(), timeoutMs);
50238
50262
  try {
50239
50263
  const res = await fetch(url, {
50240
50264
  method,
@@ -50272,12 +50296,13 @@ async function strikeVerify(input) {
50272
50296
  const marker = input.marker ?? defaultMarker();
50273
50297
  const control = input.control ?? defaultMarker();
50274
50298
  const baseUrl = input.url;
50299
+ const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
50275
50300
  const baselineTarget = inject(baseUrl, "", param).replace(/[?&]q=$/, "").replace(/[?&]$/, "");
50276
50301
  const markerTarget = inject(baseUrl, marker, param);
50277
50302
  const controlTarget = inject(baseUrl, control, param);
50278
- const baseline = await httpRequest(baselineTarget, method, input.data, input.headers);
50279
- const markerResp = await httpRequest(markerTarget, method, input.data, input.headers);
50280
- const controlResp = await httpRequest(controlTarget, method, input.data, input.headers);
50303
+ const baseline = await httpRequest(baselineTarget, method, input.data, input.headers, timeoutMs);
50304
+ const markerResp = await httpRequest(markerTarget, method, input.data, input.headers, timeoutMs);
50305
+ const controlResp = await httpRequest(controlTarget, method, input.data, input.headers, timeoutMs);
50281
50306
  if (baseline.status === 0 || markerResp.status === 0) {
50282
50307
  return {
50283
50308
  status: "blocked",
@@ -50367,6 +50392,421 @@ function resolveFinding(finding, verdict) {
50367
50392
  }
50368
50393
  }
50369
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
+
50370
50810
  // src/server.ts
50371
50811
  function createServer() {
50372
50812
  const server = new McpServer({
@@ -50478,7 +50918,8 @@ function createServer() {
50478
50918
  headers: hdrs,
50479
50919
  marker,
50480
50920
  control,
50481
- param
50921
+ param,
50922
+ timeout: timeout ? timeout * 1000 : undefined
50482
50923
  });
50483
50924
  return {
50484
50925
  content: [{
@@ -50703,6 +51144,13 @@ function createServer() {
50703
51144
  }, async () => {
50704
51145
  return { content: [{ type: "text", text: JSON.stringify(listMemory()) }] };
50705
51146
  });
51147
+ server.registerTool("memory_forget", {
51148
+ title: "Forget a memory entry",
51149
+ description: "MEMORY: remove a memory entry by id (append-only store; 'forget' = tombstone).",
51150
+ inputSchema: { id: string2().describe("Memory entry id (from memory_list)") }
51151
+ }, async ({ id }) => {
51152
+ return { content: [{ type: "text", text: JSON.stringify(forget(id)) }] };
51153
+ });
50706
51154
  server.registerTool("read_tool_manual", {
50707
51155
  title: "Read a tool manual",
50708
51156
  description: "Read the full deep reference manual for a security tool (270+ manuals from kali-pentest, Apache-2.0).",
@@ -50886,6 +51334,44 @@ function createServer() {
50886
51334
  const score = computeConfidence(factors);
50887
51335
  return { content: [{ type: "text", text: JSON.stringify({ score, level: confidenceLevel(score) }) }] };
50888
51336
  });
51337
+ server.registerTool("confidence_weights", {
51338
+ title: "Get/set confidence weights",
51339
+ description: "FINDINGS: inspect or override the deterministic confidence weights (configurable per the framework spec). Pass `set` (JSON object of weight overrides) to change them; omit to read the current weights.",
51340
+ inputSchema: {
51341
+ set: string2().optional().describe('JSON object of weight overrides (e.g. {"data_flow":0.3})')
51342
+ }
51343
+ }, async ({ set }) => {
51344
+ if (set) {
51345
+ let w;
51346
+ try {
51347
+ w = JSON.parse(set);
51348
+ } catch {
51349
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid JSON" }) }] };
51350
+ }
51351
+ setConfidenceWeights(w);
51352
+ }
51353
+ return { content: [{ type: "text", text: JSON.stringify({ weights: getConfidenceWeights() }) }] };
51354
+ });
51355
+ server.registerTool("finding_attach_evidence", {
51356
+ title: "Attach evidence to a finding",
51357
+ description: "FINDINGS: attach a redacted, SHA-256-tagged evidence record to an existing canonical finding (without advancing its lifecycle).",
51358
+ inputSchema: {
51359
+ finding: string2().describe("JSON of the Finding object"),
51360
+ type: string2().describe("Evidence type (source_location/response/validation_result/negative_control/...)"),
51361
+ description: string2().describe("Evidence description"),
51362
+ content: string2().optional().describe("Evidence artifact content")
51363
+ }
51364
+ }, async ({ finding, type, description, content }) => {
51365
+ let f;
51366
+ try {
51367
+ f = JSON.parse(finding);
51368
+ } catch {
51369
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON" }) }] };
51370
+ }
51371
+ const ev = makeEvidence({ type, description, artifacts: content ? [{ name: "artifact", kind: "evidence", content }] : [] });
51372
+ const updated = attachEvidence(f, ev);
51373
+ return { content: [{ type: "text", text: JSON.stringify(updated) }] };
51374
+ });
50889
51375
  server.registerTool("redact", {
50890
51376
  title: "Redact secrets from text",
50891
51377
  description: "EVIDENCE: redact passwords/API keys/tokens/cookies/private keys from text before persisting evidence or reporting.",
@@ -51013,6 +51499,72 @@ function createServer() {
51013
51499
  });
51014
51500
  return { content: [{ type: "text", text: JSON.stringify({ ...r, next_steps: guidance }) }] };
51015
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
+ });
51016
51568
  return server;
51017
51569
  }
51018
51570
  async function serve() {
@@ -51022,12 +51574,12 @@ async function serve() {
51022
51574
  }
51023
51575
 
51024
51576
  // src/cli.ts
51025
- import { readFileSync as readFileSync9, existsSync as existsSync6, writeFileSync, mkdirSync as mkdirSync2 } from "node:fs";
51026
- 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";
51027
51579
  import { homedir as homedir3 } from "node:os";
51028
- import { fileURLToPath as fileURLToPath5 } from "node:url";
51580
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
51029
51581
  import { spawnSync as spawnSync2 } from "node:child_process";
51030
- var ROOT4 = join7(fileURLToPath5(new URL(".", import.meta.url)), "..");
51582
+ var ROOT5 = join8(fileURLToPath6(new URL(".", import.meta.url)), "..");
51031
51583
  function which(cmd) {
51032
51584
  return spawnSync2(`command -v ${cmd}`, { shell: true }).status === 0;
51033
51585
  }
@@ -51062,7 +51614,7 @@ function runDoctor() {
51062
51614
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
51063
51615
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
51064
51616
  });
51065
- const dataOk = existsSync6(join7(ROOT4, "chains.json")) && existsSync6(join7(ROOT4, "tools-catalog.json"));
51617
+ const dataOk = existsSync5(join8(ROOT5, "chains.json")) && existsSync5(join8(ROOT5, "tools-catalog.json"));
51066
51618
  issues.push({
51067
51619
  name: "Data layers (chains + tools-catalog)",
51068
51620
  status: dataOk ? "ok" : "fail",
@@ -51090,21 +51642,21 @@ BlitzStrike — ${verdict}`);
51090
51642
  function resolveCommand() {
51091
51643
  if (which("blitzstrike"))
51092
51644
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
51093
- const src = join7(ROOT4, "src", "index.ts");
51094
- if (which("bun") && existsSync6(src))
51645
+ const src = join8(ROOT5, "src", "index.ts");
51646
+ if (which("bun") && existsSync5(src))
51095
51647
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
51096
51648
  if (which("npx"))
51097
51649
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
51098
51650
  if (which("bunx"))
51099
51651
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
51100
- const dist = join7(ROOT4, "dist", "index.js");
51101
- if (existsSync6(dist))
51652
+ const dist = join8(ROOT5, "dist", "index.js");
51653
+ if (existsSync5(dist))
51102
51654
  return { command: "bun", args: [dist, "serve", "--mcp"] };
51103
51655
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
51104
51656
  }
51105
51657
  function readJson(p) {
51106
51658
  try {
51107
- return JSON.parse(readFileSync9(p, "utf8"));
51659
+ return JSON.parse(readFileSync10(p, "utf8"));
51108
51660
  } catch {
51109
51661
  return null;
51110
51662
  }
@@ -51112,7 +51664,7 @@ function readJson(p) {
51112
51664
  function jsonMcpServersWrite(p) {
51113
51665
  return (command, args) => {
51114
51666
  const dir = dirname(p);
51115
- if (!existsSync6(dir))
51667
+ if (!existsSync5(dir))
51116
51668
  mkdirSync2(dir, { recursive: true });
51117
51669
  const existing = readJson(p) ?? {};
51118
51670
  existing.mcpServers = {
@@ -51125,7 +51677,7 @@ function jsonMcpServersWrite(p) {
51125
51677
  function opencodeWrite(p) {
51126
51678
  return (_command, _args) => {
51127
51679
  const dir = dirname(p);
51128
- if (!existsSync6(dir))
51680
+ if (!existsSync5(dir))
51129
51681
  mkdirSync2(dir, { recursive: true });
51130
51682
  const existing = readJson(p) ?? {};
51131
51683
  existing.mcp = {
@@ -51137,26 +51689,26 @@ function opencodeWrite(p) {
51137
51689
  };
51138
51690
  }
51139
51691
  function copyOpenCodeAgents() {
51140
- const srcDir = join7(ROOT4, "opencode-agents");
51141
- const dstDir = join7(homedir3(), ".config", "opencode", "agents");
51142
- if (!existsSync6(srcDir))
51692
+ const srcDir = join8(ROOT5, "opencode-agents");
51693
+ const dstDir = join8(homedir3(), ".config", "opencode", "agents");
51694
+ if (!existsSync5(srcDir))
51143
51695
  return;
51144
- if (!existsSync6(dstDir))
51696
+ if (!existsSync5(dstDir))
51145
51697
  mkdirSync2(dstDir, { recursive: true });
51146
51698
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
51147
- const src = join7(srcDir, f);
51148
- const dst = join7(dstDir, f);
51149
- if (existsSync6(src)) {
51150
- writeFileSync(dst, readFileSync9(src, "utf8"));
51699
+ const src = join8(srcDir, f);
51700
+ const dst = join8(dstDir, f);
51701
+ if (existsSync5(src)) {
51702
+ writeFileSync(dst, readFileSync10(src, "utf8"));
51151
51703
  }
51152
51704
  }
51153
51705
  }
51154
51706
  function codexWrite(p) {
51155
51707
  return (command, args) => {
51156
51708
  const dir = dirname(p);
51157
- if (!existsSync6(dir))
51709
+ if (!existsSync5(dir))
51158
51710
  mkdirSync2(dir, { recursive: true });
51159
- let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
51711
+ let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
51160
51712
  if (!existing.trimEnd().endsWith(`
51161
51713
  `))
51162
51714
  existing += `
@@ -51173,9 +51725,9 @@ args = ${JSON.stringify(args)}
51173
51725
  function hermesWrite(p) {
51174
51726
  return (command, args) => {
51175
51727
  const dir = dirname(p);
51176
- if (!existsSync6(dir))
51728
+ if (!existsSync5(dir))
51177
51729
  mkdirSync2(dir, { recursive: true });
51178
- let existing = existsSync6(p) ? readFileSync9(p, "utf8") : "";
51730
+ let existing = existsSync5(p) ? readFileSync10(p, "utf8") : "";
51179
51731
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
51180
51732
  if (!existing.trimEnd().endsWith(`
51181
51733
  `))
@@ -51202,74 +51754,74 @@ mcp_servers:
51202
51754
  function detectAgents() {
51203
51755
  const home = homedir3();
51204
51756
  const agents = [];
51205
- const claudeJson = join7(home, ".claude.json");
51757
+ const claudeJson = join8(home, ".claude.json");
51206
51758
  agents.push({
51207
51759
  name: "Claude Code",
51208
51760
  detectPath: claudeJson,
51209
- installed: () => existsSync6(claudeJson) || which("claude"),
51761
+ installed: () => existsSync5(claudeJson) || which("claude"),
51210
51762
  write: jsonMcpServersWrite(claudeJson)
51211
51763
  });
51212
- const claudeDesktop = join7(home, ".config", "Claude", "claude_desktop_config.json");
51764
+ const claudeDesktop = join8(home, ".config", "Claude", "claude_desktop_config.json");
51213
51765
  agents.push({
51214
51766
  name: "Claude Desktop",
51215
51767
  detectPath: claudeDesktop,
51216
- installed: () => existsSync6(claudeDesktop),
51768
+ installed: () => existsSync5(claudeDesktop),
51217
51769
  write: jsonMcpServersWrite(claudeDesktop)
51218
51770
  });
51219
- const cursor = join7(home, ".cursor", "mcp.json");
51771
+ const cursor = join8(home, ".cursor", "mcp.json");
51220
51772
  agents.push({
51221
51773
  name: "Cursor",
51222
51774
  detectPath: cursor,
51223
- installed: () => existsSync6(join7(home, ".cursor")) || which("cursor"),
51775
+ installed: () => existsSync5(join8(home, ".cursor")) || which("cursor"),
51224
51776
  write: jsonMcpServersWrite(cursor)
51225
51777
  });
51226
- const opencode = join7(home, ".config", "opencode", "opencode.jsonc");
51778
+ const opencode = join8(home, ".config", "opencode", "opencode.jsonc");
51227
51779
  agents.push({
51228
51780
  name: "OpenCode",
51229
51781
  detectPath: opencode,
51230
- installed: () => existsSync6(join7(home, ".config", "opencode")) || which("opencode"),
51782
+ installed: () => existsSync5(join8(home, ".config", "opencode")) || which("opencode"),
51231
51783
  write: opencodeWrite(opencode)
51232
51784
  });
51233
- const codex = join7(home, ".codex", "config.toml");
51785
+ const codex = join8(home, ".codex", "config.toml");
51234
51786
  agents.push({
51235
51787
  name: "Codex",
51236
51788
  detectPath: codex,
51237
- installed: () => existsSync6(join7(home, ".codex")) || which("codex"),
51789
+ installed: () => existsSync5(join8(home, ".codex")) || which("codex"),
51238
51790
  write: codexWrite(codex)
51239
51791
  });
51240
- const hermes = join7(home, ".hermes", "config.yaml");
51792
+ const hermes = join8(home, ".hermes", "config.yaml");
51241
51793
  agents.push({
51242
51794
  name: "Hermes",
51243
51795
  detectPath: hermes,
51244
- installed: () => existsSync6(join7(home, ".hermes")) || which("hermes"),
51796
+ installed: () => existsSync5(join8(home, ".hermes")) || which("hermes"),
51245
51797
  write: hermesWrite(hermes)
51246
51798
  });
51247
- const gemini = join7(home, ".gemini", "settings.json");
51799
+ const gemini = join8(home, ".gemini", "settings.json");
51248
51800
  agents.push({
51249
51801
  name: "Gemini CLI",
51250
51802
  detectPath: gemini,
51251
- installed: () => existsSync6(join7(home, ".gemini")) || which("gemini"),
51803
+ installed: () => existsSync5(join8(home, ".gemini")) || which("gemini"),
51252
51804
  write: jsonMcpServersWrite(gemini)
51253
51805
  });
51254
- const windsurf = join7(home, ".codeium", "windsurf", "mcp_config.json");
51806
+ const windsurf = join8(home, ".codeium", "windsurf", "mcp_config.json");
51255
51807
  agents.push({
51256
51808
  name: "Windsurf",
51257
51809
  detectPath: windsurf,
51258
- installed: () => existsSync6(join7(home, ".codeium")) || which("windsurf"),
51810
+ installed: () => existsSync5(join8(home, ".codeium")) || which("windsurf"),
51259
51811
  write: jsonMcpServersWrite(windsurf)
51260
51812
  });
51261
- const copilot = join7(home, ".copilot", "mcp.json");
51813
+ const copilot = join8(home, ".copilot", "mcp.json");
51262
51814
  agents.push({
51263
51815
  name: "Copilot",
51264
51816
  detectPath: copilot,
51265
- installed: () => existsSync6(join7(home, ".copilot")),
51817
+ installed: () => existsSync5(join8(home, ".copilot")),
51266
51818
  write: jsonMcpServersWrite(copilot)
51267
51819
  });
51268
- const cline = join7(home, ".cline", "mcp_settings.json");
51820
+ const cline = join8(home, ".cline", "mcp_settings.json");
51269
51821
  agents.push({
51270
51822
  name: "Cline",
51271
51823
  detectPath: cline,
51272
- installed: () => existsSync6(join7(home, ".cline")),
51824
+ installed: () => existsSync5(join8(home, ".cline")),
51273
51825
  write: jsonMcpServersWrite(cline)
51274
51826
  });
51275
51827
  return agents;
@@ -51313,15 +51865,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
51313
51865
  }
51314
51866
 
51315
51867
  // src/index.ts
51316
- import { readFileSync as readFileSync11, existsSync as existsSync8 } from "node:fs";
51317
- import { join as join9 } from "node:path";
51318
- import { fileURLToPath as fileURLToPath7 } from "node:url";
51319
- 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)), "..");
51320
51872
  function readVersion() {
51321
51873
  try {
51322
- const p = join9(ROOT6, "package.json");
51323
- if (existsSync8(p))
51324
- return JSON.parse(readFileSync11(p, "utf8")).version ?? "1.0.0";
51874
+ const p = join10(ROOT7, "package.json");
51875
+ if (existsSync6(p))
51876
+ return JSON.parse(readFileSync12(p, "utf8")).version ?? "1.0.0";
51325
51877
  } catch {}
51326
51878
  return "1.0.0";
51327
51879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.22",
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",