blitzstrike 2.4.107 → 2.4.108

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.
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.108] — 2026-09-17
9
+
10
+ ### Added — surface-first map (attack surface → high-value tests)
11
+
12
+ - New `surface_map(hint)` tool + `intelligence/surface-map.json`: maps 8 attack
13
+ surfaces to their highest-value test cases, derived from public target-recon
14
+ dossiers (image proxy → SSRF/URL-bypass/XSS; redirect proxy → open-redirect/
15
+ SSRF/bypass; edge cache → IDOR/cache-poisoning; payment portal →
16
+ price-manipulation/IDOR/subscription; AI agent → prompt-injection→SSRF-RCE/
17
+ deser/stored-XSS; file service → XSS-via-content/SSRF/upload-RCE; Electron →
18
+ HTML-injection→BrowserWindow-RCE/IPC; SSO → OAuth-redirect/JWT/2FA).
19
+ - `matchSurfaces(techs, headers, body)` fingerprints the detected stack against
20
+ the map; `live_recon` now emits `suggested_surfaces` + a SURFACE-FIRST next
21
+ step. Added doctrine rule `surface_first` ("map surfaces before spraying
22
+ payloads").
23
+
8
24
  ## [2.4.107] — 2026-09-17
9
25
 
10
26
  ### Added — `review_harness` (per-file review, full-coverage deterministic rubric)
package/dist/index.js CHANGED
@@ -17388,12 +17388,12 @@ document.getElementById('out').innerHTML = u;
17388
17388
  });
17389
17389
 
17390
17390
  // src/variants.ts
