blitzstrike 2.4.58 → 2.4.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.js +266 -108
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.4.59] — 2026-09-14
9
+
10
+ ### Added — Severity calibration + engagement scaffold + data health
11
+
12
+ - New `severity_calibrate` MCP tool (src/severity.ts): maps a detector type to a
13
+ platform-neutral priority tier (P1–P5) + a VRT-style category + rationale, with
14
+ an optional CVSS cross-reference — a finding is reported at the severity a triager
15
+ would assign, not the detector's raw default.
16
+ - New `engagement_scaffold` MCP tool: opens an engagement and emits a deterministic
17
+ folder layout (scope.md, findings/, evidence/, reports/, notes/) + a pre-filled
18
+ scope.md template, so a hunt starts with structure instead of improvisation.
19
+ - New `data_refresh` MCP tool (src/datarefresh.ts): re-loads every data layer
20
+ (chains, patterns, CWE map) and verifies cross-references, surfacing stale/broken
21
+ data instead of silently producing bad grounding.
22
+ - System prompt updated to drive the full grounding→gate→calibrate→scaffold flow.
23
+
8
24
  ## [2.4.58] — 2026-09-14
9
25
 
10
26
  ### Added — Empirical grounding v1 (pattern library + submission gate)
package/dist/index.js CHANGED
@@ -15490,10 +15490,10 @@ async function checkMcpServers() {
15490
15490
  var init_mcp_status = () => {};
15491
15491
 
15492
15492
  // src/sync.ts
15493
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync23, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15494
- import { join as join20 } from "node:path";
15493
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync24, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15494
+ import { join as join21 } from "node:path";
15495
15495
  import { homedir as homedir10 } from "node:os";
15496
- import { fileURLToPath as fileURLToPath10 } from "node:url";
15496
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
15497
15497
  import { spawnSync as spawnSync4 } from "node:child_process";
15498
15498
  function dirExists(p) {
15499
15499
  try {
@@ -15508,7 +15508,7 @@ function countFiles(dir) {
15508
15508
  const walk = (d) => {
15509
15509
  for (const e of readdirSync7(d, { withFileTypes: true })) {
15510
15510
  if (e.isDirectory())
15511
- walk(join20(d, e.name));
15511
+ walk(join21(d, e.name));
15512
15512
  else
15513
15513
  n++;
15514
15514
  }
@@ -15522,8 +15522,8 @@ function countFiles(dir) {
15522
15522
  function syncData() {
15523
15523
  if (!dirExists(DATA_ROOT2))
15524
15524
  mkdirSync9(DATA_ROOT2, { recursive: true });
15525
- const payloadsPath = join20(DATA_ROOT2, "payloads");
15526
- const templatesPath = join20(DATA_ROOT2, "templates");
15525
+ const payloadsPath = join21(DATA_ROOT2, "payloads");
15526
+ const templatesPath = join21(DATA_ROOT2, "templates");
15527
15527
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
15528
15528
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
15529
15529
  if (hasPayloads && hasTemplates) {
@@ -15533,8 +15533,8 @@ function syncData() {
15533
15533
  data_root: DATA_ROOT2
15534
15534
  };
15535
15535
  }
15536
- const localPayloads = join20(ROOT7, "payloads");
15537
- const localTemplates = join20(ROOT7, "templates");
15536
+ const localPayloads = join21(ROOT7, "payloads");
15537
+ const localTemplates = join21(ROOT7, "templates");
15538
15538
  let payloadsSynced = false;
15539
15539
  let templatesSynced = false;
15540
15540
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -15547,7 +15547,7 @@ function syncData() {
15547
15547
  }
15548
15548
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
15549
15549
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
15550
- const tmp = join20(DATA_ROOT2, ".tmp-clone");
15550
+ const tmp = join21(DATA_ROOT2, ".tmp-clone");
15551
15551
  if (dirExists(tmp))
15552
15552
  rmSync4(tmp, { recursive: true, force: true });
15553
15553
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -15555,11 +15555,11 @@ function syncData() {
15555
15555
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
15556
15556
  if (sparse.status === 0) {
15557
15557
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
15558
- copyDir(join20(tmp, "payloads"), payloadsPath);
15558
+ copyDir(join21(tmp, "payloads"), payloadsPath);
15559
15559
  payloadsSynced = true;
15560
15560
  }
15561
15561
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
15562
- copyDir(join20(tmp, "templates"), templatesPath);
15562
+ copyDir(join21(tmp, "templates"), templatesPath);
15563
15563
  templatesSynced = true;
15564
15564
  }
15565
15565
  }
@@ -15581,18 +15581,18 @@ function copyDir(src, dst) {
15581
15581
  function manualCopy(src, dst) {
15582
15582
  mkdirSync9(dst, { recursive: true });
15583
15583
  for (const e of readdirSync7(src, { withFileTypes: true })) {
15584
- const s = join20(src, e.name);
15585
- const d = join20(dst, e.name);
15584
+ const s = join21(src, e.name);
15585
+ const d = join21(dst, e.name);
15586
15586
  if (e.isDirectory())
15587
15587
  manualCopy(s, d);
15588
15588
  else
15589
- writeFileSync8(d, readFileSync23(s));
15589
+ writeFileSync8(d, readFileSync24(s));
15590
15590
  }
15591
15591
  }
15592
15592
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
15593
15593
  var init_sync = __esm(() => {
15594
- ROOT7 = join20(fileURLToPath10(new URL(".", import.meta.url)), "..");
15595
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join20(homedir10(), ".blitzstrike", "data");
15594
+ ROOT7 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
15595
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join21(homedir10(), ".blitzstrike", "data");
15596
15596
  });
15597
15597
 
15598
15598
  // src/update.ts
@@ -15637,15 +15637,15 @@ var init_update = __esm(() => {
15637
15637
  });
15638
15638
 
15639
15639
  // src/release.ts
15640
- import { readFileSync as readFileSync25, existsSync as existsSync12 } from "node:fs";
15641
- import { join as join21 } from "node:path";
15642
- import { fileURLToPath as fileURLToPath11 } from "node:url";
15640
+ import { readFileSync as readFileSync26, existsSync as existsSync12 } from "node:fs";
15641
+ import { join as join22 } from "node:path";
15642
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
15643
15643
  import { execSync as execSync2 } from "node:child_process";
15644
15644
  function validateRelease() {
15645
15645
  const checks = [];
15646
15646
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
15647
15647
  try {
15648
- const changelog = readFileSync25(join21(ROOT8, "CHANGELOG.md"), "utf8");
15648
+ const changelog = readFileSync26(join22(ROOT8, "CHANGELOG.md"), "utf8");
15649
15649
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
15650
15650
  const changelogVersion = m?.[1] ?? null;
15651
15651
  checks.push({
@@ -15677,22 +15677,22 @@ function validateRelease() {
15677
15677
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
15678
15678
  }
15679
15679
  function lockfilePresent() {
15680
- return existsSync12(join21(ROOT8, "package-lock.json")) || existsSync12(join21(ROOT8, "bun.lock")) || existsSync12(join21(ROOT8, "bun.lockb"));
15680
+ return existsSync12(join22(ROOT8, "package-lock.json")) || existsSync12(join22(ROOT8, "bun.lock")) || existsSync12(join22(ROOT8, "bun.lockb"));
15681
15681
  }
15682
15682
  var ROOT8;
15683
15683
  var init_release = __esm(() => {
15684
15684
  init_version();
15685
- ROOT8 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
15685
+ ROOT8 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
15686
15686
  });
15687
15687
 
15688
15688
  // src/dependency.ts
15689
- import { readFileSync as readFileSync26 } from "node:fs";
15690
- import { join as join22 } from "node:path";
15691
- import { fileURLToPath as fileURLToPath12 } from "node:url";
15689
+ import { readFileSync as readFileSync27 } from "node:fs";
15690
+ import { join as join23 } from "node:path";
15691
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
15692
15692
  function checkDependencies() {
15693
15693
  let pkg = {};
15694
15694
  try {
15695
- pkg = JSON.parse(readFileSync26(join22(ROOT9, "package.json"), "utf8"));
15695
+ pkg = JSON.parse(readFileSync27(join23(ROOT9, "package.json"), "utf8"));
15696
15696
  } catch {
15697
15697
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
15698
15698
  }
@@ -15715,14 +15715,14 @@ function checkDependencies() {
15715
15715
  }
15716
15716
  var ROOT9;
15717
15717
  var init_dependency = __esm(() => {
15718
- ROOT9 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
15718
+ ROOT9 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
15719
15719
  });
15720
15720
 
15721
15721
  // src/cli.ts
15722
- import { readFileSync as readFileSync27, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
15723
- import { join as join23, dirname as dirname3 } from "node:path";
15722
+ import { readFileSync as readFileSync28, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
15723
+ import { join as join24, dirname as dirname4 } from "node:path";
15724
15724
  import { homedir as homedir11 } from "node:os";
15725
- import { fileURLToPath as fileURLToPath13 } from "node:url";
15725
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
15726
15726
  import { spawnSync as spawnSync5 } from "node:child_process";
15727
15727
  function which(cmd) {
15728
15728
  return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
@@ -15765,7 +15765,7 @@ async function runDoctor() {
15765
15765
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
15766
15766
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
15767
15767
  });
15768
- const dataOk = existsSync13(join23(ROOT10, "chains.json")) && existsSync13(join23(ROOT10, "tools-catalog.json"));
15768
+ const dataOk = existsSync13(join24(ROOT10, "chains.json")) && existsSync13(join24(ROOT10, "tools-catalog.json"));
15769
15769
  issues.push({
15770
15770
  name: "Data layers (chains + tools-catalog)",
15771
15771
  status: dataOk ? "ok" : "fail",
@@ -15816,28 +15816,28 @@ BlitzStrike — ${verdict}`);
15816
15816
  function resolveCommand() {
15817
15817
  if (which("blitzstrike"))
15818
15818
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
15819
- const src = join23(ROOT10, "src", "index.ts");
15819
+ const src = join24(ROOT10, "src", "index.ts");
15820
15820
  if (which("bun") && existsSync13(src))
15821
15821
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
15822
15822
  if (which("npx"))
15823
15823
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
15824
15824
  if (which("bunx"))
15825
15825
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
15826
- const dist = join23(ROOT10, "dist", "index.js");
15826
+ const dist = join24(ROOT10, "dist", "index.js");
15827
15827
  if (existsSync13(dist))
15828
15828
  return { command: "bun", args: [dist, "serve", "--mcp"] };
15829
15829
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
15830
15830
  }
15831
15831
  function readJson(p) {
15832
15832
  try {
15833
- return JSON.parse(readFileSync27(p, "utf8"));
15833
+ return JSON.parse(readFileSync28(p, "utf8"));
15834
15834
  } catch {
15835
15835
  return null;
15836
15836
  }
15837
15837
  }
15838
15838
  function jsonMcpServersWrite(p) {
15839
15839
  return (command, args) => {
15840
- const dir = dirname3(p);
15840
+ const dir = dirname4(p);
15841
15841
  if (!existsSync13(dir))
15842
15842
  mkdirSync10(dir, { recursive: true });
15843
15843
  const existing = readJson(p) ?? {};
@@ -15850,7 +15850,7 @@ function jsonMcpServersWrite(p) {
15850
15850
  }
15851
15851
  function opencodeWrite(p) {
15852
15852
  return (command, args) => {
15853
- const dir = dirname3(p);
15853
+ const dir = dirname4(p);
15854
15854
  if (!existsSync13(dir))
15855
15855
  mkdirSync10(dir, { recursive: true });
15856
15856
  const existing = readJson(p) ?? {};
@@ -15863,26 +15863,26 @@ function opencodeWrite(p) {
15863
15863
  };
15864
15864
  }
15865
15865
  function copyOpenCodeAgents() {
15866
- const srcDir = join23(ROOT10, "opencode-agents");
15867
- const dstDir = join23(homedir11(), ".config", "opencode", "agents");
15866
+ const srcDir = join24(ROOT10, "opencode-agents");
15867
+ const dstDir = join24(homedir11(), ".config", "opencode", "agents");
15868
15868
  if (!existsSync13(srcDir))
15869
15869
  return;
15870
15870
  if (!existsSync13(dstDir))
15871
15871
  mkdirSync10(dstDir, { recursive: true });
15872
15872
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
15873
- const src = join23(srcDir, f);
15874
- const dst = join23(dstDir, f);
15873
+ const src = join24(srcDir, f);
15874
+ const dst = join24(dstDir, f);
15875
15875
  if (existsSync13(src)) {
15876
- writeFileSync9(dst, readFileSync27(src, "utf8"));
15876
+ writeFileSync9(dst, readFileSync28(src, "utf8"));
15877
15877
  }
15878
15878
  }
15879
15879
  }
15880
15880
  function codexWrite(p) {
15881
15881
  return (command, args) => {
15882
- const dir = dirname3(p);
15882
+ const dir = dirname4(p);
15883
15883
  if (!existsSync13(dir))
15884
15884
  mkdirSync10(dir, { recursive: true });
15885
- let existing = existsSync13(p) ? readFileSync27(p, "utf8") : "";
15885
+ let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
15886
15886
  if (!existing.trimEnd().endsWith(`
15887
15887
  `))
15888
15888
  existing += `
@@ -15898,10 +15898,10 @@ args = ${JSON.stringify(args)}
15898
15898
  }
15899
15899
  function hermesWrite(p) {
15900
15900
  return (command, args) => {
15901
- const dir = dirname3(p);
15901
+ const dir = dirname4(p);
15902
15902
  if (!existsSync13(dir))
15903
15903
  mkdirSync10(dir, { recursive: true });
15904
- let existing = existsSync13(p) ? readFileSync27(p, "utf8") : "";
15904
+ let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
15905
15905
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
15906
15906
  if (!existing.trimEnd().endsWith(`
15907
15907
  `))
@@ -15928,74 +15928,74 @@ mcp_servers:
15928
15928
  function detectAgents() {
15929
15929
  const home = homedir11();
15930
15930
  const agents = [];
15931
- const claudeJson = join23(home, ".claude.json");
15931
+ const claudeJson = join24(home, ".claude.json");
15932
15932
  agents.push({
15933
15933
  name: "Claude Code",
15934
15934
  detectPath: claudeJson,
15935
15935
  installed: () => existsSync13(claudeJson) || which("claude"),
15936
15936
  write: jsonMcpServersWrite(claudeJson)
15937
15937
  });
15938
- const claudeDesktop = join23(home, ".config", "Claude", "claude_desktop_config.json");
15938
+ const claudeDesktop = join24(home, ".config", "Claude", "claude_desktop_config.json");
15939
15939
  agents.push({
15940
15940
  name: "Claude Desktop",
15941
15941
  detectPath: claudeDesktop,
15942
15942
  installed: () => existsSync13(claudeDesktop),
15943
15943
  write: jsonMcpServersWrite(claudeDesktop)
15944
15944
  });
15945
- const cursor = join23(home, ".cursor", "mcp.json");
15945
+ const cursor = join24(home, ".cursor", "mcp.json");
15946
15946
  agents.push({
15947
15947
  name: "Cursor",
15948
15948
  detectPath: cursor,
15949
- installed: () => existsSync13(join23(home, ".cursor")) || which("cursor"),
15949
+ installed: () => existsSync13(join24(home, ".cursor")) || which("cursor"),
15950
15950
  write: jsonMcpServersWrite(cursor)
15951
15951
  });
15952
- const opencode = join23(home, ".config", "opencode", "opencode.jsonc");
15952
+ const opencode = join24(home, ".config", "opencode", "opencode.jsonc");
15953
15953
  agents.push({
15954
15954
  name: "OpenCode",
15955
15955
  detectPath: opencode,
15956
- installed: () => existsSync13(join23(home, ".config", "opencode")) || which("opencode"),
15956
+ installed: () => existsSync13(join24(home, ".config", "opencode")) || which("opencode"),
15957
15957
  write: opencodeWrite(opencode)
15958
15958
  });
15959
- const codex = join23(home, ".codex", "config.toml");
15959
+ const codex = join24(home, ".codex", "config.toml");
15960
15960
  agents.push({
15961
15961
  name: "Codex",
15962
15962
  detectPath: codex,
15963
- installed: () => existsSync13(join23(home, ".codex")) || which("codex"),
15963
+ installed: () => existsSync13(join24(home, ".codex")) || which("codex"),
15964
15964
  write: codexWrite(codex)
15965
15965
  });
15966
- const hermes = join23(home, ".hermes", "config.yaml");
15966
+ const hermes = join24(home, ".hermes", "config.yaml");
15967
15967
  agents.push({
15968
15968
  name: "Hermes",
15969
15969
  detectPath: hermes,
15970
- installed: () => existsSync13(join23(home, ".hermes")) || which("hermes"),
15970
+ installed: () => existsSync13(join24(home, ".hermes")) || which("hermes"),
15971
15971
  write: hermesWrite(hermes)
15972
15972
  });
15973
- const gemini = join23(home, ".gemini", "settings.json");
15973
+ const gemini = join24(home, ".gemini", "settings.json");
15974
15974
  agents.push({
15975
15975
  name: "Gemini CLI",
15976
15976
  detectPath: gemini,
15977
- installed: () => existsSync13(join23(home, ".gemini")) || which("gemini"),
15977
+ installed: () => existsSync13(join24(home, ".gemini")) || which("gemini"),
15978
15978
  write: jsonMcpServersWrite(gemini)
15979
15979
  });
15980
- const windsurf = join23(home, ".codeium", "windsurf", "mcp_config.json");
15980
+ const windsurf = join24(home, ".codeium", "windsurf", "mcp_config.json");
15981
15981
  agents.push({
15982
15982
  name: "Windsurf",
15983
15983
  detectPath: windsurf,
15984
- installed: () => existsSync13(join23(home, ".codeium")) || which("windsurf"),
15984
+ installed: () => existsSync13(join24(home, ".codeium")) || which("windsurf"),
15985
15985
  write: jsonMcpServersWrite(windsurf)
15986
15986
  });
15987
- const copilot = join23(home, ".copilot", "mcp.json");
15987
+ const copilot = join24(home, ".copilot", "mcp.json");
15988
15988
  agents.push({
15989
15989
  name: "Copilot",
15990
15990
  detectPath: copilot,
15991
- installed: () => existsSync13(join23(home, ".copilot")),
15991
+ installed: () => existsSync13(join24(home, ".copilot")),
15992
15992
  write: jsonMcpServersWrite(copilot)
15993
15993
  });
15994
- const cline = join23(home, ".cline", "mcp_settings.json");
15994
+ const cline = join24(home, ".cline", "mcp_settings.json");
15995
15995
  agents.push({
15996
15996
  name: "Cline",
15997
15997
  detectPath: cline,
15998
- installed: () => existsSync13(join23(home, ".cline")),
15998
+ installed: () => existsSync13(join24(home, ".cline")),
15999
15999
  write: jsonMcpServersWrite(cline)
16000
16000
  });
16001
16001
  return agents;
@@ -16113,7 +16113,7 @@ var init_cli = __esm(() => {
16113
16113
  init_release();
16114
16114
  init_dependency();
16115
16115
  init_mcp_status();
16116
- ROOT10 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
16116
+ ROOT10 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
16117
16117
  });
16118
16118
 
16119
16119
  // node_modules/zod/v3/helpers/util.js
@@ -30751,7 +30751,7 @@ class StdioServerTransport {
30751
30751
 
30752
30752
  // src/server.ts
30753
30753
  init_scanner();
30754
- import { readFileSync as readFileSync24 } from "node:fs";
30754
+ import { readFileSync as readFileSync25 } from "node:fs";
30755
30755
 
30756
30756
  // src/recon.ts
30757
30757
  var TIMEOUT_MS = 25000;
@@ -35526,6 +35526,41 @@ function engagementStatus() {
35526
35526
  next_phase: next
35527
35527
  };
35528
35528
  }
35529
+ function scaffoldEngagement(target, scope = [], out_of_scope = []) {
35530
+ const state = startEngagement(target, "live");
35531
+ const scopeLines = (scope.length ? scope : [target]).map((s) => `- ${s}`).join(`
35532
+ `);
35533
+ const oosLines = (out_of_scope.length ? out_of_scope : ["(none declared)"]).map((s) => `- ${s}`).join(`
35534
+ `);
35535
+ const scopeMd = [
35536
+ `# Engagement scope`,
35537
+ ``,
35538
+ `**Target:** ${target}`,
35539
+ ``,
35540
+ `## In scope`,
35541
+ scopeLines,
35542
+ ``,
35543
+ `## Out of scope`,
35544
+ oosLines,
35545
+ ``,
35546
+ `## Accepted impact classes`,
35547
+ `- (paste the program's vulnerability-type / accepted-impact list here)`,
35548
+ ``,
35549
+ `## Testing rules`,
35550
+ `- (paste rate limits, prohibited areas, and any special rules here)`,
35551
+ ``
35552
+ ].join(`
35553
+ `);
35554
+ return {
35555
+ id: state.id,
35556
+ target,
35557
+ scope,
35558
+ out_of_scope,
35559
+ structure: ["scope.md", "findings/", "evidence/", "reports/", "notes/"],
35560
+ scope_md: scopeMd,
35561
+ state
35562
+ };
35563
+ }
35529
35564
 
35530
35565
  // src/incremental.ts
35531
35566
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -57266,18 +57301,110 @@ function submissionGate(input) {
57266
57301
  };
57267
57302
  }
57268
57303
 
57269
- // src/benchmark.ts
57270
- import { readFileSync as readFileSync18, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57271
- import { join as join17 } from "node:path";
57304
+ // src/severity.ts
57305
+ var VRT_MAP = {
57306
+ sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
57307
+ ssti: { vrt: "injection > server_side_template_injection", priority: "P1", severity: "critical", rationale: "SSTI evaluates expressions server-side → code execution." },
57308
+ deserialization: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Deserialization escalated via a gadget chain → code execution." },
57309
+ xxe: { vrt: "injection > xml_external_entity", priority: "P1", severity: "critical", rationale: "XXE reads local files or reaches internal services." },
57310
+ file_upload: { vrt: "insecure_design > unrestricted_file_upload", priority: "P1", severity: "critical", rationale: "A web-reachable uploaded file of an executable type → code execution." },
57311
+ priv_esc: { vrt: "broken_access_control > privilege_escalation", priority: "P1", severity: "critical", rationale: "A user self-assigns an elevated role → full account/tenant takeover." },
57312
+ path_confusion: { vrt: "broken_access_control > path_traversal", priority: "P2", severity: "high", rationale: "Path traversal reads files or writes outside the intended directory; P1 if it reaches an RCE chain." },
57313
+ ssrf: { vrt: "insecure_design > server_side_request_forgery", priority: "P2", severity: "high", rationale: "SSRF reaches internal services; P1 when it hits cloud metadata and exfiltrates credentials." },
57314
+ mass_assignment: { vrt: "broken_access_control > mass_assignment", priority: "P2", severity: "high", rationale: "Attacker sets a privileged field (role/is_admin) → privilege escalation." },
57315
+ missing_authz: { vrt: "broken_access_control > missing_authorization", priority: "P2", severity: "high", rationale: "A state-changing action lacks a capability/ownership check." },
57316
+ type_juggling: { vrt: "authentication > authentication_bypass", priority: "P2", severity: "high", rationale: "Loose comparison collapses a hash/secret → authentication bypass." },
57317
+ hardcoded_secret: { vrt: "sensitive_data_exposure > hardcoded_credentials", priority: "P2", severity: "high", rationale: "A committed credential is usable by anyone with repo access → account/cloud takeover." },
57318
+ prototype_pollution: { vrt: "injection > prototype_pollution", priority: "P2", severity: "high", rationale: "Object prototype corruption can escalate to code execution in gadget-heavy runtimes." },
57319
+ crlf_injection: { vrt: "injection > crlf_injection", priority: "P3", severity: "medium", rationale: "Header injection enables cookie poisoning or cache poisoning." },
57320
+ missing_nonce: { vrt: "broken_access_control > cross_site_request_forgery", priority: "P3", severity: "medium", rationale: "A state-changing action lacks an anti-CSRF token." },
57321
+ wrong_sanitizer: { vrt: "injection > improper_input_validation", priority: "P3", severity: "medium", rationale: "A value sanitized for the wrong context still reaches the sink; severity follows the sink." }
57322
+ };
57323
+ function severityCalibrate(input) {
57324
+ const type = input.type?.toLowerCase() ?? "";
57325
+ const entry = VRT_MAP[type];
57326
+ if (input.informational) {
57327
+ return {
57328
+ type,
57329
+ vrt_category: "informational",
57330
+ priority: "P5",
57331
+ severity: "informational",
57332
+ rationale: "Marked informational — excluded from the validity ratio.",
57333
+ ...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
57334
+ };
57335
+ }
57336
+ if (!entry) {
57337
+ return {
57338
+ type,
57339
+ vrt_category: "unclassified",
57340
+ priority: "P4",
57341
+ severity: "low",
57342
+ rationale: `No VRT mapping for '${type}'; defaulting to low until classified.`,
57343
+ ...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
57344
+ };
57345
+ }
57346
+ return {
57347
+ type,
57348
+ vrt_category: entry.vrt,
57349
+ priority: entry.priority,
57350
+ severity: entry.severity,
57351
+ rationale: entry.rationale,
57352
+ ...input.cvss != null ? { cvss_severity: cvssSeverity(input.cvss) } : {}
57353
+ };
57354
+ }
57355
+ function listSeverityMap() {
57356
+ return Object.entries(VRT_MAP).map(([type, e]) => ({ type, priority: e.priority, vrt: e.vrt }));
57357
+ }
57358
+
57359
+ // src/datarefresh.ts
57360
+ import { readFileSync as readFileSync18 } from "node:fs";
57361
+ import { join as join17, dirname as dirname3 } from "node:path";
57272
57362
  import { fileURLToPath as fileURLToPath9 } from "node:url";
57363
+ var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
57364
+ function loadJson2(rel) {
57365
+ try {
57366
+ return JSON.parse(readFileSync18(join17(__dirname3, "..", rel), "utf8"));
57367
+ } catch {
57368
+ return null;
57369
+ }
57370
+ }
57371
+ function dataRefresh() {
57372
+ const chains = loadJson2("chains.json");
57373
+ const patterns = loadJson2("intelligence/patterns.json");
57374
+ const cwe = loadJson2("intelligence/cwe_map.json");
57375
+ const chainList = chains?.chains ?? [];
57376
+ const patternList = patterns?.patterns ?? [];
57377
+ const cweList = cwe?.mapping ?? [];
57378
+ const chainIds = new Set(chainList.map((c) => c.id));
57379
+ const cweIds = new Set(cweList.map((m) => m.id));
57380
+ const brokenChainRefs = patternList.flatMap((p) => (p.chain_templates ?? []).filter((c) => !chainIds.has(c)).map((c) => `${p.id} -> ${c}`));
57381
+ const missingCwe = patternList.map((p) => p.id).filter((id) => !cweIds.has(id));
57382
+ return {
57383
+ layers: {
57384
+ chains: chainIds.size,
57385
+ patterns: patternList.length,
57386
+ cwe: cweIds.size
57387
+ },
57388
+ consistency: {
57389
+ ok: brokenChainRefs.length === 0 && missingCwe.length === 0,
57390
+ broken_chain_refs: brokenChainRefs,
57391
+ missing_cwe: missingCwe
57392
+ }
57393
+ };
57394
+ }
57395
+
57396
+ // src/benchmark.ts
57397
+ import { readFileSync as readFileSync19, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57398
+ import { join as join18 } from "node:path";
57399
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
57273
57400
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
57274
- var ROOT5 = join17(fileURLToPath9(new URL(".", import.meta.url)), "..");
57275
- var CORPUS = join17(ROOT5, "bench", "corpus");
57276
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join17(homedir8(), ".blitzstrike", "corpus");
57401
+ var ROOT5 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
57402
+ var CORPUS = join18(ROOT5, "bench", "corpus");
57403
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join18(homedir8(), ".blitzstrike", "corpus");
57277
57404
  function parseCorpusFile(path) {
57278
57405
  let text;
57279
57406
  try {
57280
- text = readFileSync18(path, "utf8");
57407
+ text = readFileSync19(path, "utf8");
57281
57408
  } catch {
57282
57409
  return null;
57283
57410
  }
@@ -57333,7 +57460,7 @@ function loadDir(dir) {
57333
57460
  for (const f of readdirSync5(dir)) {
57334
57461
  if (!f.endsWith(".md"))
57335
57462
  continue;
57336
- const e = parseCorpusFile(join17(dir, f));
57463
+ const e = parseCorpusFile(join18(dir, f));
57337
57464
  if (e)
57338
57465
  entries.push(e);
57339
57466
  }
@@ -57360,7 +57487,7 @@ function captureFalsePositive(input) {
57360
57487
  input.code,
57361
57488
  "```"
57362
57489
  ].filter((l) => l !== null);
57363
- const path = join17(CAPTURED_CORPUS_DIR, `${id}.md`);
57490
+ const path = join18(CAPTURED_CORPUS_DIR, `${id}.md`);
57364
57491
  writeFileSync6(path, lines.join(`
57365
57492
  `) + `
57366
57493
  `, "utf8");
@@ -57382,7 +57509,7 @@ function detect(entry) {
57382
57509
  }
57383
57510
  const lang = entry.language;
57384
57511
  if (lang === "php") {
57385
- const tmp = join17(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57512
+ const tmp = join18(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57386
57513
  writeFileSync6(tmp, entry.code);
57387
57514
  const r = analyzeDataFlow2(tmp);
57388
57515
  rmSync3(tmp, { force: true });
@@ -57451,12 +57578,12 @@ function runBenchmark() {
57451
57578
  }
57452
57579
 
57453
57580
  // src/obligations.ts
57454
- import { readFileSync as readFileSync19, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
57455
- import { join as join18 } from "node:path";
57581
+ import { readFileSync as readFileSync20, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
57582
+ import { join as join19 } from "node:path";
57456
57583
  import { createHash as createHash6 } from "node:crypto";
57457
57584
  import { homedir as homedir9 } from "node:os";
57458
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join18(homedir9(), ".blitzstrike");
57459
- var OBLIGATION_PATH = join18(HOME_DIR4, "obligations.jsonl");
57585
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join19(homedir9(), ".blitzstrike");
57586
+ var OBLIGATION_PATH = join19(HOME_DIR4, "obligations.jsonl");
57460
57587
  function ensureDir3() {
57461
57588
  if (!existsSync10(HOME_DIR4))
57462
57589
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -57469,7 +57596,7 @@ function loadObligations() {
57469
57596
  return [];
57470
57597
  try {
57471
57598
  const out = [];
57472
- for (const line of readFileSync19(OBLIGATION_PATH, "utf8").split(`
57599
+ for (const line of readFileSync20(OBLIGATION_PATH, "utf8").split(`
57473
57600
  `)) {
57474
57601
  const t = line.trim();
57475
57602
  if (!t)
@@ -57568,11 +57695,11 @@ function openCount() {
57568
57695
 
57569
57696
  // src/variant-scan.ts
57570
57697
  init_scanner();
57571
- import { readFileSync as readFileSync21 } from "node:fs";
57698
+ import { readFileSync as readFileSync22 } from "node:fs";
57572
57699
 
57573
57700
  // src/cross-file.ts
57574
57701
  init_scanner();
57575
- import { readFileSync as readFileSync20 } from "node:fs";
57702
+ import { readFileSync as readFileSync21 } from "node:fs";
57576
57703
  var PUBLIC_HOOK_RE = /(?:wp_ajax_nopriv_\w+|admin_post_nopriv_\w+|init|template_redirect|wp_loaded|parse_request|user_register|um_user_register|register_new_user|authenticate)/;
57577
57704
  var PRIV_SINK_RE = /\b(set_role|add_role|remove_role|set_user_role|wp_set_current_user|wp_set_auth_cookie|wp_insert_user|wp_create_user|wp_update_user)\s*\(/;
57578
57705
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -57624,7 +57751,7 @@ function detectCrossFilePrivesc(root) {
57624
57751
  for (const file of files) {
57625
57752
  let code;
57626
57753
  try {
57627
- code = stripComments(readFileSync20(file, "utf8"));
57754
+ code = stripComments(readFileSync21(file, "utf8"));
57628
57755
  } catch {
57629
57756
  continue;
57630
57757
  }
@@ -57665,7 +57792,7 @@ function detectCrossFilePrivesc(root) {
57665
57792
  for (const file of files) {
57666
57793
  let code;
57667
57794
  try {
57668
- code = stripComments(readFileSync20(file, "utf8"));
57795
+ code = stripComments(readFileSync21(file, "utf8"));
57669
57796
  } catch {
57670
57797
  continue;
57671
57798
  }
@@ -57688,7 +57815,7 @@ function detectCrossFilePrivesc(root) {
57688
57815
  for (const file of files) {
57689
57816
  let code;
57690
57817
  try {
57691
- code = stripComments(readFileSync20(file, "utf8"));
57818
+ code = stripComments(readFileSync21(file, "utf8"));
57692
57819
  } catch {
57693
57820
  continue;
57694
57821
  }
@@ -57803,7 +57930,7 @@ function variantScan(root, opts = {}) {
57803
57930
  for (const file of files) {
57804
57931
  let code;
57805
57932
  try {
57806
- code = readFileSync21(file, "utf8");
57933
+ code = readFileSync22(file, "utf8");
57807
57934
  } catch {
57808
57935
  continue;
57809
57936
  }
@@ -58463,16 +58590,16 @@ function analyzeJwt(token, opts = {}) {
58463
58590
  }
58464
58591
 
58465
58592
  // src/enterprise-benchmark.ts
58466
- import { existsSync as existsSync11, readFileSync as readFileSync22, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
58467
- import { join as join19, resolve } from "node:path";
58593
+ import { existsSync as existsSync11, readFileSync as readFileSync23, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
58594
+ import { join as join20, resolve } from "node:path";
58468
58595
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
58469
58596
  var ENTERPRISE_FIXTURES = [
58470
- { id: "php-laravel", dir: join19(ROOT6, "php-laravel"), expected_vulns: 4 },
58471
- { id: "js-express", dir: join19(ROOT6, "js-express"), expected_vulns: 2 },
58472
- { id: "py-django", dir: join19(ROOT6, "py-django"), expected_vulns: 2 },
58473
- { id: "java-spring", dir: join19(ROOT6, "java-spring"), expected_vulns: 2 },
58474
- { id: "wp-batch-route", dir: join19(ROOT6, "wp-batch-route"), expected_vulns: 3 },
58475
- { id: "php-complex", dir: join19(ROOT6, "php-complex"), expected_vulns: 4 }
58597
+ { id: "php-laravel", dir: join20(ROOT6, "php-laravel"), expected_vulns: 4 },
58598
+ { id: "js-express", dir: join20(ROOT6, "js-express"), expected_vulns: 2 },
58599
+ { id: "py-django", dir: join20(ROOT6, "py-django"), expected_vulns: 2 },
58600
+ { id: "java-spring", dir: join20(ROOT6, "java-spring"), expected_vulns: 2 },
58601
+ { id: "wp-batch-route", dir: join20(ROOT6, "wp-batch-route"), expected_vulns: 3 },
58602
+ { id: "php-complex", dir: join20(ROOT6, "php-complex"), expected_vulns: 4 }
58476
58603
  ];
58477
58604
  function sourceFiles(root) {
58478
58605
  const out = [];
@@ -58484,7 +58611,7 @@ function sourceFiles(root) {
58484
58611
  return;
58485
58612
  }
58486
58613
  for (const e of entries) {
58487
- const p = join19(dir, e);
58614
+ const p = join20(dir, e);
58488
58615
  let st;
58489
58616
  try {
58490
58617
  st = statSync3(p);
@@ -58505,7 +58632,7 @@ function detectFixture(dir) {
58505
58632
  for (const f of sourceFiles(dir)) {
58506
58633
  let code;
58507
58634
  try {
58508
- code = readFileSync22(f, "utf8");
58635
+ code = readFileSync23(f, "utf8");
58509
58636
  } catch {
58510
58637
  continue;
58511
58638
  }
@@ -58700,8 +58827,7 @@ function createServer() {
58700
58827
  ` + `- A hard block (Cloudflare/WAF/IP-block/captcha) is NOT the end: mark the finding blocked + note the required vantage (residential / authorized / correct-SNI / --resolve direct-to-origin), then CONTINUE to the next lead (origin bypass, non-prod cluster, mobile APK, subdomain).
58701
58828
  ` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
58702
58829
 
58703
- ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + `DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, and citable references, so a raw hit becomes a class-aware finding. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.
58704
- ` + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
58830
+ ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, and citable references, so a raw hit becomes a class-aware finding. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
58705
58831
  ` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
58706
58832
  ` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
58707
58833
  ` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
@@ -59087,6 +59213,38 @@ ${statusLine}` : statusLine },
59087
59213
  }, async (args) => {
59088
59214
  return { content: [{ type: "text", text: JSON.stringify(submissionGate(args)) }] };
59089
59215
  });
59216
+ server.registerTool("severity_calibrate", {
59217
+ title: "Severity calibration (VRT-style)",
59218
+ description: "SEVERITY: calibrate a finding to a platform-neutral priority tier (P1–P5) + a VRT-style category, so it is reported at the severity a triager would assign. Maps a detector type (sql_injection, ssrf, ssti, …) to its category + rationale; pass a CVSS score to cross-reference, or 'list' to enumerate every calibrated class.",
59219
+ inputSchema: {
59220
+ type: string2().optional().describe("Detector/vuln class id, or 'list'"),
59221
+ severity: string2().optional().describe("Detector-reported severity (informational for reference)"),
59222
+ cvss: number2().min(0).max(10).optional().describe("CVSS base score (0–10) to cross-reference"),
59223
+ informational: boolean2().optional().describe("True if the finding is informational-only")
59224
+ }
59225
+ }, async ({ type, severity, cvss, informational }) => {
59226
+ if (type === "list")
59227
+ return { content: [{ type: "text", text: JSON.stringify(listSeverityMap()) }] };
59228
+ return { content: [{ type: "text", text: JSON.stringify(severityCalibrate({ type, severity, cvss, informational })) }] };
59229
+ });
59230
+ server.registerTool("engagement_scaffold", {
59231
+ title: "Engagement scaffold",
59232
+ description: "SCAFFOLD: open an engagement and emit a deterministic folder layout (scope.md, findings/, evidence/, reports/, notes/) + a pre-filled scope.md template. Returns the engagement id + initial state so the hunt starts with structure, not improvisation.",
59233
+ inputSchema: {
59234
+ target: string2().describe("The in-scope target (domain/URL/program)"),
59235
+ scope: array(string2()).optional().describe("In-scope assets/domains"),
59236
+ out_of_scope: array(string2()).optional().describe("Out-of-scope assets")
59237
+ }
59238
+ }, async ({ target, scope, out_of_scope }) => {
59239
+ return { content: [{ type: "text", text: JSON.stringify(scaffoldEngagement(target, scope ?? [], out_of_scope ?? [])) }] };
59240
+ });
59241
+ server.registerTool("data_refresh", {
59242
+ title: "Data-layer health check",
59243
+ description: "HEALTH: re-load every data layer (chains, patterns, CWE map) and verify cross-references — a pattern pointing at a missing chain, or a detector without a CWE mapping. Returns counts + any broken reference, so stale data is surfaced instead of silently producing bad grounding.",
59244
+ inputSchema: {}
59245
+ }, async () => {
59246
+ return { content: [{ type: "text", text: JSON.stringify(dataRefresh()) }] };
59247
+ });
59090
59248
  server.registerTool("fofa_search", {
59091
59249
  title: "FOFA asset search",
59092
59250
  description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
@@ -59471,7 +59629,7 @@ ${statusLine}` : statusLine },
59471
59629
  let src = code;
59472
59630
  if (!src && path) {
59473
59631
  try {
59474
- src = readFileSync24(path, "utf8");
59632
+ src = readFileSync25(path, "utf8");
59475
59633
  } catch {
59476
59634
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
59477
59635
  }
@@ -60089,7 +60247,7 @@ ${statusLine}` : statusLine },
60089
60247
  description: "Route confusion (separate class from source→sink taint): detect attacker-controlled dispatch (call_user_func/forward/dispatch), dynamic method calls ($obj->$m()), dynamic include (LFI), batch/proxy forwarding without per-route auth re-check, and route matching without path normalization.",
60090
60248
  inputSchema: { path: string2().describe("Source file path (PHP)") }
60091
60249
  }, async ({ path }) => {
60092
- const code = readFileSync24(path, "utf8");
60250
+ const code = readFileSync25(path, "utf8");
60093
60251
  const findings = detectRouteConfusion(code, path);
60094
60252
  return {
60095
60253
  content: [{
@@ -60103,7 +60261,7 @@ ${statusLine}` : statusLine },
60103
60261
  description: "Complex-bug detection beyond taint: deserialization→POP gadget chain (unserialize + magic method), type juggling (loose ==/!= vs hash/secret), mass assignment (extract/parse_str without a safe flag), prototype pollution (unsafe merge of request data), CRLF/header injection (user input into a header), path confusion (user input into a file path without canonicalization), SSRF (user-controlled URL with no host allowlist), XXE (user XML parsed without disabling entities), SSTI (user input into a template render with no sandbox), wrong-context sanitization (sanitize_text_field() into a SQL query), and missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback).",
60104
60262
  inputSchema: { path: string2().describe("Source file path") }
60105
60263
  }, async ({ path }) => {
60106
- const code = readFileSync24(path, "utf8");
60264
+ const code = readFileSync25(path, "utf8");
60107
60265
  const findings = detectComplexBugs(code, path);
60108
60266
  return {
60109
60267
  content: [{
@@ -60189,7 +60347,7 @@ ${statusLine}` : statusLine },
60189
60347
  let lang = language;
60190
60348
  if (path && !code) {
60191
60349
  try {
60192
- src = readFileSync24(path, "utf8");
60350
+ src = readFileSync25(path, "utf8");
60193
60351
  } catch {
60194
60352
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
60195
60353
  }
@@ -60430,15 +60588,15 @@ async function serve() {
60430
60588
 
60431
60589
  // src/index.ts
60432
60590
  init_cli();
60433
- import { readFileSync as readFileSync28, existsSync as existsSync14 } from "node:fs";
60434
- import { join as join24 } from "node:path";
60435
- import { fileURLToPath as fileURLToPath14 } from "node:url";
60436
- var ROOT11 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
60591
+ import { readFileSync as readFileSync29, existsSync as existsSync14 } from "node:fs";
60592
+ import { join as join25 } from "node:path";
60593
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
60594
+ var ROOT11 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
60437
60595
  function readVersion() {
60438
60596
  try {
60439
- const p = join24(ROOT11, "package.json");
60597
+ const p = join25(ROOT11, "package.json");
60440
60598
  if (existsSync14(p))
60441
- return JSON.parse(readFileSync28(p, "utf8")).version ?? "1.0.0";
60599
+ return JSON.parse(readFileSync29(p, "utf8")).version ?? "1.0.0";
60442
60600
  } catch {}
60443
60601
  return "1.0.0";
60444
60602
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.58",
3
+ "version": "2.4.59",
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": {