blitzstrike 2.4.93 → 2.4.94

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,23 @@ 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.94] — 2026-09-17
9
+
10
+ ### Added — Hunting intel (DNS sinkhole detection + CWE/CVE watchlist)
11
+
12
+ - `resolve_check(host)` — detects when an ISP/country redirects a domain to a
13
+ blocking/sinkhole page (gambling/adult/streaming niches). Compares the local
14
+ resolver against public DNS (8.8.8.8/1.1.1.1) and probes the local IP for
15
+ blocking-page content — the CONTENT is the signal (a differing IP set alone is
16
+ normal for CDN/anycast). A sinkhole verdict tells the agent to audit the PUBLIC
17
+ IP, never the sinkhole.
18
+ - `watchlist()` — the ranked bug-bounty CWE frequency (17 classes) + high-value
19
+ CVE watchlist (9 CVEs), each with payout range + a saturated flag, joined to a
20
+ Blitz Strike detector pattern for grounding. Prioritizes high-frequency /
21
+ high-payout / not-yet-saturated classes (missing-authz, SQLi, SSRF,
22
+ deserialization) and flags saturated low-value ones (XSS/CSRF/open-redirect).
23
+ Data: intelligence/watchlist.json.
24
+
8
25
  ## [2.4.93] — 2026-09-17
9
26
 
10
27
  ### Fixed — reproducible verify harness (flaky wordlist test)
package/dist/index.js CHANGED
@@ -17349,8 +17349,8 @@ var init_variants = __esm(() => {
17349
17349
  });
17350
17350
 
17351
17351
  // src/sync.ts
17352
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync26, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync5 } from "node:fs";
17353
- import { join as join25 } from "node:path";
17352
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync27, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync5 } from "node:fs";
17353
+ import { join as join26 } from "node:path";
17354
17354
  import { homedir as homedir10 } from "node:os";
17355
17355
  import { fileURLToPath as fileURLToPath13 } from "node:url";
17356
17356
  import { spawnSync as spawnSync4 } from "node:child_process";