17391
- import { readFileSync as readFileSync22 } from "node:fs";
17392
- import { join as join21, dirname as dirname5 } from "node:path";
17393
- import { fileURLToPath as fileURLToPath12 } from "node:url";
17391
+ import { readFileSync as readFileSync23 } from "node:fs";
17392
+ import { join as join22, dirname as dirname5 } from "node:path";
17393
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
17394
17394
  function loadVariants() {
17395
17395
  try {
17396
- const doc = JSON.parse(readFileSync22(join21(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
17396
+ const doc = JSON.parse(readFileSync23(join22(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
17397
17397
  return doc.variants ?? [];
17398
17398
  } catch {
17399
17399
  return [];
@@ -17447,15 +17447,15 @@ function variantCoverageText() {
17447
17447
  var __dirname5, CROSS_FILE_ONLY;
17448
17448
  var init_variants = __esm(() => {
17449
17449
  init_complex_bugs();
17450
- __dirname5 = dirname5(fileURLToPath12(import.meta.url));
17450
+ __dirname5 = dirname5(fileURLToPath13(import.meta.url));
17451
17451
  CROSS_FILE_ONLY = new Set(["priv_esc"]);
17452
17452
  });
17453
17453
 
17454
17454
  // src/sync.ts
17455
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync28, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync5 } from "node:fs";
17456
- import { join as join26 } from "node:path";
17455
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync29, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync5 } from "node:fs";
17456
+ import { join as join27 } from "node:path";
17457
17457
  import { homedir as homedir10 } from "node:os";
17458
- import { fileURLToPath as fileURLToPath15 } from "node:url";
17458
+ import { fileURLToPath as fileURLToPath16 } from "node:url";
17459
17459
  import { spawnSync as spawnSync4 } from "node:child_process";
17460
17460
  function dirExists(p) {
17461
17461
  try {
@@ -17470,7 +17470,7 @@ function countFiles(dir) {
17470
17470
  const walk = (d) => {
17471
17471
  for (const e of readdirSync7(d, { withFileTypes: true })) {
17472
17472
  if (e.isDirectory())
17473
- walk(join26(d, e.name));
17473
+ walk(join27(d, e.name));
17474
17474
  else
17475
17475
  n++;
17476
17476
  }
@@ -17484,8 +17484,8 @@ function countFiles(dir) {
17484
17484
  function syncData() {
17485
17485
  if (!dirExists(DATA_ROOT2))
17486
17486
  mkdirSync10(DATA_ROOT2, { recursive: true });
17487
- const payloadsPath = join26(DATA_ROOT2, "payloads");
17488
- const templatesPath = join26(DATA_ROOT2, "templates");
17487
+ const payloadsPath = join27(DATA_ROOT2, "payloads");
17488
+ const templatesPath = join27(DATA_ROOT2, "templates");
17489
17489
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
17490
17490
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
17491
17491
  if (hasPayloads && hasTemplates) {
@@ -17495,8 +17495,8 @@ function syncData() {
17495
17495
  data_root: DATA_ROOT2
17496
17496
  };
17497
17497
  }
17498
- const localPayloads = join26(ROOT8, "payloads");
17499
- const localTemplates = join26(ROOT8, "templates");
17498
+ const localPayloads = join27(ROOT9, "payloads");
17499
+ const localTemplates = join27(ROOT9, "templates");
17500
17500
  let payloadsSynced = false;
17501
17501
  let templatesSynced = false;
17502
17502
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -17509,7 +17509,7 @@ function syncData() {
17509
17509
  }
17510
17510
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
17511
17511
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
17512
- const tmp = join26(DATA_ROOT2, ".tmp-clone");
17512
+ const tmp = join27(DATA_ROOT2, ".tmp-clone");
17513
17513
  if (dirExists(tmp))
17514
17514
  rmSync5(tmp, { recursive: true, force: true });
17515
17515
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -17517,11 +17517,11 @@ function syncData() {
17517
17517
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
17518
17518
  if (sparse.status === 0) {
17519
17519
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
17520
- copyDir(join26(tmp, "payloads"), payloadsPath);
17520
+ copyDir(join27(tmp, "payloads"), payloadsPath);
17521
17521
  payloadsSynced = true;
17522
17522
  }
17523
17523
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
17524
- copyDir(join26(tmp, "templates"), templatesPath);
17524
+ copyDir(join27(tmp, "templates"), templatesPath);
17525
17525
  templatesSynced = true;
17526
17526
  }
17527
17527
  }
@@ -17543,18 +17543,18 @@ function copyDir(src, dst) {
17543
17543
  function manualCopy(src, dst) {
17544
17544
  mkdirSync10(dst, { recursive: true });
17545
17545
  for (const e of readdirSync7(src, { withFileTypes: true })) {
17546
- const s = join26(src, e.name);
17547
- const d = join26(dst, e.name);
17546
+ const s = join27(src, e.name);
17547
+ const d = join27(dst, e.name);
17548
17548
  if (e.isDirectory())
17549
17549
  manualCopy(s, d);
17550
17550
  else
17551
- writeFileSync9(d, readFileSync28(s));
17551
+ writeFileSync9(d, readFileSync29(s));
17552
17552
  }
17553
17553
  }
17554
- var ROOT8, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
17554
+ var ROOT9, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
17555
17555
  var init_sync = __esm(() => {
17556
- ROOT8 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
17557
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join26(homedir10(), ".blitzstrike", "data");
17556
+ ROOT9 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
17557
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join27(homedir10(), ".blitzstrike", "data");
17558
17558
  });
17559
17559
 
17560
17560
  // src/update.ts
@@ -17599,15 +17599,15 @@ var init_update = __esm(() => {
17599
17599
  });
17600
17600
 
17601
17601
  // src/release.ts
17602
- import { readFileSync as readFileSync30, existsSync as existsSync16 } from "node:fs";
17603
- import { join as join27 } from "node:path";
17604
- import { fileURLToPath as fileURLToPath16 } from "node:url";
17602
+ import { readFileSync as readFileSync31, existsSync as existsSync16 } from "node:fs";
17603
+ import { join as join28 } from "node:path";
17604
+ import { fileURLToPath as fileURLToPath17 } from "node:url";
17605
17605
  import { execSync as execSync4 } from "node:child_process";
17606
17606
  function validateRelease() {
17607
17607
  const checks = [];
17608
17608
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
17609
17609
  try {
17610
- const changelog = readFileSync30(join27(ROOT9, "CHANGELOG.md"), "utf8");
17610
+ const changelog = readFileSync31(join28(ROOT10, "CHANGELOG.md"), "utf8");
17611
17611
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
17612
17612
  const changelogVersion = m?.[1] ?? null;
17613
17613
  checks.push({
@@ -17619,7 +17619,7 @@ function validateRelease() {
17619
17619
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
17620
17620
  }
17621
17621
  try {
17622
- const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT9, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17622
+ const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT10, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17623
17623
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
17624
17624
  } catch {
17625
17625
  checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git/tags)", soft: true });
@@ -17639,22 +17639,22 @@ function validateRelease() {
17639
17639
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
17640
17640
  }
17641
17641
  function lockfilePresent() {
17642
- return existsSync16(join27(ROOT9, "package-lock.json")) || existsSync16(join27(ROOT9, "bun.lock")) || existsSync16(join27(ROOT9, "bun.lockb"));
17642
+ return existsSync16(join28(ROOT10, "package-lock.json")) || existsSync16(join28(ROOT10, "bun.lock")) || existsSync16(join28(ROOT10, "bun.lockb"));
17643
17643
  }
17644
- var ROOT9;
17644
+ var ROOT10;
17645
17645
  var init_release = __esm(() => {
17646
17646
  init_version();
17647
- ROOT9 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
17647
+ ROOT10 = join28(fileURLToPath17(new URL(".", import.meta.url)), "..");
17648
17648
  });
17649
17649
 
17650
17650
  // src/dependency.ts
17651
- import { readFileSync as readFileSync31 } from "node:fs";
17652
- import { join as join28 } from "node:path";
17653
- import { fileURLToPath as fileURLToPath17 } from "node:url";
17651
+ import { readFileSync as readFileSync32 } from "node:fs";
17652
+ import { join as join29 } from "node:path";
17653
+ import { fileURLToPath as fileURLToPath18 } from "node:url";
17654
17654
  function checkDependencies() {
17655
17655
  let pkg = {};
17656
17656
  try {
17657
- pkg = JSON.parse(readFileSync31(join28(ROOT10, "package.json"), "utf8"));
17657
+ pkg = JSON.parse(readFileSync32(join29(ROOT11, "package.json"), "utf8"));
17658
17658
  } catch {
17659
17659
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
17660
17660
  }
@@ -17675,16 +17675,16 @@ function checkDependencies() {
17675
17675
  safe: installScripts.length === 0 && gitDependencies.length === 0
17676
17676
  };
17677
17677
  }
17678
- var ROOT10;
17678
+ var ROOT11;
17679
17679
  var init_dependency = __esm(() => {
17680
- ROOT10 = join28(fileURLToPath17(new URL(".", import.meta.url)), "..");
17680
+ ROOT11 = join29(fileURLToPath18(new URL(".", import.meta.url)), "..");
17681
17681
  });
17682
17682
 
17683
17683
  // src/cli.ts
17684
- import { readFileSync as readFileSync32, existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "node:fs";
17685
- import { join as join29, dirname as dirname6 } from "node:path";
17684
+ import { readFileSync as readFileSync33, existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "node:fs";
17685
+ import { join as join30, dirname as dirname6 } from "node:path";
17686
17686
  import { homedir as homedir11 } from "node:os";
17687
- import { fileURLToPath as fileURLToPath18 } from "node:url";
17687
+ import { fileURLToPath as fileURLToPath19 } from "node:url";
17688
17688
  import { spawnSync as spawnSync5 } from "node:child_process";
17689
17689
  function which(cmd) {
17690
17690
  return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
@@ -17727,7 +17727,7 @@ async function runDoctor() {
17727
17727
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17728
17728
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17729
17729
  });
17730
- const dataOk = existsSync17(join29(ROOT11, "chains.json")) && existsSync17(join29(ROOT11, "tools-catalog.json"));
17730
+ const dataOk = existsSync17(join30(ROOT12, "chains.json")) && existsSync17(join30(ROOT12, "tools-catalog.json"));
17731
17731
  issues.push({
17732
17732
  name: "Data layers (chains + tools-catalog)",
17733
17733
  status: dataOk ? "ok" : "fail",
@@ -17778,21 +17778,21 @@ BlitzStrike — ${verdict}`);
17778
17778
  function resolveCommand() {
17779
17779
  if (which("blitzstrike"))
17780
17780
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17781
- const src = join29(ROOT11, "src", "index.ts");
17781
+ const src = join30(ROOT12, "src", "index.ts");
17782
17782
  if (which("bun") && existsSync17(src))
17783
17783
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17784
17784
  if (which("npx"))
17785
17785
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17786
17786
  if (which("bunx"))
17787
17787
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17788
- const dist = join29(ROOT11, "dist", "index.js");
17788
+ const dist = join30(ROOT12, "dist", "index.js");
17789
17789
  if (existsSync17(dist))
17790
17790
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17791
17791
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17792
17792
  }
17793
17793
  function readJson(p) {
17794
17794
  try {
17795
- return JSON.parse(readFileSync32(p, "utf8"));
17795
+ return JSON.parse(readFileSync33(p, "utf8"));
17796
17796
  } catch {
17797
17797
  return null;
17798
17798
  }
@@ -17825,17 +17825,17 @@ function opencodeWrite(p) {
17825
17825
  };
17826
17826
  }
17827
17827
  function copyOpenCodeAgents() {
17828
- const srcDir = join29(ROOT11, "opencode-agents");
17829
- const dstDir = join29(homedir11(), ".config", "opencode", "agents");
17828
+ const srcDir = join30(ROOT12, "opencode-agents");
17829
+ const dstDir = join30(homedir11(), ".config", "opencode", "agents");
17830
17830
  if (!existsSync17(srcDir))
17831
17831
  return;
17832
17832
  if (!existsSync17(dstDir))
17833
17833
  mkdirSync11(dstDir, { recursive: true });
17834
17834
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17835
- const src = join29(srcDir, f);
17836
- const dst = join29(dstDir, f);
17835
+ const src = join30(srcDir, f);
17836
+ const dst = join30(dstDir, f);
17837
17837
  if (existsSync17(src)) {
17838
- writeFileSync10(dst, readFileSync32(src, "utf8"));
17838
+ writeFileSync10(dst, readFileSync33(src, "utf8"));
17839
17839
  }
17840
17840
  }
17841
17841
  }
@@ -17844,7 +17844,7 @@ function codexWrite(p) {
17844
17844
  const dir = dirname6(p);
17845
17845
  if (!existsSync17(dir))
17846
17846
  mkdirSync11(dir, { recursive: true });
17847
- let existing = existsSync17(p) ? readFileSync32(p, "utf8") : "";
17847
+ let existing = existsSync17(p) ? readFileSync33(p, "utf8") : "";
17848
17848
  if (!existing.trimEnd().endsWith(`
17849
17849
  `))
17850
17850
  existing += `
@@ -17863,7 +17863,7 @@ function hermesWrite(p) {
17863
17863
  const dir = dirname6(p);
17864
17864
  if (!existsSync17(dir))
17865
17865
  mkdirSync11(dir, { recursive: true });
17866
- let existing = existsSync17(p) ? readFileSync32(p, "utf8") : "";
17866
+ let existing = existsSync17(p) ? readFileSync33(p, "utf8") : "";
17867
17867
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17868
17868
  if (!existing.trimEnd().endsWith(`
17869
17869
  `))
@@ -17890,74 +17890,74 @@ mcp_servers:
17890
17890
  function detectAgents() {
17891
17891
  const home = homedir11();
17892
17892
  const agents = [];
17893
- const claudeJson = join29(home, ".claude.json");
17893
+ const claudeJson = join30(home, ".claude.json");
17894
17894
  agents.push({
17895
17895
  name: "Claude Code",
17896
17896
  detectPath: claudeJson,
17897
17897
  installed: () => existsSync17(claudeJson) || which("claude"),
17898
17898
  write: jsonMcpServersWrite(claudeJson)
17899
17899
  });
17900
- const claudeDesktop = join29(home, ".config", "Claude", "claude_desktop_config.json");
17900
+ const claudeDesktop = join30(home, ".config", "Claude", "claude_desktop_config.json");
17901
17901
  agents.push({
17902
17902
  name: "Claude Desktop",
17903
17903
  detectPath: claudeDesktop,
17904
17904
  installed: () => existsSync17(claudeDesktop),
17905
17905
  write: jsonMcpServersWrite(claudeDesktop)
17906
17906
  });
17907
- const cursor = join29(home, ".cursor", "mcp.json");
17907
+ const cursor = join30(home, ".cursor", "mcp.json");
17908
17908
  agents.push({
17909
17909
  name: "Cursor",
17910
17910
  detectPath: cursor,
17911
- installed: () => existsSync17(join29(home, ".cursor")) || which("cursor"),
17911
+ installed: () => existsSync17(join30(home, ".cursor")) || which("cursor"),
17912
17912
  write: jsonMcpServersWrite(cursor)
17913
17913
  });
17914
- const opencode = join29(home, ".config", "opencode", "opencode.jsonc");
17914
+ const opencode = join30(home, ".config", "opencode", "opencode.jsonc");
17915
17915
  agents.push({
17916
17916
  name: "OpenCode",
17917
17917
  detectPath: opencode,
17918
- installed: () => existsSync17(join29(home, ".config", "opencode")) || which("opencode"),
17918
+ installed: () => existsSync17(join30(home, ".config", "opencode")) || which("opencode"),
17919
17919
  write: opencodeWrite(opencode)
17920
17920
  });
17921
- const codex = join29(home, ".codex", "config.toml");
17921
+ const codex = join30(home, ".codex", "config.toml");
17922
17922
  agents.push({
17923
17923
  name: "Codex",
17924
17924
  detectPath: codex,
17925
- installed: () => existsSync17(join29(home, ".codex")) || which("codex"),
17925
+ installed: () => existsSync17(join30(home, ".codex")) || which("codex"),
17926
17926
  write: codexWrite(codex)
17927
17927
  });
17928
- const hermes = join29(home, ".hermes", "config.yaml");
17928
+ const hermes = join30(home, ".hermes", "config.yaml");
17929
17929
  agents.push({
17930
17930
  name: "Hermes",
17931
17931
  detectPath: hermes,
17932
- installed: () => existsSync17(join29(home, ".hermes")) || which("hermes"),
17932
+ installed: () => existsSync17(join30(home, ".hermes")) || which("hermes"),
17933
17933
  write: hermesWrite(hermes)
17934
17934
  });
17935
- const gemini = join29(home, ".gemini", "settings.json");
17935
+ const gemini = join30(home, ".gemini", "settings.json");
17936
17936
  agents.push({
17937
17937
  name: "Gemini CLI",
17938
17938
  detectPath: gemini,
17939
- installed: () => existsSync17(join29(home, ".gemini")) || which("gemini"),
17939
+ installed: () => existsSync17(join30(home, ".gemini")) || which("gemini"),
17940
17940
  write: jsonMcpServersWrite(gemini)
17941
17941
  });
17942
- const windsurf = join29(home, ".codeium", "windsurf", "mcp_config.json");
17942
+ const windsurf = join30(home, ".codeium", "windsurf", "mcp_config.json");
17943
17943
  agents.push({
17944
17944
  name: "Windsurf",
17945
17945
  detectPath: windsurf,
17946
- installed: () => existsSync17(join29(home, ".codeium")) || which("windsurf"),
17946
+ installed: () => existsSync17(join30(home, ".codeium")) || which("windsurf"),
17947
17947
  write: jsonMcpServersWrite(windsurf)
17948
17948
  });
17949
- const copilot = join29(home, ".copilot", "mcp.json");
17949
+ const copilot = join30(home, ".copilot", "mcp.json");
17950
17950
  agents.push({
17951
17951
  name: "Copilot",
17952
17952
  detectPath: copilot,
17953
- installed: () => existsSync17(join29(home, ".copilot")),
17953
+ installed: () => existsSync17(join30(home, ".copilot")),
17954
17954
  write: jsonMcpServersWrite(copilot)
17955
17955
  });
17956
- const cline = join29(home, ".cline", "mcp_settings.json");
17956
+ const cline = join30(home, ".cline", "mcp_settings.json");
17957
17957
  agents.push({
17958
17958
  name: "Cline",
17959
17959
  detectPath: cline,
17960
- installed: () => existsSync17(join29(home, ".cline")),
17960
+ installed: () => existsSync17(join30(home, ".cline")),
17961
17961
  write: jsonMcpServersWrite(cline)
17962
17962
  });
17963
17963
  return agents;
@@ -18069,13 +18069,13 @@ Failed (${failedList.length}) — reason per tool:`);
18069
18069
  Common causes: missing toolchain, pip/git network failures, or very heavy builds. ` + `Re-run after installing the missing toolchain — failed tools are skipped, not fatal.`);
18070
18070
  }
18071
18071
  }
18072
- var ROOT11;
18072
+ var ROOT12;
18073
18073
  var init_cli = __esm(() => {
18074
18074
  init_catalog();
18075
18075
  init_release();
18076
18076
  init_dependency();
18077
18077
  init_mcp_status();
18078
- ROOT11 = join29(fileURLToPath18(new URL(".", import.meta.url)), "..");
18078
+ ROOT12 = join30(fileURLToPath19(new URL(".", import.meta.url)), "..");
18079
18079
  });
18080
18080
 
18081
18081
  // node_modules/zod/v3/helpers/util.js
@@ -32713,7 +32713,7 @@ class StdioServerTransport {
32713
32713
 
32714
32714
  // src/server.ts
32715
32715
  init_scanner();
32716
- import { readFileSync as readFileSync29 } from "node:fs";
32716
+ import { readFileSync as readFileSync30 } from "node:fs";
32717
32717
 
32718
32718
  // src/review-harness.ts
32719
32719
  init_scanner();
@@ -32879,9 +32879,9 @@ async function nvdLookup(cveId) {
32879
32879
 
32880
32880
  // src/orchestrator.ts
32881
32881
  init_scanner();
32882
- import { readFileSync as readFileSync12, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
32883
- import { join as join13 } from "node:path";
32884
- import { fileURLToPath as fileURLToPath7 } from "node:url";
32882
+ import { readFileSync as readFileSync13, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
32883
+ import { join as join14 } from "node:path";
32884
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
32885
32885
 
32886
32886
  // src/memory.ts
32887
32887
  import { readFileSync as readFileSync3, appendFileSync, mkdirSync, existsSync, writeFileSync } from "node:fs";
@@ -36444,6 +36444,33 @@ function watchlistCveMatches(techs, headers = {}, body = "", versions = []) {
36444
36444
  });
36445
36445
  }
36446
36446
 
36447
+ // src/surface-map.ts
36448
+ import { readFileSync as readFileSync12 } from "node:fs";
36449
+ import { join as join13 } from "node:path";
36450
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
36451
+ var ROOT5 = join13(fileURLToPath7(new URL(".", import.meta.url)), "..");
36452
+ function load2() {
36453
+ const raw = readFileSync12(join13(ROOT5, "intelligence", "surface-map.json"), "utf8");
36454
+ return JSON.parse(raw);
36455
+ }
36456
+ function surfaceMap(hint = "") {
36457
+ const data = load2();
36458
+ if (!hint)
36459
+ return data.surfaces;
36460
+ const h = hint.toLowerCase();
36461
+ return data.surfaces.filter((s) => s.id.includes(h) || s.name.toLowerCase().includes(h) || s.fingerprints.some((f) => f.toLowerCase().includes(h)));
36462
+ }
36463
+ function matchSurfaces(techs, headers = {}, body = "") {
36464
+ const haystack = `${techs.join(" ")} ${Object.keys(headers).join(" ")} ${Object.values(headers).join(" ")} ${body}`.toLowerCase();
36465
+ const out = [];
36466
+ for (const s of load2().surfaces) {
36467
+ const hits = s.fingerprints.filter((f) => haystack.includes(f.toLowerCase()));
36468
+ if (hits.length > 0)
36469
+ out.push({ ...s, hits });
36470
+ }
36471
+ return out.sort((a, b) => b.hits.length - a.hits.length);
36472
+ }
36473
+
36447
36474
  // src/live-recon.ts
36448
36475
  import { resolve4 as resolve42 } from "node:dns/promises";
36449
36476
  import { connect } from "node:net";
@@ -36879,6 +36906,7 @@ async function liveRecon(target, includeActive = false) {
36879
36906
  const techs = detectTech(root.headers, root.body);
36880
36907
  const versions = extractVersion(root.headers, root.body);
36881
36908
  const suggestedCves = watchlistCveMatches(techs, root.headers, root.body, versions);
36909
+ const surfaces = matchSurfaces(techs, root.headers, root.body);
36882
36910
  const { links, scripts } = crawlLinks(base, root.body);
36883
36911
  const { robots, sitemap } = await fetchRobotsAndSitemap(base);
36884
36912
  const allUrls = [...links, ...scripts, ...robots, ...sitemap];
@@ -36915,6 +36943,9 @@ async function liveRecon(target, includeActive = false) {
36915
36943
  if (suggestedCves.length > 0) {
36916
36944
  nextSteps.push(`FINGERPRINT MATCH: the stack matches a high-value watchlist product — test these specific CVEs first instead of generic scanning: ${suggestedCves.map((c) => `${c.cve} (${c.name}, EPSS ${((c.epss ?? 0) * 100).toFixed(0)}%)`).join("; ")}.`);
36917
36945
  }
36946
+ if (surfaces.length > 0) {
36947
+ nextSteps.push(`SURFACE-FIRST: detected ${surfaces.length} attack surface(s) — ${surfaces.map((s) => `${s.id} (${s.high_value_tests.length} high-value tests)`).join(", ")}. Map each surface to its tests via surface_map before spraying payloads.`);
36948
+ }
36918
36949
  if (crawl.endpoints.length > 0)
36919
36950
  nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
36920
36951
  if (params.length > 0)
@@ -36942,6 +36973,7 @@ async function liveRecon(target, includeActive = false) {
36942
36973
  tech: { detected: techs, server: fingerprint.server },
36943
36974
  versions,
36944
36975
  suggested_cves: suggestedCves.map((c) => ({ cve: c.cve, name: c.name, product: c.product, epss: c.epss, reports: c.reports })),
36976
+ suggested_surfaces: surfaces.map((s) => ({ id: s.id, name: s.name, high_value_tests: s.high_value_tests, matched_fingerprints: s.hits })),
36945
36977
  crawl,
36946
36978
  params,
36947
36979
  subdomains,
@@ -37018,9 +37050,9 @@ function loadChains() {
37018
37050
  if (_chainsCache)
37019
37051
  return _chainsCache;
37020
37052
  try {
37021
- const dir = join13(fileURLToPath7(new URL(".", import.meta.url)), "..");
37022
- const path = join13(dir, "chains.json");
37023
- const raw = JSON.parse(readFileSync12(path, "utf8"));
37053
+ const dir = join14(fileURLToPath8(new URL(".", import.meta.url)), "..");
37054
+ const path = join14(dir, "chains.json");
37055
+ const raw = JSON.parse(readFileSync13(path, "utf8"));
37024
37056
  _chainsCache = raw.chains ?? [];
37025
37057
  return _chainsCache;
37026
37058
  } catch {
@@ -37801,8 +37833,8 @@ var CHAIN_ID_TO_ESCALATION = {
37801
37833
  };
37802
37834
  function loadChainLinks() {
37803
37835
  try {
37804
- const dir = join13(fileURLToPath7(new URL(".", import.meta.url)), "..");
37805
- const data = JSON.parse(readFileSync12(join13(dir, "intelligence", "chain_links.json"), "utf8"));
37836
+ const dir = join14(fileURLToPath8(new URL(".", import.meta.url)), "..");
37837
+ const data = JSON.parse(readFileSync13(join14(dir, "intelligence", "chain_links.json"), "utf8"));
37806
37838
  return data.links ?? [];
37807
37839
  } catch {
37808
37840
  return [];
@@ -37958,18 +37990,18 @@ function compactState(phase, findings) {
37958
37990
  }
37959
37991
 
37960
37992
  // src/tasks.ts
37961
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync3 } from "node:fs";
37962
- import { join as join14 } from "node:path";
37963
- import { fileURLToPath as fileURLToPath8 } from "node:url";
37993
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync3 } from "node:fs";
37994
+ import { join as join15 } from "node:path";
37995
+ import { fileURLToPath as fileURLToPath9 } from "node:url";
37964
37996
  function tasksDir() {
37965
- return join14(fileURLToPath8(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37997
+ return join15(fileURLToPath9(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37966
37998
  }
37967
37999
  function taskPath(id) {
37968
- return join14(tasksDir(), `${id}.json`);
38000
+ return join15(tasksDir(), `${id}.json`);
37969
38001
  }
37970
- function load2(id) {
38002
+ function load3(id) {
37971
38003
  try {
37972
- return JSON.parse(readFileSync13(taskPath(id), "utf8"));
38004
+ return JSON.parse(readFileSync14(taskPath(id), "utf8"));
37973
38005
  } catch {
37974
38006
  return null;
37975
38007
  }
@@ -37994,7 +38026,7 @@ function taskStart(id, objective, plan) {
37994
38026
  return { ...t, progress: `0/${plan.length}`, next: t.current_step };
37995
38027
  }
37996
38028
  function taskCheckpoint(id, step, status, note, outputHash) {
37997
- const t = load2(id);
38029
+ const t = load3(id);
37998
38030
  if (!t)
37999
38031
  return { ok: false, error: `task '${id}' not found — call task_start first` };
38000
38032
  t.checkpoints.push({ step, status, output_hash: outputHash, note, ts: Date.now() });
@@ -38020,7 +38052,7 @@ function taskCheckpoint(id, step, status, note, outputHash) {
38020
38052
  };
38021
38053
  }
38022
38054
  function taskStatus(id) {
38023
- const t = load2(id);
38055
+ const t = load3(id);
38024
38056
  if (!t)
38025
38057
  return { found: false, error: `task '${id}' not found` };
38026
38058
  const done = t.checkpoints.filter((c) => c.status === "done").length;
@@ -38036,7 +38068,7 @@ function taskStatus(id) {
38036
38068
  };
38037
38069
  }
38038
38070
  function taskResume(id) {
38039
- const t = load2(id);
38071
+ const t = load3(id);
38040
38072
  if (!t)
38041
38073
  return { found: false, error: `task '${id}' not found` };
38042
38074
  const doneSteps = t.checkpoints.filter((c) => c.status === "done").map((c) => c.step);
@@ -38309,16 +38341,16 @@ function scaffoldEngagement(target, scope = [], out_of_scope = []) {
38309
38341
  // src/incremental.ts
38310
38342
  import { execFileSync as execFileSync3 } from "node:child_process";
38311
38343
  import { writeFileSync as writeFileSync4, mkdtempSync, rmSync } from "node:fs";
38312
- import { join as join15 } from "node:path";
38344
+ import { join as join16 } from "node:path";
38313
38345
  import { tmpdir } from "node:os";
38314
38346
 
38315
38347
  // src/eagle2.ts
38316
38348
  var import_php_parser2 = __toESM(require_src(), 1);
38317
- import { readFileSync as readFileSync16 } from "node:fs";
38349
+ import { readFileSync as readFileSync17 } from "node:fs";
38318
38350
 
38319
38351
  // src/dataflow.ts
38320
38352
  init_scanner();
38321
- import { readFileSync as readFileSync14 } from "node:fs";
38353
+ import { readFileSync as readFileSync15 } from "node:fs";
38322
38354
  var SOURCES = {
38323
38355
  $_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
38324
38356
  $_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
@@ -38403,7 +38435,7 @@ function traceDataFlow(path) {
38403
38435
  return result;
38404
38436
  let text;
38405
38437
  try {
38406
- text = readFileSync14(path, "utf8");
38438
+ text = readFileSync15(path, "utf8");
38407
38439
  } catch {
38408
38440
  return result;
38409
38441
  }
@@ -38484,7 +38516,7 @@ init_scanner();
38484
38516
 
38485
38517
  // src/callgraph.ts
38486
38518
  var import_php_parser = __toESM(require_src(), 1);
38487
- import { readFileSync as readFileSync15 } from "node:fs";
38519
+ import { readFileSync as readFileSync16 } from "node:fs";
38488
38520
  var parser = new import_php_parser.Engine({
38489
38521
  parser: { extractDoc: false, php7: true },
38490
38522
  ast: { withPositions: true, withSource: false }
@@ -38545,7 +38577,7 @@ function buildCallGraph(path) {
38545
38577
  const g = { nodes: new Map, edges: [] };
38546
38578
  let code;
38547
38579
  try {
38548
- code = readFileSync15(path, "utf8");
38580
+ code = readFileSync16(path, "utf8");
38549
38581
  } catch {
38550
38582
  return g;
38551
38583
  }
@@ -39207,7 +39239,7 @@ function resolveFuncId(name, summaries) {
39207
39239
  function analyzeDataFlow2(path) {
39208
39240
  let cacheCode = "";
39209
39241
  try {
39210
- cacheCode = readFileSync16(path, "utf8");
39242
+ cacheCode = readFileSync17(path, "utf8");
39211
39243
  } catch {}
39212
39244
  if (cacheCode) {
39213
39245
  const cached = getCache("eagle2", cacheCode);
@@ -39229,7 +39261,7 @@ function analyzeDataFlow2(path) {
39229
39261
  };
39230
39262
  let code;
39231
39263
  try {
39232
- code = cacheCode || readFileSync16(path, "utf8");
39264
+ code = cacheCode || readFileSync17(path, "utf8");
39233
39265
  } catch {
39234
39266
  return result;
39235
39267
  }
@@ -57915,8 +57947,8 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57915
57947
  continue;
57916
57948
  const lang = detectLanguage(file)?.language;
57917
57949
  if (lang === "php") {
57918
- const dir = mkdtempSync(join15(tmpdir(), "blitz-incr-"));
57919
- const tmp = join15(dir, "changed.php");
57950
+ const dir = mkdtempSync(join16(tmpdir(), "blitz-incr-"));
57951
+ const tmp = join16(dir, "changed.php");
57920
57952
  writeFileSync4(tmp, code);
57921
57953
  try {
57922
57954
  const r = analyzeDataFlow2(tmp);
@@ -57937,7 +57969,7 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57937
57969
  // src/taint.ts
57938
57970
  init_scanner();
57939
57971
  var import_php_parser4 = __toESM(require_src(), 1);
57940
- import { readFileSync as readFileSync17 } from "node:fs";
57972
+ import { readFileSync as readFileSync18 } from "node:fs";
57941
57973
  var parser4 = new import_php_parser4.Engine({
57942
57974
  parser: { extractDoc: false, php7: true },
57943
57975
  ast: { withPositions: true, withSource: false }
@@ -58235,7 +58267,7 @@ function analyzeTaint(path) {
58235
58267
  const result = { file: path, findings: [], suppressed: 0 };
58236
58268
  let code;
58237
58269
  try {
58238
- code = readFileSync17(path, "utf8");
58270
+ code = readFileSync18(path, "utf8");
58239
58271
  } catch {
58240
58272
  return result;
58241
58273
  }
@@ -58329,15 +58361,15 @@ function taintTree(root, maxFiles = 2000) {
58329
58361
  }
58330
58362
 
58331
58363
  // src/differential.ts
58332
- import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18 } from "node:fs";
58333
- import { join as join16 } from "node:path";
58364
+ import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync19 } from "node:fs";
58365
+ import { join as join17 } from "node:path";
58334
58366
  import { tmpdir as tmpdir2 } from "node:os";
58335
58367
  import { execFileSync as execFileSync4 } from "node:child_process";
58336
58368
  init_scanner();
58337
58369
  function runEngine(code, language, file) {
58338
58370
  if (language === "php") {
58339
- const dir = mkdtempSync2(join16(tmpdir2(), "blitz-diff-"));
58340
- const tmp = join16(dir, "old.php");
58371
+ const dir = mkdtempSync2(join17(tmpdir2(), "blitz-diff-"));
58372
+ const tmp = join17(dir, "old.php");
58341
58373
  writeFileSync5(tmp, code);
58342
58374
  try {
58343
58375
  const r = analyzeDataFlow2(tmp);
@@ -58491,8 +58523,8 @@ function languageOf(file) {
58491
58523
  return adapter?.language ?? "php";
58492
58524
  }
58493
58525
  function analyzeDifferentialFiles(oldPath, newPath, language) {
58494
- const oldCode = readFileSync18(oldPath, "utf8");
58495
- const newCode = readFileSync18(newPath, "utf8");
58526
+ const oldCode = readFileSync19(oldPath, "utf8");
58527
+ const newCode = readFileSync19(newPath, "utf8");
58496
58528
  const lang = language ?? languageOf(newPath);
58497
58529
  return analyzeDifferential(oldCode, newCode, lang, newPath);
58498
58530
  }
@@ -58619,46 +58651,46 @@ function analyzePatch(oldCode, newCode, language = "php") {
58619
58651
  };
58620
58652
  }
58621
58653
  function analyzePatchFiles(oldPath, newPath, language) {
58622
- const oldCode = readFileSync18(oldPath, "utf8");
58623
- const newCode = readFileSync18(newPath, "utf8");
58654
+ const oldCode = readFileSync19(oldPath, "utf8");
58655
+ const newCode = readFileSync19(newPath, "utf8");
58624
58656
  const lang = language ?? languageOf(newPath);
58625
58657
  return analyzePatch(oldCode, newCode, lang);
58626
58658
  }
58627
58659
 
58628
58660
  // src/frameworks.ts
58629
- import { readFileSync as readFileSync19, existsSync as existsSync9 } from "node:fs";
58630
- import { join as join17 } from "node:path";
58631
- import { fileURLToPath as fileURLToPath9 } from "node:url";
58632
- var ROOT5 = join17(fileURLToPath9(new URL(".", import.meta.url)), "..");
58633
- var INTEL2 = join17(ROOT5, "intelligence");
58661
+ import { readFileSync as readFileSync20, existsSync as existsSync9 } from "node:fs";
58662
+ import { join as join18 } from "node:path";
58663
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
58664
+ var ROOT6 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
58665
+ var INTEL2 = join18(ROOT6, "intelligence");
58634
58666
  var cache = null;
58635
- function load3() {
58667
+ function load4() {
58636
58668
  if (cache)
58637
58669
  return cache;
58638
- const p = join17(INTEL2, "frameworks.json");
58670
+ const p = join18(INTEL2, "frameworks.json");
58639
58671
  if (!existsSync9(p))
58640
58672
  return null;
58641
58673
  try {
58642
- cache = JSON.parse(readFileSync19(p, "utf8"));
58674
+ cache = JSON.parse(readFileSync20(p, "utf8"));
58643
58675
  return cache;
58644
58676
  } catch {
58645
58677
  return null;
58646
58678
  }
58647
58679
  }
58648
58680
  function listFrameworks2() {
58649
- const data = load3();
58681
+ const data = load4();
58650
58682
  if (!data)
58651
58683
  return [];
58652
58684
  return data.frameworks.map((f) => ({ id: f.id, name: f.name, language: f.language }));
58653
58685
  }
58654
58686
  function frameworkProfile(id) {
58655
- const data = load3();
58687
+ const data = load4();
58656
58688
  if (!data)
58657
58689
  return null;
58658
58690
  return data.frameworks.find((f) => f.id === id) ?? null;
58659
58691
  }
58660
58692
  function detectFramework(code, language) {
58661
- const data = load3();
58693
+ const data = load4();
58662
58694
  const hay = code.toLowerCase();
58663
58695
  const candidates = [];
58664
58696
  if (data) {
@@ -59151,7 +59183,7 @@ function nextSteps(ctx) {
59151
59183
  init_complex_bugs();
59152
59184
  import { execFileSync as execFileSync5 } from "node:child_process";
59153
59185
  import { existsSync as existsSync11 } from "node:fs";
59154
- import { join as join18 } from "node:path";
59186
+ import { join as join19 } from "node:path";
59155
59187
  function parseDeletions(output) {
59156
59188
  const out = [];
59157
59189
  let commit = "";
@@ -59180,7 +59212,7 @@ function blobBeforeDeletion(root, commit, path) {
59180
59212
  function scanGitHistory(root, opts) {
59181
59213
  const limit = opts?.limit ?? 30;
59182
59214
  const findings = [];
59183
- if (!existsSync11(join18(root, ".git"))) {
59215
+ if (!existsSync11(join19(root, ".git"))) {
59184
59216
  return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
59185
59217
  }
59186
59218
  if (opts?.secretPattern) {
@@ -59327,13 +59359,13 @@ async function validateLeakedKey(input) {
59327
59359
  }
59328
59360
 
59329
59361
  // src/hackbot-arena.ts
59330
- import { readFileSync as readFileSync20 } from "node:fs";
59331
- import { join as join19, dirname as dirname3 } from "node:path";
59332
- import { fileURLToPath as fileURLToPath10 } from "node:url";
59333
- var __dirname3 = dirname3(fileURLToPath10(import.meta.url));
59334
- function load4() {
59362
+ import { readFileSync as readFileSync21 } from "node:fs";
59363
+ import { join as join20, dirname as dirname3 } from "node:path";
59364
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
59365
+ var __dirname3 = dirname3(fileURLToPath11(import.meta.url));
59366
+ function load5() {
59335
59367
  try {
59336
- return JSON.parse(readFileSync20(join19(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
59368
+ return JSON.parse(readFileSync21(join20(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
59337
59369
  } catch {
59338
59370
  return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
59339
59371
  }
@@ -59370,10 +59402,10 @@ function mapLab(lab) {
59370
59402
  }
59371
59403
  function findByLab(labId) {
59372
59404
  const id = labId.toLowerCase().replace(/[^a-z0-9]/g, "");
59373
- return load4().labs.find((l) => l.id.toLowerCase() === id || l.id.toLowerCase() === `labs${id}`) ?? null;
59405
+ return load5().labs.find((l) => l.id.toLowerCase() === id || l.id.toLowerCase() === `labs${id}`) ?? null;
59374
59406
  }
59375
59407
  function hackbotArenaList() {
59376
- const doc = load4();
59408
+ const doc = load5();
59377
59409
  return {
59378
59410
  arena: "Hackbot-Arena (NusaSec)",
59379
59411
  total: doc.total,
@@ -59394,18 +59426,18 @@ function hackbotArenaBrief(labId) {
59394
59426
  target: `http://localhost:${lab.port}`,
59395
59427
  vuln_class: lab.vuln_class,
59396
59428
  stack: lab.stack,
59397
- flag_format: load4().flag_format,
59429
+ flag_format: load5().flag_format,
59398
59430
  attack_plan: {
59399
59431
  vectors: plan.vectors,
59400
59432
  detectors: plan.detectors,
59401
59433
  chains: plan.chains,
59402
59434
  tools: plan.tools
59403
59435
  },
59404
- hint: `Start with live_recon(target) to fingerprint, then drive the ${plan.chains.join(" + ") || "detected"} chain. Verify every step live (strike_verify / browser_validate) and recover the flag. Flag format: ${load4().flag_format}.`
59436
+ hint: `Start with live_recon(target) to fingerprint, then drive the ${plan.chains.join(" + ") || "detected"} chain. Verify every step live (strike_verify / browser_validate) and recover the flag. Flag format: ${load5().flag_format}.`
59405
59437
  };
59406
59438
  }
59407
59439
  function hackbotArenaCoverage() {
59408
- const doc = load4();
59440
+ const doc = load5();
59409
59441
  const rows = doc.labs.map((l) => {
59410
59442
  const plan = mapLab(l);
59411
59443
  return { id: l.id, name: l.name, vuln_class: l.vuln_class, vectors: plan.vectors, detectors: plan.detectors, chains: plan.chains };
@@ -59497,13 +59529,13 @@ function listSeverityMap() {
59497
59529
  }
59498
59530
 
59499
59531
  // src/datarefresh.ts
59500
- import { readFileSync as readFileSync21 } from "node:fs";
59501
- import { join as join20, dirname as dirname4 } from "node:path";
59502
- import { fileURLToPath as fileURLToPath11 } from "node:url";
59503
- var __dirname4 = dirname4(fileURLToPath11(import.meta.url));
59532
+ import { readFileSync as readFileSync22 } from "node:fs";
59533
+ import { join as join21, dirname as dirname4 } from "node:path";
59534
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
59535
+ var __dirname4 = dirname4(fileURLToPath12(import.meta.url));
59504
59536
  function loadJson2(rel) {
59505
59537
  try {
59506
- return JSON.parse(readFileSync21(join20(__dirname4, "..", rel), "utf8"));
59538
+ return JSON.parse(readFileSync22(join21(__dirname4, "..", rel), "utf8"));
59507
59539
  } catch {
59508
59540
  return null;
59509
59541
  }
@@ -59695,18 +59727,18 @@ async function brokenLinkScan(url, maxHosts = 30) {
59695
59727
  }
59696
59728
 
59697
59729
  // src/benchmark.ts
59698
- import { readFileSync as readFileSync23, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59699
- import { join as join22 } from "node:path";
59700
- import { fileURLToPath as fileURLToPath13 } from "node:url";
59730
+ import { readFileSync as readFileSync24, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59731
+ import { join as join23 } from "node:path";
59732
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
59701
59733
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
59702
59734
  init_complex_bugs();
59703
- var ROOT6 = join22(fileURLToPath13(new URL(".", import.meta.url)), "..");
59704
- var CORPUS = join22(ROOT6, "bench", "corpus");
59705
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join22(homedir8(), ".blitzstrike", "corpus");
59735
+ var ROOT7 = join23(fileURLToPath14(new URL(".", import.meta.url)), "..");
59736
+ var CORPUS = join23(ROOT7, "bench", "corpus");
59737
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join23(homedir8(), ".blitzstrike", "corpus");
59706
59738
  function parseCorpusFile(path) {
59707
59739
  let text;
59708
59740
  try {
59709
- text = readFileSync23(path, "utf8");
59741
+ text = readFileSync24(path, "utf8");
59710
59742
  } catch {
59711
59743
  return null;
59712
59744
  }
@@ -59762,7 +59794,7 @@ function loadDir(dir) {
59762
59794
  for (const f of readdirSync5(dir)) {
59763
59795
  if (!f.endsWith(".md"))
59764
59796
  continue;
59765
- const e = parseCorpusFile(join22(dir, f));
59797
+ const e = parseCorpusFile(join23(dir, f));
59766
59798
  if (e)
59767
59799
  entries.push(e);
59768
59800
  }
@@ -59789,7 +59821,7 @@ function captureFalsePositive(input) {
59789
59821
  input.code,
59790
59822
  "```"
59791
59823
  ].filter((l) => l !== null);
59792
- const path = join22(CAPTURED_CORPUS_DIR, `${id}.md`);
59824
+ const path = join23(CAPTURED_CORPUS_DIR, `${id}.md`);
59793
59825
  writeFileSync6(path, lines.join(`
59794
59826
  `) + `
59795
59827
  `, "utf8");
@@ -59811,7 +59843,7 @@ function detect(entry) {
59811
59843
  }
59812
59844
  const lang = entry.language;
59813
59845
  if (lang === "php") {
59814
- const tmp = join22(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59846
+ const tmp = join23(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59815
59847
  writeFileSync6(tmp, entry.code);
59816
59848
  const r = analyzeDataFlow2(tmp);
59817
59849
  rmSync3(tmp, { force: true });
@@ -59880,12 +59912,12 @@ function runBenchmark() {
59880
59912
  }
59881
59913
 
59882
59914
  // src/obligations.ts
59883
- import { readFileSync as readFileSync24, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
59884
- import { join as join23 } from "node:path";
59915
+ import { readFileSync as readFileSync25, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
59916
+ import { join as join24 } from "node:path";
59885
59917
  import { createHash as createHash6 } from "node:crypto";
59886
59918
  import { homedir as homedir9 } from "node:os";
59887
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join23(homedir9(), ".blitzstrike");
59888
- var OBLIGATION_PATH = join23(HOME_DIR4, "obligations.jsonl");
59919
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join24(homedir9(), ".blitzstrike");
59920
+ var OBLIGATION_PATH = join24(HOME_DIR4, "obligations.jsonl");
59889
59921
  function ensureDir3() {
59890
59922
  if (!existsSync12(HOME_DIR4))
59891
59923
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -59898,7 +59930,7 @@ function loadObligations() {
59898
59930
  return [];
59899
59931
  try {
59900
59932
  const out = [];
59901
- for (const line of readFileSync24(OBLIGATION_PATH, "utf8").split(`
59933
+ for (const line of readFileSync25(OBLIGATION_PATH, "utf8").split(`
59902
59934
  `)) {
59903
59935
  const t = line.trim();
59904
59936
  if (!t)
@@ -60002,11 +60034,11 @@ init_framework_security();
60002
60034
  // src/variant-scan.ts
60003
60035
  init_scanner();
60004
60036
  init_complex_bugs();
60005
- import { readFileSync as readFileSync26 } from "node:fs";
60037
+ import { readFileSync as readFileSync27 } from "node:fs";
60006
60038
 
60007
60039
  // src/cross-file.ts
60008
60040
  init_scanner();
60009
- import { readFileSync as readFileSync25 } from "node:fs";
60041
+ import { readFileSync as readFileSync26 } from "node:fs";
60010
60042
  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)/;
60011
60043
  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*\(/;
60012
60044
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -60058,7 +60090,7 @@ function detectCrossFilePrivesc(root) {
60058
60090
  for (const file of files) {
60059
60091
  let code;
60060
60092
  try {
60061
- code = stripComments(readFileSync25(file, "utf8"));
60093
+ code = stripComments(readFileSync26(file, "utf8"));
60062
60094
  } catch {
60063
60095
  continue;
60064
60096
  }
@@ -60099,7 +60131,7 @@ function detectCrossFilePrivesc(root) {
60099
60131
  for (const file of files) {
60100
60132
  let code;
60101
60133
  try {
60102
- code = stripComments(readFileSync25(file, "utf8"));
60134
+ code = stripComments(readFileSync26(file, "utf8"));
60103
60135
  } catch {
60104
60136
  continue;
60105
60137
  }
@@ -60122,7 +60154,7 @@ function detectCrossFilePrivesc(root) {
60122
60154
  for (const file of files) {
60123
60155
  let code;
60124
60156
  try {
60125
- code = stripComments(readFileSync25(file, "utf8"));
60157
+ code = stripComments(readFileSync26(file, "utf8"));
60126
60158
  } catch {
60127
60159
  continue;
60128
60160
  }
@@ -60237,7 +60269,7 @@ function variantScan(root, opts = {}) {
60237
60269
  for (const file of files) {
60238
60270
  let code;
60239
60271
  try {
60240
- code = readFileSync26(file, "utf8");
60272
+ code = readFileSync27(file, "utf8");
60241
60273
  } catch {
60242
60274
  continue;
60243
60275
  }
@@ -61489,6 +61521,11 @@ var HUNTING_DOCTRINE = [
61489
61521
  id: "dedup_before_report",
61490
61522
  title: "Search for Duplicates Before Reporting",
61491
61523
  rule: "The #1 cause of 'Duplicate'/'N/A' rejections is not searching first. Before writing the report, search Hacktivity (and the program's prior public reports) for the same endpoint/tech/vuln class; an already-reported open S3 bucket, CORS misconfig, or XSS param is not worth a second report. Only submit what is genuinely new."
61524
+ },
61525
+ {
61526
+ id: "surface_first",
61527
+ title: "Map Attack Surfaces Before Spraying Payloads",
61528
+ rule: "A large target is won by mapping its attack SURFACES (image proxy → SSRF/URL-bypass; redirect proxy → open-redirect/SSRF; edge cache → IDOR/cache-poisoning; payment portal → price-manipulation/IDOR; AI agent → prompt-injection→SSRF-RCE; file service → XSS-via-content/SSRF/upload-RCE; Electron app → HTML-injection→BrowserWindow-RCE; SSO → OAuth-redirect/JWT). Use surface_map(surface) to get each surface's high-value tests, then test those FIRST — not a payload spray across every endpoint."
61492
61529
  }
61493
61530
  ];
61494
61531
  function huntingDoctrine() {
@@ -61917,7 +61954,7 @@ function listPocTypes() {
61917
61954
  import { execSync as execSync3 } from "node:child_process";
61918
61955
  import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, rmSync as rmSync4, existsSync as existsSync14 } from "node:fs";
61919
61956
  import { tmpdir as tmpdir4 } from "node:os";
61920
- import { join as join24 } from "node:path";
61957
+ import { join as join25 } from "node:path";
61921
61958
  function findForge() {
61922
61959
  try {
61923
61960
  const p = execSync3("which forge 2>/dev/null", { encoding: "utf8", timeout: 3000 }).trim();
@@ -61966,14 +62003,14 @@ async function foundryRun(input) {
61966
62003
  };
61967
62004
  }
61968
62005
  const poc = input.poc_code ?? foundryPoc(input.class_ ?? "missing_access_control", name);
61969
- const dir = mkdtempSync3(join24(tmpdir4(), "blitzstrike-foundry-"));
62006
+ const dir = mkdtempSync3(join25(tmpdir4(), "blitzstrike-foundry-"));
61970
62007
  try {
61971
62008
  try {
61972
62009
  execSync3(`${forge} init --force --no-git ${dir}`, { stdio: "ignore", timeout: 60000 });
61973
62010
  } catch {
61974
- mkdirSync9(join24(dir, "src"), { recursive: true });
61975
- mkdirSync9(join24(dir, "test"), { recursive: true });
61976
- writeFileSync8(join24(dir, "foundry.toml"), `[profile.default]
62011
+ mkdirSync9(join25(dir, "src"), { recursive: true });
62012
+ mkdirSync9(join25(dir, "test"), { recursive: true });
62013
+ writeFileSync8(join25(dir, "foundry.toml"), `[profile.default]
61977
62014
  src = "src"
61978
62015
  out = "out"
61979
62016
  libs = ["lib"]
@@ -61983,12 +62020,12 @@ solc = "0.8.20"
61983
62020
  for (const f of ["Counter.sol", "Counter.t.sol", "Counter.s.sol"]) {
61984
62021
  for (const sub of ["src", "test", "script"]) {
61985
62022
  try {
61986
- rmSync4(join24(dir, sub, f), { force: true });
62023
+ rmSync4(join25(dir, sub, f), { force: true });
61987
62024
  } catch {}
61988
62025
  }
61989
62026
  }
61990
- writeFileSync8(join24(dir, "src", `${name}.sol`), input.contract_code);
61991
- writeFileSync8(join24(dir, "test", `${name}.t.sol`), poc);
62027
+ writeFileSync8(join25(dir, "src", `${name}.sol`), input.contract_code);
62028
+ writeFileSync8(join25(dir, "test", `${name}.t.sol`), poc);
61992
62029
  const { out } = runForge(forge, dir, input.timeout_ms ?? 60000);
61993
62030
  const tests = parseTestResults(out);
61994
62031
  const anyFail = tests.some((t) => t.status === "fail");
@@ -62006,18 +62043,18 @@ solc = "0.8.20"
62006
62043
  }
62007
62044
 
62008
62045
  // src/enterprise-benchmark.ts
62009
- import { existsSync as existsSync15, readFileSync as readFileSync27, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
62010
- import { join as join25, resolve } from "node:path";
62011
- import { fileURLToPath as fileURLToPath14 } from "node:url";
62046
+ import { existsSync as existsSync15, readFileSync as readFileSync28, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
62047
+ import { join as join26, resolve } from "node:path";
62048
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
62012
62049
  init_complex_bugs();
62013
- var ROOT7 = resolve(fileURLToPath14(new URL(".", import.meta.url)), "..", "bench", "enterprise");
62050
+ var ROOT8 = resolve(fileURLToPath15(new URL(".", import.meta.url)), "..", "bench", "enterprise");
62014
62051
  var ENTERPRISE_FIXTURES = [
62015
- { id: "php-laravel", dir: join25(ROOT7, "php-laravel"), expected_vulns: 4 },
62016
- { id: "js-express", dir: join25(ROOT7, "js-express"), expected_vulns: 2 },
62017
- { id: "py-django", dir: join25(ROOT7, "py-django"), expected_vulns: 2 },
62018
- { id: "java-spring", dir: join25(ROOT7, "java-spring"), expected_vulns: 2 },
62019
- { id: "wp-batch-route", dir: join25(ROOT7, "wp-batch-route"), expected_vulns: 3 },
62020
- { id: "php-complex", dir: join25(ROOT7, "php-complex"), expected_vulns: 4 }
62052
+ { id: "php-laravel", dir: join26(ROOT8, "php-laravel"), expected_vulns: 4 },
62053
+ { id: "js-express", dir: join26(ROOT8, "js-express"), expected_vulns: 2 },
62054
+ { id: "py-django", dir: join26(ROOT8, "py-django"), expected_vulns: 2 },
62055
+ { id: "java-spring", dir: join26(ROOT8, "java-spring"), expected_vulns: 2 },
62056
+ { id: "wp-batch-route", dir: join26(ROOT8, "wp-batch-route"), expected_vulns: 3 },
62057
+ { id: "php-complex", dir: join26(ROOT8, "php-complex"), expected_vulns: 4 }
62021
62058
  ];
62022
62059
  function sourceFiles(root) {
62023
62060
  const out = [];
@@ -62029,7 +62066,7 @@ function sourceFiles(root) {
62029
62066
  return;
62030
62067
  }
62031
62068
  for (const e of entries) {
62032
- const p = join25(dir, e);
62069
+ const p = join26(dir, e);
62033
62070
  let st;
62034
62071
  try {
62035
62072
  st = statSync3(p);
@@ -62050,7 +62087,7 @@ function detectFixture(dir) {
62050
62087
  for (const f of sourceFiles(dir)) {
62051
62088
  let code;
62052
62089
  try {
62053
- code = readFileSync27(f, "utf8");
62090
+ code = readFileSync28(f, "utf8");
62054
62091
  } catch {
62055
62092
  continue;
62056
62093
  }
@@ -63154,7 +63191,7 @@ ${statusLine}` : statusLine },
63154
63191
  let src = code;
63155
63192
  if (!src && path) {
63156
63193
  try {
63157
- src = readFileSync29(path, "utf8");
63194
+ src = readFileSync30(path, "utf8");
63158
63195
  } catch {
63159
63196
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
63160
63197
  }
@@ -63235,7 +63272,7 @@ ${statusLine}` : statusLine },
63235
63272
  let src = code;
63236
63273
  if (!src && path) {
63237
63274
  try {
63238
- src = readFileSync29(path, "utf8");
63275
+ src = readFileSync30(path, "utf8");
63239
63276
  } catch {
63240
63277
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
63241
63278
  }
@@ -63308,6 +63345,15 @@ ${statusLine}` : statusLine },
63308
63345
  }, async ({ url }) => {
63309
63346
  return { content: [{ type: "text", text: JSON.stringify(await brokenLinkScan(url)) }] };
63310
63347
  });
63348
+ server.registerTool("surface_map", {
63349
+ title: "Attack-surface → high-value-test map (surface-first targeting)",
63350
+ description: "Map an attack surface to its highest-value test cases (derived from public target-recon dossiers). Surfaces: image_proxy (SSRF/URL-bypass/XSS), redirect_proxy (open-redirect/SSRF/bypass), edge_cache (IDOR/cache-poisoning), payment_portal (price-manipulation/IDOR/subscription), ai_agent (prompt-injection→SSRF-RCE/deser/stored-XSS), file_service (XSS-via-content/SSRF/upload-RCE), electron_desktop (HTML-injection→BrowserWindow-RCE/IPC), auth_sso (OAuth-redirect/JWT/2FA). Pass a hint to filter, or no arg to list all. SURFACE-FIRST: a large target is won by mapping surfaces to tests, not by spraying payloads.",
63351
+ inputSchema: {
63352
+ hint: string2().optional().describe("Surface id/keyword to filter (e.g. 'image_proxy', 'payment', 'ai')")
63353
+ }
63354
+ }, async ({ hint }) => {
63355
+ return { content: [{ type: "text", text: JSON.stringify(surfaceMap(hint ?? "")) }] };
63356
+ });
63311
63357
  server.registerTool("auto_poc", {
63312
63358
  title: "Auto-PoC — generate a copy-paste-ready reproduction recipe",
63313
63359
  description: "Turn a finding into a concrete reproduction recipe (the 'reproducible' gate question made executable). Given a vuln class + target + endpoint + param, emit the exact request (with a MARKER), a NEGATIVE CONTROL, and the expected differential that PROVES the bug — plus a Patchstack-style title, derived severity, and the fix. Covers sql_injection, missing_authz/idor, ssrf, xss, ssti, command_injection, file_upload, mass_assignment, jwt_alg_confusion, crlf, open_redirect, xxe, prototype_pollution, deserialization, hardcoded_secret, cache_deception, cors_misconfiguration, subdomain_takeover. Pass type='list' to enumerate supported classes.",
@@ -63927,7 +63973,7 @@ ${statusLine}` : statusLine },
63927
63973
  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.",
63928
63974
  inputSchema: { path: string2().describe("Source file path (PHP)") }
63929
63975
  }, async ({ path }) => {
63930
- const code = readFileSync29(path, "utf8");
63976
+ const code = readFileSync30(path, "utf8");
63931
63977
  const findings = detectRouteConfusion(code, path);
63932
63978
  return {
63933
63979
  content: [{
@@ -63941,7 +63987,7 @@ ${statusLine}` : statusLine },
63941
63987
  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), missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback), CORS misconfiguration (reflected/wildcard origin + credentials), XSS (user input into a DOM sink: innerHTML / dangerouslySetInnerHTML / document.write / .html / eval), and subdomain takeover (CNAME/AliasTarget to a claimable service).",
63942
63988
  inputSchema: { path: string2().describe("Source file path") }
63943
63989
  }, async ({ path }) => {
63944
- const code = readFileSync29(path, "utf8");
63990
+ const code = readFileSync30(path, "utf8");
63945
63991
  const findings = detectComplexBugs(code, path);
63946
63992
  return {
63947
63993
  content: [{
@@ -64027,7 +64073,7 @@ ${statusLine}` : statusLine },
64027
64073
  let lang = language;
64028
64074
  if (path && !code) {
64029
64075
  try {
64030
- src = readFileSync29(path, "utf8");
64076
+ src = readFileSync30(path, "utf8");
64031
64077
  } catch {
64032
64078
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
64033
64079
  }
@@ -64276,15 +64322,15 @@ async function serve() {
64276
64322
 
64277
64323
  // src/index.ts
64278
64324
  init_cli();
64279
- import { readFileSync as readFileSync33, existsSync as existsSync18 } from "node:fs";
64280
- import { join as join30 } from "node:path";
64281
- import { fileURLToPath as fileURLToPath19 } from "node:url";
64282
- var ROOT12 = join30(fileURLToPath19(new URL(".", import.meta.url)), "..");
64325
+ import { readFileSync as readFileSync34, existsSync as existsSync18 } from "node:fs";
64326
+ import { join as join31 } from "node:path";
64327
+ import { fileURLToPath as fileURLToPath20 } from "node:url";
64328
+ var ROOT13 = join31(fileURLToPath20(new URL(".", import.meta.url)), "..");
64283
64329
  function readVersion() {
64284
64330
  try {
64285
- const p = join30(ROOT12, "package.json");
64331
+ const p = join31(ROOT13, "package.json");
64286
64332
  if (existsSync18(p))
64287
- return JSON.parse(readFileSync33(p, "utf8")).version ?? "1.0.0";
64333
+ return JSON.parse(readFileSync34(p, "utf8")).version ?? "1.0.0";
64288
64334
  } catch {}
64289
64335
  return "1.0.0";
64290
64336
  }
@@ -0,0 +1,159 @@
1
+ {
2
+ "surfaces": [
3
+ {
4
+ "id": "image_proxy",
5
+ "name": "Image proxy / URL-rewrite service",
6
+ "fingerprints": [
7
+ "imgproxy",
8
+ "image proxy",
9
+ "url unfurling",
10
+ "image resize",
11
+ "?url=",
12
+ "slack-imgs",
13
+ "image-resize",
14
+ "proxy.php?url"
15
+ ],
16
+ "high_value_tests": [
17
+ "SSRF \u2014 the url= param reaches the proxy; point it at 169.254.169.254 or an internal range",
18
+ "URL validation bypass \u2014 encoded/internal-scheme (file://, gopher://, 127.0.0.1) variants",
19
+ "XSS via image content \u2014 SVG/HTML payloads rendered inline"
20
+ ]
21
+ },
22
+ {
23
+ "id": "redirect_proxy",
24
+ "name": "URL redirect / safety proxy",
25
+ "fingerprints": [
26
+ "redirect",
27
+ "safety proxy",
28
+ "redir",
29
+ "url shortener",
30
+ "outbound link",
31
+ "?to=",
32
+ "?redirect=",
33
+ "?url="
34
+ ],
35
+ "high_value_tests": [
36
+ "Open redirect \u2014 bypass the safety check to cloak phishing links",
37
+ "SSRF \u2014 the proxy fetches the target URL server-side",
38
+ "URL validation bypass \u2014 scheme/domain confusion (//, @, encoded chars)"
39
+ ]
40
+ },
41
+ {
42
+ "id": "edge_cache",
43
+ "name": "Edge cache / entity data service",
44
+ "fingerprints": [
45
+ "edge",
46
+ "cache",
47
+ "conditional fetch",
48
+ "timestamp",
49
+ "entity",
50
+ "flannel",
51
+ "graphql"
52
+ ],
53
+ "high_value_tests": [
54
+ "IDOR \u2014 read other entities (user/channel/tenant) by id without auth",
55
+ "Cache poisoning \u2014 inject a key that serves attacker content to other users",
56
+ "Unauthorized entity enumeration \u2014 bulk read via timestamp/resource pattern"
57
+ ]
58
+ },
59
+ {
60
+ "id": "payment_portal",
61
+ "name": "Payment / billing portal",
62
+ "fingerprints": [
63
+ "payment",
64
+ "billing",
65
+ "checkout",
66
+ "invoice",
67
+ "subscription",
68
+ "stripe",
69
+ "paypal",
70
+ "braintree",
71
+ "plan",
72
+ "pricing"
73
+ ],
74
+ "high_value_tests": [
75
+ "Price/amount manipulation \u2014 alter the client-side total before checkout",
76
+ "IDOR on invoices/payment records \u2014 read or modify other users' billing",
77
+ "Subscription tampering \u2014 upgrade/entitlement without payment",
78
+ "Auth bypass on checkout/order endpoints"
79
+ ]
80
+ },
81
+ {
82
+ "id": "ai_agent",
83
+ "name": "AI agent / no-code app builder",
84
+ "fingerprints": [
85
+ "agent",
86
+ "ai",
87
+ "llm",
88
+ "no-code",
89
+ "builder",
90
+ "prompt",
91
+ "gpt",
92
+ "assistant",
93
+ "horizons"
94
+ ],
95
+ "high_value_tests": [
96
+ "Prompt injection \u2192 SSRF / RCE / data exfiltration via tool-call abuse",
97
+ "Insecure deserialization of tool-call arguments",
98
+ "Stored XSS in agent-generated output rendered in the dashboard"
99
+ ]
100
+ },
101
+ {
102
+ "id": "file_service",
103
+ "name": "File upload / download service",
104
+ "fingerprints": [
105
+ "file",
106
+ "upload",
107
+ "download",
108
+ "supra",
109
+ "miata",
110
+ "attachment",
111
+ "?file=",
112
+ "?download="
113
+ ],
114
+ "high_value_tests": [
115
+ "XSS via file content \u2014 SVG/HTML served inline",
116
+ "SSRF via file URL / remote file fetch",
117
+ "Upload \u2192 RCE \u2014 executable file landing web-reachable"
118
+ ]
119
+ },
120
+ {
121
+ "id": "electron_desktop",
122
+ "name": "Electron / desktop app",
123
+ "fingerprints": [
124
+ "electron",
125
+ "browserwindow",
126
+ "desktop",
127
+ "chromium",
128
+ "node.js desktop",
129
+ "app.asar"
130
+ ],
131
+ "high_value_tests": [
132
+ "HTML injection \u2192 BrowserWindow RCE (nodeIntegration + remote content)",
133
+ "Insecure IPC / preload bridge \u2014 expose privileged APIs to renderer",
134
+ "Remote content loading \u2014 hot-update/CDN resources replaced"
135
+ ]
136
+ },
137
+ {
138
+ "id": "auth_sso",
139
+ "name": "Authentication / SSO portal",
140
+ "fingerprints": [
141
+ "auth",
142
+ "sso",
143
+ "oauth",
144
+ "saml",
145
+ "login",
146
+ "passport",
147
+ "jwt",
148
+ "token",
149
+ "signin"
150
+ ],
151
+ "high_value_tests": [
152
+ "OAuth open redirect via redirect_uri manipulation",
153
+ "JWT alg:none / weak secret / missing expiry",
154
+ "Token leakage / account enumeration / 2FA bypass"
155
+ ]
156
+ }
157
+ ],
158
+ "note": "Generalized attack-surface \u2192 high-value-test map, derived from public target-recon dossiers. Each surface is matched by fingerprint keywords against the detected tech stack/headers/body; when a surface is present, test its high-value cases FIRST (surface-first, not payload-spraying)."
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.107",
3
+ "version": "2.4.108",
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": {