@@ -17367,7 +17367,7 @@ function countFiles(dir) {
17367
17367
  const walk = (d) => {
17368
17368
  for (const e of readdirSync7(d, { withFileTypes: true })) {
17369
17369
  if (e.isDirectory())
17370
- walk(join25(d, e.name));
17370
+ walk(join26(d, e.name));
17371
17371
  else
17372
17372
  n++;
17373
17373
  }
@@ -17381,8 +17381,8 @@ function countFiles(dir) {
17381
17381
  function syncData() {
17382
17382
  if (!dirExists(DATA_ROOT2))
17383
17383
  mkdirSync10(DATA_ROOT2, { recursive: true });
17384
- const payloadsPath = join25(DATA_ROOT2, "payloads");
17385
- const templatesPath = join25(DATA_ROOT2, "templates");
17384
+ const payloadsPath = join26(DATA_ROOT2, "payloads");
17385
+ const templatesPath = join26(DATA_ROOT2, "templates");
17386
17386
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
17387
17387
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
17388
17388
  if (hasPayloads && hasTemplates) {
@@ -17392,8 +17392,8 @@ function syncData() {
17392
17392
  data_root: DATA_ROOT2
17393
17393
  };
17394
17394
  }
17395
- const localPayloads = join25(ROOT7, "payloads");
17396
- const localTemplates = join25(ROOT7, "templates");
17395
+ const localPayloads = join26(ROOT7, "payloads");
17396
+ const localTemplates = join26(ROOT7, "templates");
17397
17397
  let payloadsSynced = false;
17398
17398
  let templatesSynced = false;
17399
17399
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -17406,7 +17406,7 @@ function syncData() {
17406
17406
  }
17407
17407
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
17408
17408
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
17409
- const tmp = join25(DATA_ROOT2, ".tmp-clone");
17409
+ const tmp = join26(DATA_ROOT2, ".tmp-clone");
17410
17410
  if (dirExists(tmp))
17411
17411
  rmSync5(tmp, { recursive: true, force: true });
17412
17412
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -17414,11 +17414,11 @@ function syncData() {
17414
17414
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
17415
17415
  if (sparse.status === 0) {
17416
17416
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
17417
- copyDir(join25(tmp, "payloads"), payloadsPath);
17417
+ copyDir(join26(tmp, "payloads"), payloadsPath);
17418
17418
  payloadsSynced = true;
17419
17419
  }
17420
17420
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
17421
- copyDir(join25(tmp, "templates"), templatesPath);
17421
+ copyDir(join26(tmp, "templates"), templatesPath);
17422
17422
  templatesSynced = true;
17423
17423
  }
17424
17424
  }
@@ -17440,18 +17440,18 @@ function copyDir(src, dst) {
17440
17440
  function manualCopy(src, dst) {
17441
17441
  mkdirSync10(dst, { recursive: true });
17442
17442
  for (const e of readdirSync7(src, { withFileTypes: true })) {
17443
- const s = join25(src, e.name);
17444
- const d = join25(dst, e.name);
17443
+ const s = join26(src, e.name);
17444
+ const d = join26(dst, e.name);
17445
17445
  if (e.isDirectory())
17446
17446
  manualCopy(s, d);
17447
17447
  else
17448
- writeFileSync9(d, readFileSync26(s));
17448
+ writeFileSync9(d, readFileSync27(s));
17449
17449
  }
17450
17450
  }
17451
17451
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
17452
17452
  var init_sync = __esm(() => {
17453
- ROOT7 = join25(fileURLToPath13(new URL(".", import.meta.url)), "..");
17454
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join25(homedir10(), ".blitzstrike", "data");
17453
+ ROOT7 = join26(fileURLToPath13(new URL(".", import.meta.url)), "..");
17454
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join26(homedir10(), ".blitzstrike", "data");
17455
17455
  });
17456
17456
 
17457
17457
  // src/update.ts
@@ -17496,15 +17496,15 @@ var init_update = __esm(() => {
17496
17496
  });
17497
17497
 
17498
17498
  // src/release.ts
17499
- import { readFileSync as readFileSync28, existsSync as existsSync16 } from "node:fs";
17500
- import { join as join26 } from "node:path";
17499
+ import { readFileSync as readFileSync29, existsSync as existsSync16 } from "node:fs";
17500
+ import { join as join27 } from "node:path";
17501
17501
  import { fileURLToPath as fileURLToPath14 } from "node:url";
17502
- import { execSync as execSync4 } from "node:child_process";
17502
+ import { execSync as execSync5 } from "node:child_process";
17503
17503
  function validateRelease() {
17504
17504
  const checks = [];
17505
17505
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
17506
17506
  try {
17507
- const changelog = readFileSync28(join26(ROOT8, "CHANGELOG.md"), "utf8");
17507
+ const changelog = readFileSync29(join27(ROOT8, "CHANGELOG.md"), "utf8");
17508
17508
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
17509
17509
  const changelogVersion = m?.[1] ?? null;
17510
17510
  checks.push({
@@ -17516,13 +17516,13 @@ function validateRelease() {
17516
17516
  checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
17517
17517
  }
17518
17518
  try {
17519
- const tag = execSync4("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17519
+ const tag = execSync5("git describe --tags --abbrev=0", { cwd: ROOT8, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
17520
17520
  checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
17521
17521
  } catch {
17522
17522
  checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git/tags)", soft: true });
17523
17523
  }
17524
17524
  try {
17525
- const published = execSync4("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
17525
+ const published = execSync5("npm view blitzstrike version", { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"] }).trim();
17526
17526
  checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
17527
17527
  } catch {
17528
17528
  checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
@@ -17536,22 +17536,22 @@ function validateRelease() {
17536
17536
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
17537
17537
  }
17538
17538
  function lockfilePresent() {
17539
- return existsSync16(join26(ROOT8, "package-lock.json")) || existsSync16(join26(ROOT8, "bun.lock")) || existsSync16(join26(ROOT8, "bun.lockb"));
17539
+ return existsSync16(join27(ROOT8, "package-lock.json")) || existsSync16(join27(ROOT8, "bun.lock")) || existsSync16(join27(ROOT8, "bun.lockb"));
17540
17540
  }
17541
17541
  var ROOT8;
17542
17542
  var init_release = __esm(() => {
17543
17543
  init_version();
17544
- ROOT8 = join26(fileURLToPath14(new URL(".", import.meta.url)), "..");
17544
+ ROOT8 = join27(fileURLToPath14(new URL(".", import.meta.url)), "..");
17545
17545
  });
17546
17546
 
17547
17547
  // src/dependency.ts
17548
- import { readFileSync as readFileSync29 } from "node:fs";
17549
- import { join as join27 } from "node:path";
17548
+ import { readFileSync as readFileSync30 } from "node:fs";
17549
+ import { join as join28 } from "node:path";
17550
17550
  import { fileURLToPath as fileURLToPath15 } from "node:url";
17551
17551
  function checkDependencies() {
17552
17552
  let pkg = {};
17553
17553
  try {
17554
- pkg = JSON.parse(readFileSync29(join27(ROOT9, "package.json"), "utf8"));
17554
+ pkg = JSON.parse(readFileSync30(join28(ROOT9, "package.json"), "utf8"));
17555
17555
  } catch {
17556
17556
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
17557
17557
  }
@@ -17574,12 +17574,12 @@ function checkDependencies() {
17574
17574
  }
17575
17575
  var ROOT9;
17576
17576
  var init_dependency = __esm(() => {
17577
- ROOT9 = join27(fileURLToPath15(new URL(".", import.meta.url)), "..");
17577
+ ROOT9 = join28(fileURLToPath15(new URL(".", import.meta.url)), "..");
17578
17578
  });
17579
17579
 
17580
17580
  // src/cli.ts
17581
- import { readFileSync as readFileSync30, existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "node:fs";
17582
- import { join as join28, dirname as dirname6 } from "node:path";
17581
+ import { readFileSync as readFileSync31, existsSync as existsSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11 } from "node:fs";
17582
+ import { join as join29, dirname as dirname6 } from "node:path";
17583
17583
  import { homedir as homedir11 } from "node:os";
17584
17584
  import { fileURLToPath as fileURLToPath16 } from "node:url";
17585
17585
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -17624,7 +17624,7 @@ async function runDoctor() {
17624
17624
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17625
17625
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17626
17626
  });
17627
- const dataOk = existsSync17(join28(ROOT10, "chains.json")) && existsSync17(join28(ROOT10, "tools-catalog.json"));
17627
+ const dataOk = existsSync17(join29(ROOT10, "chains.json")) && existsSync17(join29(ROOT10, "tools-catalog.json"));
17628
17628
  issues.push({
17629
17629
  name: "Data layers (chains + tools-catalog)",
17630
17630
  status: dataOk ? "ok" : "fail",
@@ -17675,21 +17675,21 @@ BlitzStrike — ${verdict}`);
17675
17675
  function resolveCommand() {
17676
17676
  if (which("blitzstrike"))
17677
17677
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17678
- const src = join28(ROOT10, "src", "index.ts");
17678
+ const src = join29(ROOT10, "src", "index.ts");
17679
17679
  if (which("bun") && existsSync17(src))
17680
17680
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17681
17681
  if (which("npx"))
17682
17682
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17683
17683
  if (which("bunx"))
17684
17684
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17685
- const dist = join28(ROOT10, "dist", "index.js");
17685
+ const dist = join29(ROOT10, "dist", "index.js");
17686
17686
  if (existsSync17(dist))
17687
17687
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17688
17688
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17689
17689
  }
17690
17690
  function readJson(p) {
17691
17691
  try {
17692
- return JSON.parse(readFileSync30(p, "utf8"));
17692
+ return JSON.parse(readFileSync31(p, "utf8"));
17693
17693
  } catch {
17694
17694
  return null;
17695
17695
  }
@@ -17722,17 +17722,17 @@ function opencodeWrite(p) {
17722
17722
  };
17723
17723
  }
17724
17724
  function copyOpenCodeAgents() {
17725
- const srcDir = join28(ROOT10, "opencode-agents");
17726
- const dstDir = join28(homedir11(), ".config", "opencode", "agents");
17725
+ const srcDir = join29(ROOT10, "opencode-agents");
17726
+ const dstDir = join29(homedir11(), ".config", "opencode", "agents");
17727
17727
  if (!existsSync17(srcDir))
17728
17728
  return;
17729
17729
  if (!existsSync17(dstDir))
17730
17730
  mkdirSync11(dstDir, { recursive: true });
17731
17731
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17732
- const src = join28(srcDir, f);
17733
- const dst = join28(dstDir, f);
17732
+ const src = join29(srcDir, f);
17733
+ const dst = join29(dstDir, f);
17734
17734
  if (existsSync17(src)) {
17735
- writeFileSync10(dst, readFileSync30(src, "utf8"));
17735
+ writeFileSync10(dst, readFileSync31(src, "utf8"));
17736
17736
  }
17737
17737
  }
17738
17738
  }
@@ -17741,7 +17741,7 @@ function codexWrite(p) {
17741
17741
  const dir = dirname6(p);
17742
17742
  if (!existsSync17(dir))
17743
17743
  mkdirSync11(dir, { recursive: true });
17744
- let existing = existsSync17(p) ? readFileSync30(p, "utf8") : "";
17744
+ let existing = existsSync17(p) ? readFileSync31(p, "utf8") : "";
17745
17745
  if (!existing.trimEnd().endsWith(`
17746
17746
  `))
17747
17747
  existing += `
@@ -17760,7 +17760,7 @@ function hermesWrite(p) {
17760
17760
  const dir = dirname6(p);
17761
17761
  if (!existsSync17(dir))
17762
17762
  mkdirSync11(dir, { recursive: true });
17763
- let existing = existsSync17(p) ? readFileSync30(p, "utf8") : "";
17763
+ let existing = existsSync17(p) ? readFileSync31(p, "utf8") : "";
17764
17764
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17765
17765
  if (!existing.trimEnd().endsWith(`
17766
17766
  `))
@@ -17787,74 +17787,74 @@ mcp_servers:
17787
17787
  function detectAgents() {
17788
17788
  const home = homedir11();
17789
17789
  const agents = [];
17790
- const claudeJson = join28(home, ".claude.json");
17790
+ const claudeJson = join29(home, ".claude.json");
17791
17791
  agents.push({
17792
17792
  name: "Claude Code",
17793
17793
  detectPath: claudeJson,
17794
17794
  installed: () => existsSync17(claudeJson) || which("claude"),
17795
17795
  write: jsonMcpServersWrite(claudeJson)
17796
17796
  });
17797
- const claudeDesktop = join28(home, ".config", "Claude", "claude_desktop_config.json");
17797
+ const claudeDesktop = join29(home, ".config", "Claude", "claude_desktop_config.json");
17798
17798
  agents.push({
17799
17799
  name: "Claude Desktop",
17800
17800
  detectPath: claudeDesktop,
17801
17801
  installed: () => existsSync17(claudeDesktop),
17802
17802
  write: jsonMcpServersWrite(claudeDesktop)
17803
17803
  });
17804
- const cursor = join28(home, ".cursor", "mcp.json");
17804
+ const cursor = join29(home, ".cursor", "mcp.json");
17805
17805
  agents.push({
17806
17806
  name: "Cursor",
17807
17807
  detectPath: cursor,
17808
- installed: () => existsSync17(join28(home, ".cursor")) || which("cursor"),
17808
+ installed: () => existsSync17(join29(home, ".cursor")) || which("cursor"),
17809
17809
  write: jsonMcpServersWrite(cursor)
17810
17810
  });
17811
- const opencode = join28(home, ".config", "opencode", "opencode.jsonc");
17811
+ const opencode = join29(home, ".config", "opencode", "opencode.jsonc");
17812
17812
  agents.push({
17813
17813
  name: "OpenCode",
17814
17814
  detectPath: opencode,
17815
- installed: () => existsSync17(join28(home, ".config", "opencode")) || which("opencode"),
17815
+ installed: () => existsSync17(join29(home, ".config", "opencode")) || which("opencode"),
17816
17816
  write: opencodeWrite(opencode)
17817
17817
  });
17818
- const codex = join28(home, ".codex", "config.toml");
17818
+ const codex = join29(home, ".codex", "config.toml");
17819
17819
  agents.push({
17820
17820
  name: "Codex",
17821
17821
  detectPath: codex,
17822
- installed: () => existsSync17(join28(home, ".codex")) || which("codex"),
17822
+ installed: () => existsSync17(join29(home, ".codex")) || which("codex"),
17823
17823
  write: codexWrite(codex)
17824
17824
  });
17825
- const hermes = join28(home, ".hermes", "config.yaml");
17825
+ const hermes = join29(home, ".hermes", "config.yaml");
17826
17826
  agents.push({
17827
17827
  name: "Hermes",
17828
17828
  detectPath: hermes,
17829
- installed: () => existsSync17(join28(home, ".hermes")) || which("hermes"),
17829
+ installed: () => existsSync17(join29(home, ".hermes")) || which("hermes"),
17830
17830
  write: hermesWrite(hermes)
17831
17831
  });
17832
- const gemini = join28(home, ".gemini", "settings.json");
17832
+ const gemini = join29(home, ".gemini", "settings.json");
17833
17833
  agents.push({
17834
17834
  name: "Gemini CLI",
17835
17835
  detectPath: gemini,
17836
- installed: () => existsSync17(join28(home, ".gemini")) || which("gemini"),
17836
+ installed: () => existsSync17(join29(home, ".gemini")) || which("gemini"),
17837
17837
  write: jsonMcpServersWrite(gemini)
17838
17838
  });
17839
- const windsurf = join28(home, ".codeium", "windsurf", "mcp_config.json");
17839
+ const windsurf = join29(home, ".codeium", "windsurf", "mcp_config.json");
17840
17840
  agents.push({
17841
17841
  name: "Windsurf",
17842
17842
  detectPath: windsurf,
17843
- installed: () => existsSync17(join28(home, ".codeium")) || which("windsurf"),
17843
+ installed: () => existsSync17(join29(home, ".codeium")) || which("windsurf"),
17844
17844
  write: jsonMcpServersWrite(windsurf)
17845
17845
  });
17846
- const copilot = join28(home, ".copilot", "mcp.json");
17846
+ const copilot = join29(home, ".copilot", "mcp.json");
17847
17847
  agents.push({
17848
17848
  name: "Copilot",
17849
17849
  detectPath: copilot,
17850
- installed: () => existsSync17(join28(home, ".copilot")),
17850
+ installed: () => existsSync17(join29(home, ".copilot")),
17851
17851
  write: jsonMcpServersWrite(copilot)
17852
17852
  });
17853
- const cline = join28(home, ".cline", "mcp_settings.json");
17853
+ const cline = join29(home, ".cline", "mcp_settings.json");
17854
17854
  agents.push({
17855
17855
  name: "Cline",
17856
17856
  detectPath: cline,
17857
- installed: () => existsSync17(join28(home, ".cline")),
17857
+ installed: () => existsSync17(join29(home, ".cline")),
17858
17858
  write: jsonMcpServersWrite(cline)
17859
17859
  });
17860
17860
  return agents;
@@ -17972,7 +17972,7 @@ var init_cli = __esm(() => {
17972
17972
  init_release();
17973
17973
  init_dependency();
17974
17974
  init_mcp_status();
17975
- ROOT10 = join28(fileURLToPath16(new URL(".", import.meta.url)), "..");
17975
+ ROOT10 = join29(fileURLToPath16(new URL(".", import.meta.url)), "..");
17976
17976
  });
17977
17977
 
17978
17978
  // node_modules/zod/v3/helpers/util.js
@@ -32610,7 +32610,7 @@ class StdioServerTransport {
32610
32610
 
32611
32611
  // src/server.ts
32612
32612
  init_scanner();
32613
- import { readFileSync as readFileSync27 } from "node:fs";
32613
+ import { readFileSync as readFileSync28 } from "node:fs";
32614
32614
 
32615
32615
  // src/recon.ts
32616
32616
  var TIMEOUT_MS = 25000;
@@ -61447,18 +61447,123 @@ solc = "0.8.20"
61447
61447
  }
61448
61448
  }
61449
61449
 
61450
+ // src/hunting-intel.ts
61451
+ import { execSync as execSync4 } from "node:child_process";
61452
+ import { resolve4 as resolve42, Resolver } from "node:dns/promises";
61453
+ import { readFileSync as readFileSync25 } from "node:fs";
61454
+ import { join as join24 } from "node:path";
61455
+ var BLOCKING_KEYWORDS = /internet positif|kominfo|\.go\.id\b|pemblokiran|ditutup|dilarang|akses dibatasi|blocked|restricted|illegal|advertencia|coljuegos|regulat|forbidden|this (site|website) has been (blocked|banned)/i;
61456
+ async function localResolve(host) {
61457
+ try {
61458
+ return await resolve42(host);
61459
+ } catch {
61460
+ return [];
61461
+ }
61462
+ }
61463
+ async function publicResolve(host) {
61464
+ const r = new Resolver;
61465
+ r.setServers(["8.8.8.8", "1.1.1.1"]);
61466
+ try {
61467
+ return await r.resolve4(host);
61468
+ } catch {
61469
+ return [];
61470
+ }
61471
+ }
61472
+ function probeBlockingPage(host, ip, timeoutMs = 8000) {
61473
+ for (const port of ["443", "80"]) {
61474
+ const scheme = port === "443" ? "https" : "http";
61475
+ try {
61476
+ const out = execSync4(`curl -sk -m 6 --resolve ${host}:${port}:${ip} ${scheme}://${host}/ 2>/dev/null`, { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
61477
+ const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
61478
+ if (!preview)
61479
+ continue;
61480
+ return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
61481
+ } catch {}
61482
+ }
61483
+ return { probed: false, sinkhole: false, preview: "" };
61484
+ }
61485
+ async function resolveCheck(host) {
61486
+ const clean = host.replace(/^https?:\/\//, "").split("/")[0];
61487
+ const local = await localResolve(clean);
61488
+ const pub = await publicResolve(clean);
61489
+ if (local.length === 0 && pub.length === 0) {
61490
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
61491
+ }
61492
+ if (local.length === 0) {
61493
+ return { host: clean, local_ips: [], public_ips: pub, sinkhole: false, verdict: "local-unresolvable", evidence: "local resolver returned no A record; public DNS resolves. Use the public IP to reach the real target." };
61494
+ }
61495
+ const firstLocal = local[0];
61496
+ const probe = probeBlockingPage(clean, firstLocal);
61497
+ if (probe.sinkhole) {
61498
+ return {
61499
+ host: clean,
61500
+ local_ips: local,
61501
+ public_ips: pub,
61502
+ sinkhole: true,
61503
+ verdict: "sinkhole",
61504
+ evidence: `local IP ${firstLocal} serves a blocking/sinkhole page ("${probe.preview.slice(0, 120)}"). Audit the PUBLIC IP (${pub[0] ?? "n/a"}) instead — e.g. curl --resolve ${clean}:443:${pub[0]} https://${clean}/`
61505
+ };
61506
+ }
61507
+ if (!probe.probed) {
61508
+ return { host: clean, local_ips: local, public_ips: pub, sinkhole: false, verdict: "inconclusive", evidence: "local IP did not respond on 443/80 — inconclusive; for sensitive niches prefer the public IP anyway." };
61509
+ }
61510
+ const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
61511
+ return {
61512
+ host: clean,
61513
+ local_ips: local,
61514
+ public_ips: pub,
61515
+ sinkhole: false,
61516
+ verdict: ipDiff ? "clean-cdn" : "clean",
61517
+ evidence: ipDiff ? "local and public IPs differ but the local page shows no blocking content (normal for CDN/anycast/round-robin)." : "local resolution matches public DNS and shows no blocking content."
61518
+ };
61519
+ }
61520
+ function loadWatchlist() {
61521
+ const raw = readFileSync25(join24(process.cwd(), "intelligence", "watchlist.json"), "utf8");
61522
+ const data = JSON.parse(raw);
61523
+ return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
61524
+ }
61525
+ function fmtPayout(min, max) {
61526
+ if (min == null || max == null)
61527
+ return "n/a";
61528
+ const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
61529
+ return `${f(min)}–${f(max)}`;
61530
+ }
61531
+ function watchlist() {
61532
+ const { cwe_frequency, cve_watchlist } = loadWatchlist();
61533
+ const entries = cwe_frequency.map((e) => {
61534
+ const pat = e.pattern ? patternLookup(e.pattern) : null;
61535
+ const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
61536
+ const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
61537
+ return {
61538
+ cwe: e.cwe,
61539
+ name: e.name,
61540
+ rank: e.rank,
61541
+ saturated: e.saturated,
61542
+ pattern: e.pattern ?? null,
61543
+ payout_min: payoutMin,
61544
+ payout_max: payoutMax,
61545
+ typical_payout: fmtPayout(payoutMin, payoutMax),
61546
+ notes: e.notes,
61547
+ grounded: pat != null
61548
+ };
61549
+ }).sort((a, b) => a.rank - b.rank);
61550
+ const unsaturated = entries.filter((e) => !e.saturated);
61551
+ const recommendation = `Prioritize the ${unsaturated.length} non-saturated classes (top: ${unsaturated.slice(0, 3).map((e) => e.cwe).join(", ")}) — high frequency + high payout + not yet saturated. Skip saturated/low-value classes (XSS/CSRF/open-redirect standalone) unless they chain into account takeover.`;
61552
+ return { cwe_frequency: entries, cve_watchlist, recommendation };
61553
+ }
61554
+
61450
61555
  // src/enterprise-benchmark.ts
61451
- import { existsSync as existsSync15, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
61452
- import { join as join24, resolve } from "node:path";
61556
+ import { existsSync as existsSync15, readFileSync as readFileSync26, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
61557
+ import { join as join25, resolve } from "node:path";
61453
61558
  init_complex_bugs();
61454
61559
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
61455
61560
  var ENTERPRISE_FIXTURES = [
61456
- { id: "php-laravel", dir: join24(ROOT6, "php-laravel"), expected_vulns: 4 },
61457
- { id: "js-express", dir: join24(ROOT6, "js-express"), expected_vulns: 2 },
61458
- { id: "py-django", dir: join24(ROOT6, "py-django"), expected_vulns: 2 },
61459
- { id: "java-spring", dir: join24(ROOT6, "java-spring"), expected_vulns: 2 },
61460
- { id: "wp-batch-route", dir: join24(ROOT6, "wp-batch-route"), expected_vulns: 3 },
61461
- { id: "php-complex", dir: join24(ROOT6, "php-complex"), expected_vulns: 4 }
61561
+ { id: "php-laravel", dir: join25(ROOT6, "php-laravel"), expected_vulns: 4 },
61562
+ { id: "js-express", dir: join25(ROOT6, "js-express"), expected_vulns: 2 },
61563
+ { id: "py-django", dir: join25(ROOT6, "py-django"), expected_vulns: 2 },
61564
+ { id: "java-spring", dir: join25(ROOT6, "java-spring"), expected_vulns: 2 },
61565
+ { id: "wp-batch-route", dir: join25(ROOT6, "wp-batch-route"), expected_vulns: 3 },
61566
+ { id: "php-complex", dir: join25(ROOT6, "php-complex"), expected_vulns: 4 }
61462
61567
  ];
61463
61568
  function sourceFiles(root) {
61464
61569
  const out = [];
@@ -61470,7 +61575,7 @@ function sourceFiles(root) {
61470
61575
  return;
61471
61576
  }
61472
61577
  for (const e of entries) {
61473
- const p = join24(dir, e);
61578
+ const p = join25(dir, e);
61474
61579
  let st;
61475
61580
  try {
61476
61581
  st = statSync3(p);
@@ -61491,7 +61596,7 @@ function detectFixture(dir) {
61491
61596
  for (const f of sourceFiles(dir)) {
61492
61597
  let code;
61493
61598
  try {
61494
- code = readFileSync25(f, "utf8");
61599
+ code = readFileSync26(f, "utf8");
61495
61600
  } catch {
61496
61601
  continue;
61497
61602
  }
@@ -61704,7 +61809,7 @@ function createServer2() {
61704
61809
  ` + `- 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).
61705
61810
  ` + `- 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.
61706
61811
 
61707
- ` + "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, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. 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. Generate the concrete reproduction recipe with auto_poc(type, base_url, endpoint, param) — it emits the exact marker request + negative control + expected differential + Patchstack-style title, so 'reproducible' is executable, not abstract. 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. HUNTING DOCTRINE (maximize payout rate): (1) SIBLING RULE — if one endpoint requires auth, check every sibling (/export /delete /share); ~30% of paid IDOR/auth bugs come from siblings — AUTOMATE it with sibling_scan(base_url, endpoint, token_a, token_b), the deterministic live logic-bug prober (sibling enumeration + differential IDOR + auth matrix + mass assignment + rate limit, evidence-first). (2) A→B SIGNAL — a confirmed bug signals a class of developer mistake; hunt B and C (20-min box) before reporting. (3) TWO-ACCOUNT IDOR — always test with attacker account A reading victim account B, and state it in the report. (4) PoC ESCALATION — IDOR shows victim data (not 200), XSS shows cookie exfil (not alert), SSRF shows internal response (not DNS), SQLi shows DB content (not error). (5) FOLLOW THE MONEY — billing/credits/refunds/wallet flows. (6) Kill weak findings with kill_list(type) BEFORE the gate — missing headers, self-XSS, open-redirect-alone, SSRF-DNS-only, GraphQL-introspection-alone are always N/A standalone. (7) CI/CD + SAML/SSO are critical attack surfaces (pull_request_target, expression injection, XML signature wrapping, comment injection). Full doctrine: hunting_doctrine().\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/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. 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. OOB (out-of-band) PROOF for BLIND vulns: oob_start() spins an interactsh listener (unique *.oast.* domain, streams DNS/HTTP interactions — the standard for public targets) or a local HTTP listener for local labs; inject the canary, then oob_poll(id) until the hit arrives; oob_stop(id) to clean up. A blind SSRF/XXE/SQLi/command-injection is CONFIRMED only when the canary actually arrives — never from a guess. 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. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE). Rust is supported end-to-end: the taint engine (blitz_scan / taint_file) maps Rust sinks (std::process::Command, sqlx/diesel, reqwest/hyper, std::fs, serde/bincode) to the classic classes, plus two Rust-specific detectors — rust_unsafe (transmute / assume_init / from_raw_parts / ptr::* — memory-unsafety surface, CWE-119) and rust_format_string (println!/format! with a variable as the format string — CWE-134). The sqlx query! compile-time macro is treated as safe (suppressed). Two more web-class detectors: jwt_alg_confusion (a JWT verifier that dispatches on the token's alg header and accepts BOTH RS256 and HS256, with the HS256 HMAC secret derived from public/shared key material — CWE-347; the attacker sets alg=HS256 and signs with the public key to forge admin tokens) and cache_deception (a reverse proxy caching static-extension URLs with a cache key that omits the session cookie, over a session-authenticated backend — CWE-525; the victim's cached private response is re-fetched anonymously). TO TRY a jwt_alg_confusion hit: fetch the public key (/.well-known/jwks.json), forge an HS256 token signed with that public key, and confirm admin access. TO TRY a cache_deception hit: request an authenticated path with a static extension appended (.css/.js) and re-fetch it anonymously. Three more detectors: cors_misconfiguration (Access-Control-Allow-Origin reflects the request Origin or is '*' while credentials are true — CWE-942; any site reads the victim's authenticated responses), xss (user input into a DOM sink — innerHTML / dangerouslySetInnerHTML / document.write / .html() / eval — CWE-79), and subdomain_takeover (a dangling CNAME/AliasTarget to a claimable service like *.github.io/*.herokuapp.com/*.amazonaws.com — CWE-404). TO TRY a cors hit: send an evil Origin + confirm the response echoes it with Allow-Credentials:true. TO TRY an xss hit: confirm the sink executes (drive_devtools). TO TRY a takeover hit: verify the CNAME target resolves to an unclaimed/not-found page. WEB3: for Solidity/EVM, call web3_audit(code|path) — a deterministic 13-class smart-contract audit (reentrancy incl. ERC721/ERC1155 callback reentrancy, unchecked return, unchecked arithmetic, tx.origin auth, signature replay, unprotected selfdestruct, delegatecall-to-user-input, missing access control, timestamp dependence, spot-price oracle manipulation incl. slot0/latestRoundData, unbounded loop, abi.encodePacked hash collision, missing address(0) validation) returning ranked findings (class/CWE/severity/line), then foundry_poc(class, contract) to generate an executable Foundry test, and foundry_run(contract_code, poc_code|class_) to actually EXECUTE it — a PASSING PoC test is the proof that the exploit reproduces (deterministic: forge's own test verdict). A web3 hit is a HYPOTHESIS until the Foundry PoC reproduces it.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
61812
+ ` + "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, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. 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. Generate the concrete reproduction recipe with auto_poc(type, base_url, endpoint, param) — it emits the exact marker request + negative control + expected differential + Patchstack-style title, so 'reproducible' is executable, not abstract. 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. HUNTING DOCTRINE (maximize payout rate): (1) SIBLING RULE — if one endpoint requires auth, check every sibling (/export /delete /share); ~30% of paid IDOR/auth bugs come from siblings — AUTOMATE it with sibling_scan(base_url, endpoint, token_a, token_b), the deterministic live logic-bug prober (sibling enumeration + differential IDOR + auth matrix + mass assignment + rate limit, evidence-first). (2) A→B SIGNAL — a confirmed bug signals a class of developer mistake; hunt B and C (20-min box) before reporting. (3) TWO-ACCOUNT IDOR — always test with attacker account A reading victim account B, and state it in the report. (4) PoC ESCALATION — IDOR shows victim data (not 200), XSS shows cookie exfil (not alert), SSRF shows internal response (not DNS), SQLi shows DB content (not error). (5) FOLLOW THE MONEY — billing/credits/refunds/wallet flows. (6) Kill weak findings with kill_list(type) BEFORE the gate — missing headers, self-XSS, open-redirect-alone, SSRF-DNS-only, GraphQL-introspection-alone are always N/A standalone. (7) CI/CD + SAML/SSO are critical attack surfaces (pull_request_target, expression injection, XML signature wrapping, comment injection). Full doctrine: hunting_doctrine(). WHAT TO HUNT NEXT: watchlist() returns the ranked CWE frequency + high-value CVEs with payout + a saturated flag (prioritize high-freq/high-payout/not-saturated: missing-authz, SQLi, SSRF, deserialization; skip saturated XSS/CSRF/open-redirect unless they chain into takeover). PRECISION GUARD: resolve_check(host) detects when an ISP/country redirects the domain to a blocking/sinkhole page (gambling/adult/streaming niches) — audit the PUBLIC IP, never the sinkhole.\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/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. 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. OOB (out-of-band) PROOF for BLIND vulns: oob_start() spins an interactsh listener (unique *.oast.* domain, streams DNS/HTTP interactions — the standard for public targets) or a local HTTP listener for local labs; inject the canary, then oob_poll(id) until the hit arrives; oob_stop(id) to clean up. A blind SSRF/XXE/SQLi/command-injection is CONFIRMED only when the canary actually arrives — never from a guess. 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. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE). Rust is supported end-to-end: the taint engine (blitz_scan / taint_file) maps Rust sinks (std::process::Command, sqlx/diesel, reqwest/hyper, std::fs, serde/bincode) to the classic classes, plus two Rust-specific detectors — rust_unsafe (transmute / assume_init / from_raw_parts / ptr::* — memory-unsafety surface, CWE-119) and rust_format_string (println!/format! with a variable as the format string — CWE-134). The sqlx query! compile-time macro is treated as safe (suppressed). Two more web-class detectors: jwt_alg_confusion (a JWT verifier that dispatches on the token's alg header and accepts BOTH RS256 and HS256, with the HS256 HMAC secret derived from public/shared key material — CWE-347; the attacker sets alg=HS256 and signs with the public key to forge admin tokens) and cache_deception (a reverse proxy caching static-extension URLs with a cache key that omits the session cookie, over a session-authenticated backend — CWE-525; the victim's cached private response is re-fetched anonymously). TO TRY a jwt_alg_confusion hit: fetch the public key (/.well-known/jwks.json), forge an HS256 token signed with that public key, and confirm admin access. TO TRY a cache_deception hit: request an authenticated path with a static extension appended (.css/.js) and re-fetch it anonymously. Three more detectors: cors_misconfiguration (Access-Control-Allow-Origin reflects the request Origin or is '*' while credentials are true — CWE-942; any site reads the victim's authenticated responses), xss (user input into a DOM sink — innerHTML / dangerouslySetInnerHTML / document.write / .html() / eval — CWE-79), and subdomain_takeover (a dangling CNAME/AliasTarget to a claimable service like *.github.io/*.herokuapp.com/*.amazonaws.com — CWE-404). TO TRY a cors hit: send an evil Origin + confirm the response echoes it with Allow-Credentials:true. TO TRY an xss hit: confirm the sink executes (drive_devtools). TO TRY a takeover hit: verify the CNAME target resolves to an unclaimed/not-found page. WEB3: for Solidity/EVM, call web3_audit(code|path) — a deterministic 13-class smart-contract audit (reentrancy incl. ERC721/ERC1155 callback reentrancy, unchecked return, unchecked arithmetic, tx.origin auth, signature replay, unprotected selfdestruct, delegatecall-to-user-input, missing access control, timestamp dependence, spot-price oracle manipulation incl. slot0/latestRoundData, unbounded loop, abi.encodePacked hash collision, missing address(0) validation) returning ranked findings (class/CWE/severity/line), then foundry_poc(class, contract) to generate an executable Foundry test, and foundry_run(contract_code, poc_code|class_) to actually EXECUTE it — a PASSING PoC test is the proof that the exploit reproduces (deterministic: forge's own test verdict). A web3 hit is a HYPOTHESIS until the Foundry PoC reproduces it.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
61708
61813
  ` + `- 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.
61709
61814
  ` + `- 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.
61710
61815
  ` + "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."
@@ -62585,7 +62690,7 @@ ${statusLine}` : statusLine },
62585
62690
  let src = code;
62586
62691
  if (!src && path) {
62587
62692
  try {
62588
- src = readFileSync27(path, "utf8");
62693
+ src = readFileSync28(path, "utf8");
62589
62694
  } catch {
62590
62695
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
62591
62696
  }
@@ -62666,7 +62771,7 @@ ${statusLine}` : statusLine },
62666
62771
  let src = code;
62667
62772
  if (!src && path) {
62668
62773
  try {
62669
- src = readFileSync27(path, "utf8");
62774
+ src = readFileSync28(path, "utf8");
62670
62775
  } catch {
62671
62776
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
62672
62777
  }
@@ -62714,6 +62819,22 @@ ${statusLine}` : statusLine },
62714
62819
  }, async () => {
62715
62820
  return { content: [{ type: "text", text: JSON.stringify(huntingDoctrine()) }] };
62716
62821
  });
62822
+ server.registerTool("resolve_check", {
62823
+ title: "DNS sinkhole / blocking-page detection (precision guard)",
62824
+ description: "Detect when a target domain is redirected by the ISP/country to a blocking/sinkhole page (gambling/adult/streaming niches) instead of the real site. Compares the local resolver against public DNS (8.8.8.8/1.1.1.1) and probes the local IP for blocking-page content (.gov / 'Internet Positif' / 'blocked' keywords). If a sinkhole is detected, audit the PUBLIC IP — never the sinkhole. Content is the signal (a different IP set alone is normal for CDN/anycast). Use before live_recon/active_scan on any sensitive-niche domain.",
62825
+ inputSchema: {
62826
+ host: string2().describe("Domain to check (e.g. indoav.site or https://target.com)")
62827
+ }
62828
+ }, async ({ host }) => {
62829
+ return { content: [{ type: "text", text: JSON.stringify(await resolveCheck(host)) }] };
62830
+ });
62831
+ server.registerTool("watchlist", {
62832
+ title: "Known CWE/CVE frequency + payout watchlist (what to hunt next)",
62833
+ description: "Return the most-reported bug-bounty classes (CWE frequency, ranked) and high-value CVEs, each with payout range + a 'saturated' flag, joined to a Blitz Strike detector pattern for grounding. Use it to decide WHAT to hunt next: prioritize high-frequency, high-payout, NOT-yet-saturated classes (IDOR/missing-authz, SQLi, SSRF, deserialization); skip saturated/low-value classes (XSS/CSRF/open-redirect) unless they chain into account takeover.",
62834
+ inputSchema: {}
62835
+ }, async () => {
62836
+ return { content: [{ type: "text", text: JSON.stringify(watchlist()) }] };
62837
+ });
62717
62838
  server.registerTool("auto_poc", {
62718
62839
  title: "Auto-PoC — generate a copy-paste-ready reproduction recipe",
62719
62840
  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.",
@@ -63333,7 +63454,7 @@ ${statusLine}` : statusLine },
63333
63454
  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.",
63334
63455
  inputSchema: { path: string2().describe("Source file path (PHP)") }
63335
63456
  }, async ({ path }) => {
63336
- const code = readFileSync27(path, "utf8");
63457
+ const code = readFileSync28(path, "utf8");
63337
63458
  const findings = detectRouteConfusion(code, path);
63338
63459
  return {
63339
63460
  content: [{
@@ -63347,7 +63468,7 @@ ${statusLine}` : statusLine },
63347
63468
  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).",
63348
63469
  inputSchema: { path: string2().describe("Source file path") }
63349
63470
  }, async ({ path }) => {
63350
- const code = readFileSync27(path, "utf8");
63471
+ const code = readFileSync28(path, "utf8");
63351
63472
  const findings = detectComplexBugs(code, path);
63352
63473
  return {
63353
63474
  content: [{
@@ -63433,7 +63554,7 @@ ${statusLine}` : statusLine },
63433
63554
  let lang = language;
63434
63555
  if (path && !code) {
63435
63556
  try {
63436
- src = readFileSync27(path, "utf8");
63557
+ src = readFileSync28(path, "utf8");
63437
63558
  } catch {
63438
63559
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
63439
63560
  }
@@ -63682,15 +63803,15 @@ async function serve() {
63682
63803
 
63683
63804
  // src/index.ts
63684
63805
  init_cli();
63685
- import { readFileSync as readFileSync31, existsSync as existsSync18 } from "node:fs";
63686
- import { join as join29 } from "node:path";
63806
+ import { readFileSync as readFileSync32, existsSync as existsSync18 } from "node:fs";
63807
+ import { join as join30 } from "node:path";
63687
63808
  import { fileURLToPath as fileURLToPath17 } from "node:url";
63688
- var ROOT11 = join29(fileURLToPath17(new URL(".", import.meta.url)), "..");
63809
+ var ROOT11 = join30(fileURLToPath17(new URL(".", import.meta.url)), "..");
63689
63810
  function readVersion() {
63690
63811
  try {
63691
- const p = join29(ROOT11, "package.json");
63812
+ const p = join30(ROOT11, "package.json");
63692
63813
  if (existsSync18(p))
63693
- return JSON.parse(readFileSync31(p, "utf8")).version ?? "1.0.0";
63814
+ return JSON.parse(readFileSync32(p, "utf8")).version ?? "1.0.0";
63694
63815
  } catch {}
63695
63816
  return "1.0.0";
63696
63817
  }
@@ -0,0 +1,33 @@
1
+ {
2
+ "_comment": "Bug-bounty disclosure frequency + payout watchlist. Derived from public disclosure statistics (HackerOne Hacktivity / CWE Top 25) and typical bounty ranges — a 'what to hunt next' signal, not a vulnerability database. Each CWE entry links to a Blitz Strike detector pattern when one exists (grounding via pattern_lookup).",
3
+ "cwe_frequency": [
4
+ { "cwe": "CWE-79", "name": "Cross-site Scripting (XSS)", "rank": 1, "saturated": true, "pattern": "xss", "payout_min": 100, "payout_max": 2500, "notes": "Most-reported class; heavily automated and low-paying — hunt only for high-impact variants (stored/account-takeover)." },
5
+ { "cwe": "CWE-862", "name": "Missing Authorization / IDOR", "rank": 2, "saturated": false, "pattern": "missing_authz", "payout_min": 1000, "payout_max": 10000, "notes": "~30% of paid access-control bugs; sibling rule + two-account test are the highest-ROI moves." },
6
+ { "cwe": "CWE-284", "name": "Improper Access Control (priv-esc)", "rank": 3, "saturated": false, "pattern": "priv_esc", "payout_min": 1000, "payout_max": 15000, "notes": "Role/privilege escalation; mass assignment + forced membership are under-hunted." },
7
+ { "cwe": "CWE-89", "name": "SQL Injection", "rank": 4, "saturated": false, "pattern": "sql_injection", "payout_min": 1000, "payout_max": 15000, "notes": "Still high-value where it reaches a real query; WAF bypass + second-order matter." },
8
+ { "cwe": "CWE-918", "name": "Server-Side Request Forgery (SSRF)", "rank": 5, "saturated": false, "pattern": "ssrf", "payout_min": 1000, "payout_max": 15000, "notes": "P1 when it hits cloud metadata; proof = internal response or OOB callback." },
9
+ { "cwe": "CWE-78", "name": "OS Command Injection", "rank": 6, "saturated": false, "pattern": "command_injection", "payout_min": 1000, "payout_max": 15000, "notes": "Critical class; less common than XSS/SQLi but very high impact." },
10
+ { "cwe": "CWE-502", "name": "Insecure Deserialization", "rank": 7, "saturated": false, "pattern": "deserialization", "payout_min": 5000, "payout_max": 30000, "notes": "Highest per-finding payout; needs a gadget chain but very rewarded." },
11
+ { "cwe": "CWE-94", "name": "Code Injection / eval", "rank": 8, "saturated": false, "pattern": "command_injection", "payout_min": 1000, "payout_max": 15000, "notes": "eval/exec with user input; folds into RCE umbrella." },
12
+ { "cwe": "CWE-434", "name": "Unrestricted File Upload", "rank": 9, "saturated": false, "pattern": "file_upload", "payout_min": 1000, "payout_max": 10000, "notes": "Upload → RCE chains pay well; confirm the file actually executes." },
13
+ { "cwe": "CWE-611", "name": "XML External Entity (XXE)", "rank": 10, "saturated": false, "pattern": "xxe", "payout_min": 1000, "payout_max": 10000, "notes": "Less common post-2020 but still found in SOAP/XML upload flows." },
14
+ { "cwe": "CWE-287", "name": "Authentication Bypass", "rank": 11, "saturated": false, "pattern": "type_juggling", "payout_min": 1000, "payout_max": 15000, "notes": "Loose comparison / JWT confusion / OAuth misconfig — account takeover class." },
15
+ { "cwe": "CWE-798", "name": "Hardcoded Credentials", "rank": 12, "saturated": false, "pattern": "hardcoded_secret", "payout_min": 500, "payout_max": 10000, "notes": "Committed keys are common in JS bundles + .git history; prove the key is ACTIVE." },
16
+ { "cwe": "CWE-22", "name": "Path Traversal / LFI", "rank": 13, "saturated": true, "pattern": "path_confusion", "payout_min": 500, "payout_max": 5000, "notes": "Saturated; value only when it chains to RCE (log poisoning, session files)." },
17
+ { "cwe": "CWE-601", "name": "Open Redirect", "rank": 14, "saturated": true, "pattern": "oauth_misconfig", "payout_min": 100, "payout_max": 1500, "notes": "Standalone is usually N/A; value only chained into OAuth/token theft." },
18
+ { "cwe": "CWE-352", "name": "Cross-Site Request Forgery (CSRF)", "rank": 15, "saturated": true, "pattern": "missing_nonce", "payout_min": 100, "payout_max": 2000, "notes": "Saturated + low; only for high-impact state changes (password/email/transfer)." },
19
+ { "cwe": "CWE-200", "name": "Information Exposure", "rank": 16, "saturated": true, "pattern": "hardcoded_secret", "payout_min": 100, "payout_max": 2000, "notes": "Excessive data exposure / verbose errors; usually a chain enabler, not a standalone bounty." },
20
+ { "cwe": "CWE-400", "name": "Denial of Service / rate-limit", "rank": 17, "saturated": true, "pattern": null, "payout_min": 100, "payout_max": 1000, "notes": "Usually out of scope standalone; relevant only for business-critical flows." }
21
+ ],
22
+ "cve_watchlist": [
23
+ { "cve": "CVE-2021-44228", "name": "Log4Shell", "product": "Apache Log4j 2", "class": "RCE", "payout_hint": "critical" },
24
+ { "cve": "CVE-2022-22965", "name": "Spring4Shell", "product": "Spring Framework", "class": "RCE", "payout_hint": "critical" },
25
+ { "cve": "CVE-2021-41773", "name": "Apache path traversal", "product": "Apache httpd 2.4.49", "class": "Path traversal / RCE", "payout_hint": "high" },
26
+ { "cve": "CVE-2023-22518", "name": "Confluence auth bypass", "product": "Atlassian Confluence", "class": "Auth bypass", "payout_hint": "high" },
27
+ { "cve": "CVE-2024-27198", "name": "TeamCity auth bypass", "product": "JetBrains TeamCity", "class": "Auth bypass / RCE", "payout_hint": "high" },
28
+ { "cve": "CVE-2023-34362", "name": "MOVEit SQLi", "product": "Progress MOVEit", "class": "SQL injection", "payout_hint": "critical" },
29
+ { "cve": "CVE-2022-42475", "name": "Fortinet SSL-VPN", "product": "FortiOS", "class": "RCE", "payout_hint": "critical" },
30
+ { "cve": "CVE-2023-27997", "name": "Fortinet SSL-VPN pre-auth", "product": "FortiOS", "class": "RCE", "payout_hint": "critical" },
31
+ { "cve": "CVE-2024-6387", "name": "regreSSHion", "product": "OpenSSH", "class": "RCE (race)", "payout_hint": "critical" }
32
+ ]
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.93",
3
+ "version": "2.4.94",
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": {