blitzstrike 2.4.97 → 2.4.99

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,9 +5,30 @@ 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.99] — 2026-09-17
9
+
10
+ ### Fixed — grounding completeness for the CVE watchlist class tags
11
+
12
+ - The CVE watchlist's `class` tags (`auth_bypass`, `infoleak`) did not resolve
13
+ to a detector pattern. Added synonyms (`auth bypass`/`authentication bypass`
14
+ → `type_juggling`; `infoleak`/`info|information disclosure|exposure|leak` →
15
+ `hardcoded_secret`). Every one of the 18 CVE class tags now resolves to a
16
+ grounded pattern (locked by a regression check).
17
+
18
+ ## [2.4.98] — 2026-09-17
19
+
20
+ ### Added — fingerprint-first CVE matching wired into live_recon
21
+
22
+ - `live_recon()` now cross-references the detected stack (tech + headers +
23
+ body) against the CVE watchlist and returns `suggested_cves` + a
24
+ "FINGERPRINT MATCH" next-step: when the target matches a high-value
25
+ watchlist product (Next.js, Kibana, Langflow, Confluence/Jira, PAN-OS,
26
+ Grafana, Keycloak, …) it names the specific CVEs to test first (with EPSS)
27
+ instead of relying on generic scanning.
28
+
8
29
  ## [2.4.97] — 2026-09-17
9
30
 
10
- ### Added — bugbounty-lab101 knowledge extraction (watchlist upgrade + dedup doctrine)
31
+ ### Added — hunting-intel upgrade (Hacktivity-ranked CVE watchlist + dedup doctrine)
11
32
 
12
33
  - Upgraded `watchlist()` CVE data from a generic 9-CVE list to the 18
13
34
  Hacktivity-ranked CVEs with EPSS, report counts, and per-CVE fingerprint
package/dist/index.js CHANGED
@@ -17285,12 +17285,12 @@ document.getElementById('out').innerHTML = u;
17285
17285
  });
17286
17286
 
17287
17287
  // src/variants.ts
17288
- import { readFileSync as readFileSync20 } from "node:fs";
17289
- import { join as join20, dirname as dirname5 } from "node:path";
17288
+ import { readFileSync as readFileSync21 } from "node:fs";
17289
+ import { join as join21, dirname as dirname5 } from "node:path";
17290
17290
  import { fileURLToPath as fileURLToPath11 } from "node:url";
17291
17291
  function loadVariants() {
17292
17292
  try {
17293
- const doc = JSON.parse(readFileSync20(join20(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
17293
+ const doc = JSON.parse(readFileSync21(join21(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
17294
17294
  return doc.variants ?? [];
17295
17295
  } catch {
17296
17296
  return [];
@@ -32693,8 +32693,8 @@ async function nvdLookup(cveId) {
32693
32693
 
32694
32694
  // src/orchestrator.ts
32695
32695
  init_scanner();
32696
- import { readFileSync as readFileSync10, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
32697
- import { join as join12 } from "node:path";
32696
+ import { readFileSync as readFileSync11, existsSync as existsSync8, statSync as statSync2 } from "node:fs";
32697
+ import { join as join13 } from "node:path";
32698
32698
  import { fileURLToPath as fileURLToPath6 } from "node:url";
32699
32699
 
32700
32700
  // src/memory.ts
@@ -34489,6 +34489,14 @@ var ALIASES = {
34489
34489
  "hardcoded credential": "hardcoded_secret",
34490
34490
  "hardcoded secret": "hardcoded_secret",
34491
34491
  "credential leak": "hardcoded_secret",
34492
+ "auth bypass": "type_juggling",
34493
+ "authentication bypass": "type_juggling",
34494
+ auth_bypass: "type_juggling",
34495
+ infoleak: "hardcoded_secret",
34496
+ "info leak": "hardcoded_secret",
34497
+ "information leak": "hardcoded_secret",
34498
+ "information disclosure": "hardcoded_secret",
34499
+ "information exposure": "hardcoded_secret",
34492
34500
  "hard coded credentials": "hardcoded_secret",
34493
34501
  "priv esc": "priv_esc",
34494
34502
  privesc: "priv_esc",
@@ -36067,8 +36075,135 @@ function toSarif(findings, opts = {}) {
36067
36075
  // src/orchestrator.ts
36068
36076
  init_version();
36069
36077
 
36078
+ // src/hunting-intel.ts
36079
+ import { execFileSync } from "node:child_process";
36080
+ import { resolve4, Resolver } from "node:dns/promises";
36081
+ import { readFileSync as readFileSync10 } from "node:fs";
36082
+ import { join as join12 } from "node:path";
36083
+ 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;
36084
+ var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
36085
+ function isValidHostname(host) {
36086
+ return host.length <= 253 && HOSTNAME_RE.test(host);
36087
+ }
36088
+ async function localResolve(host) {
36089
+ try {
36090
+ return await resolve4(host);
36091
+ } catch {
36092
+ return [];
36093
+ }
36094
+ }
36095
+ async function publicResolve(host) {
36096
+ const r = new Resolver;
36097
+ r.setServers(["8.8.8.8", "1.1.1.1"]);
36098
+ try {
36099
+ return await r.resolve4(host);
36100
+ } catch {
36101
+ return [];
36102
+ }
36103
+ }
36104
+ function probeBlockingPage(host, ip, timeoutMs = 8000) {
36105
+ for (const port of ["443", "80"]) {
36106
+ const scheme = port === "443" ? "https" : "http";
36107
+ try {
36108
+ const out = execFileSync("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
36109
+ const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
36110
+ if (!preview)
36111
+ continue;
36112
+ return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
36113
+ } catch {}
36114
+ }
36115
+ return { probed: false, sinkhole: false, preview: "" };
36116
+ }
36117
+ async function resolveCheck(host) {
36118
+ const clean = host.replace(/^https?:\/\//, "").split("/")[0];
36119
+ if (!isValidHostname(clean)) {
36120
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
36121
+ }
36122
+ const local = await localResolve(clean);
36123
+ const pub = await publicResolve(clean);
36124
+ if (local.length === 0 && pub.length === 0) {
36125
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
36126
+ }
36127
+ if (local.length === 0) {
36128
+ 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." };
36129
+ }
36130
+ const firstLocal = local[0];
36131
+ const probe = probeBlockingPage(clean, firstLocal);
36132
+ if (probe.sinkhole) {
36133
+ return {
36134
+ host: clean,
36135
+ local_ips: local,
36136
+ public_ips: pub,
36137
+ sinkhole: true,
36138
+ verdict: "sinkhole",
36139
+ 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}/`
36140
+ };
36141
+ }
36142
+ if (!probe.probed) {
36143
+ 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." };
36144
+ }
36145
+ const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
36146
+ return {
36147
+ host: clean,
36148
+ local_ips: local,
36149
+ public_ips: pub,
36150
+ sinkhole: false,
36151
+ verdict: ipDiff ? "clean-cdn" : "clean",
36152
+ 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."
36153
+ };
36154
+ }
36155
+ function loadWatchlist() {
36156
+ const raw = readFileSync10(join12(process.cwd(), "intelligence", "watchlist.json"), "utf8");
36157
+ const data = JSON.parse(raw);
36158
+ return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
36159
+ }
36160
+ function fmtPayout(min, max) {
36161
+ if (min == null || max == null)
36162
+ return "n/a";
36163
+ const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
36164
+ return `${f(min)}–${f(max)}`;
36165
+ }
36166
+ function watchlist() {
36167
+ const { cwe_frequency, cve_watchlist } = loadWatchlist();
36168
+ const entries = cwe_frequency.map((e) => {
36169
+ const pat = e.pattern ? patternLookup(e.pattern) : null;
36170
+ const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
36171
+ const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
36172
+ return {
36173
+ cwe: e.cwe,
36174
+ name: e.name,
36175
+ rank: e.rank,
36176
+ saturated: e.saturated,
36177
+ pattern: e.pattern ?? null,
36178
+ payout_min: payoutMin,
36179
+ payout_max: payoutMax,
36180
+ typical_payout: fmtPayout(payoutMin, payoutMax),
36181
+ notes: e.notes,
36182
+ grounded: pat != null
36183
+ };
36184
+ }).sort((a, b) => a.rank - b.rank);
36185
+ const unsaturated = entries.filter((e) => !e.saturated);
36186
+ const topCve = cve_watchlist.filter((c) => (c.epss ?? 0) >= 0.8 || (c.reports ?? 0) >= 100).slice(0, 4).map((c) => c.cve).join(", ");
36187
+ 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. FINGERPRINT FIRST: if the target stack matches a watchlist product (Next.js, Kibana, Langflow, Confluence/Jira, PAN-OS, Grafana), test that specific CVE — top EPSS/report CVEs: ${topCve}.`;
36188
+ return { cwe_frequency: entries, cve_watchlist, recommendation };
36189
+ }
36190
+ function watchlistCveMatches(techs, headers = {}, body = "") {
36191
+ const { cve_watchlist } = loadWatchlist();
36192
+ const haystack = `${techs.join(" ")} ${Object.keys(headers).join(" ")} ${Object.values(headers).join(" ")} ${body}`.toLowerCase();
36193
+ return cve_watchlist.filter((c) => {
36194
+ const m = c.match;
36195
+ if (!m)
36196
+ return false;
36197
+ try {
36198
+ return new RegExp(m, "i").test(haystack);
36199
+ } catch {
36200
+ return false;
36201
+ }
36202
+ });
36203
+ }
36204
+
36070
36205
  // src/live-recon.ts
36071
- import { resolve4 } from "node:dns/promises";
36206
+ import { resolve4 as resolve42 } from "node:dns/promises";
36072
36207
  import { connect } from "node:net";
36073
36208
  var HTTP_TIMEOUT_MS = 20000;
36074
36209
  function isLikelyCdn(ip) {
@@ -36284,7 +36419,7 @@ async function dnsBrute(domain) {
36284
36419
  const hosts = COMMON_SUBDOMAINS.map((p) => `${p}.${base}`).filter((h) => !seen.has(h));
36285
36420
  const settled = await Promise.allSettled(hosts.map(async (host) => {
36286
36421
  try {
36287
- const ips = await resolve4(host);
36422
+ const ips = await resolve42(host);
36288
36423
  return ips.length > 0 ? { host, ips } : null;
36289
36424
  } catch {
36290
36425
  return null;
@@ -36501,6 +36636,7 @@ async function liveRecon(target, includeActive = false) {
36501
36636
  const waf = detectWaf(root.headers, root.body);
36502
36637
  const techs = detectTech(root.headers, root.body);
36503
36638
  const versions = extractVersion(root.headers, root.body);
36639
+ const suggestedCves = watchlistCveMatches(techs, root.headers, root.body);
36504
36640
  const { links, scripts } = crawlLinks(base, root.body);
36505
36641
  const { robots, sitemap } = await fetchRobotsAndSitemap(base);
36506
36642
  const allUrls = [...links, ...scripts, ...robots, ...sitemap];
@@ -36534,6 +36670,9 @@ async function liveRecon(target, includeActive = false) {
36534
36670
  for (const v of versions) {
36535
36671
  nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
36536
36672
  }
36673
+ if (suggestedCves.length > 0) {
36674
+ 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("; ")}.`);
36675
+ }
36537
36676
  if (crawl.endpoints.length > 0)
36538
36677
  nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
36539
36678
  if (params.length > 0)
@@ -36560,6 +36699,7 @@ async function liveRecon(target, includeActive = false) {
36560
36699
  waf,
36561
36700
  tech: { detected: techs, server: fingerprint.server },
36562
36701
  versions,
36702
+ suggested_cves: suggestedCves.map((c) => ({ cve: c.cve, name: c.name, product: c.product, epss: c.epss, reports: c.reports })),
36563
36703
  crawl,
36564
36704
  params,
36565
36705
  subdomains,
@@ -36636,9 +36776,9 @@ function loadChains() {
36636
36776
  if (_chainsCache)
36637
36777
  return _chainsCache;
36638
36778
  try {
36639
- const dir = join12(fileURLToPath6(new URL(".", import.meta.url)), "..");
36640
- const path = join12(dir, "chains.json");
36641
- const raw = JSON.parse(readFileSync10(path, "utf8"));
36779
+ const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
36780
+ const path = join13(dir, "chains.json");
36781
+ const raw = JSON.parse(readFileSync11(path, "utf8"));
36642
36782
  _chainsCache = raw.chains ?? [];
36643
36783
  return _chainsCache;
36644
36784
  } catch {
@@ -37419,8 +37559,8 @@ var CHAIN_ID_TO_ESCALATION = {
37419
37559
  };
37420
37560
  function loadChainLinks() {
37421
37561
  try {
37422
- const dir = join12(fileURLToPath6(new URL(".", import.meta.url)), "..");
37423
- const data = JSON.parse(readFileSync10(join12(dir, "intelligence", "chain_links.json"), "utf8"));
37562
+ const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
37563
+ const data = JSON.parse(readFileSync11(join13(dir, "intelligence", "chain_links.json"), "utf8"));
37424
37564
  return data.links ?? [];
37425
37565
  } catch {
37426
37566
  return [];
@@ -37491,7 +37631,7 @@ async function runAutonomous(target, scope = "", mode = "bug-bounty", maxPasses
37491
37631
  }
37492
37632
 
37493
37633
  // src/ops.ts
37494
- import { execSync, execFileSync } from "node:child_process";
37634
+ import { execSync, execFileSync as execFileSync2 } from "node:child_process";
37495
37635
  function decomposeBatches(tasks) {
37496
37636
  const remaining = [...tasks];
37497
37637
  const done = new Set;
@@ -37536,13 +37676,13 @@ function worktree(action, path, branch) {
37536
37676
  if (!path)
37537
37677
  return { action, ok: false, error: "path is required for worktree add" };
37538
37678
  const b = branch ?? `blitz-${Date.now().toString(36)}`;
37539
- const out = execFileSync("git", ["worktree", "add", "-b", b, path], { encoding: "utf8" }).trim();
37679
+ const out = execFileSync2("git", ["worktree", "add", "-b", b, path], { encoding: "utf8" }).trim();
37540
37680
  return { action, ok: true, branch: b, path, out };
37541
37681
  }
37542
37682
  if (action === "remove") {
37543
37683
  if (!path)
37544
37684
  return { action, ok: false, error: "path is required for worktree remove" };
37545
- execFileSync("git", ["worktree", "remove", path, "--force"], { encoding: "utf8" });
37685
+ execFileSync2("git", ["worktree", "remove", path, "--force"], { encoding: "utf8" });
37546
37686
  return { action, ok: true, path };
37547
37687
  }
37548
37688
  return { action, ok: false, error: "unknown action (use add | list | remove)" };
@@ -37576,18 +37716,18 @@ function compactState(phase, findings) {
37576
37716
  }
37577
37717
 
37578
37718
  // src/tasks.ts
37579
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
37580
- import { join as join13 } from "node:path";
37719
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "node:fs";
37720
+ import { join as join14 } from "node:path";
37581
37721
  import { fileURLToPath as fileURLToPath7 } from "node:url";
37582
37722
  function tasksDir() {
37583
- return join13(fileURLToPath7(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37723
+ return join14(fileURLToPath7(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37584
37724
  }
37585
37725
  function taskPath(id) {
37586
- return join13(tasksDir(), `${id}.json`);
37726
+ return join14(tasksDir(), `${id}.json`);
37587
37727
  }
37588
37728
  function load2(id) {
37589
37729
  try {
37590
- return JSON.parse(readFileSync11(taskPath(id), "utf8"));
37730
+ return JSON.parse(readFileSync12(taskPath(id), "utf8"));
37591
37731
  } catch {
37592
37732
  return null;
37593
37733
  }
@@ -37925,18 +38065,18 @@ function scaffoldEngagement(target, scope = [], out_of_scope = []) {
37925
38065
  }
37926
38066
 
37927
38067
  // src/incremental.ts
37928
- import { execFileSync as execFileSync2 } from "node:child_process";
38068
+ import { execFileSync as execFileSync3 } from "node:child_process";
37929
38069
  import { writeFileSync as writeFileSync4, mkdtempSync, rmSync } from "node:fs";
37930
- import { join as join14 } from "node:path";
38070
+ import { join as join15 } from "node:path";
37931
38071
  import { tmpdir } from "node:os";
37932
38072
 
37933
38073
  // src/eagle2.ts
37934
38074
  var import_php_parser2 = __toESM(require_src(), 1);
37935
- import { readFileSync as readFileSync14 } from "node:fs";
38075
+ import { readFileSync as readFileSync15 } from "node:fs";
37936
38076
 
37937
38077
  // src/dataflow.ts
37938
38078
  init_scanner();
37939
- import { readFileSync as readFileSync12 } from "node:fs";
38079
+ import { readFileSync as readFileSync13 } from "node:fs";
37940
38080
  var SOURCES = {
37941
38081
  $_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
37942
38082
  $_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
@@ -38021,7 +38161,7 @@ function traceDataFlow(path) {
38021
38161
  return result;
38022
38162
  let text;
38023
38163
  try {
38024
- text = readFileSync12(path, "utf8");
38164
+ text = readFileSync13(path, "utf8");
38025
38165
  } catch {
38026
38166
  return result;
38027
38167
  }
@@ -38102,7 +38242,7 @@ init_scanner();
38102
38242
 
38103
38243
  // src/callgraph.ts
38104
38244
  var import_php_parser = __toESM(require_src(), 1);
38105
- import { readFileSync as readFileSync13 } from "node:fs";
38245
+ import { readFileSync as readFileSync14 } from "node:fs";
38106
38246
  var parser = new import_php_parser.Engine({
38107
38247
  parser: { extractDoc: false, php7: true },
38108
38248
  ast: { withPositions: true, withSource: false }
@@ -38163,7 +38303,7 @@ function buildCallGraph(path) {
38163
38303
  const g = { nodes: new Map, edges: [] };
38164
38304
  let code;
38165
38305
  try {
38166
- code = readFileSync13(path, "utf8");
38306
+ code = readFileSync14(path, "utf8");
38167
38307
  } catch {
38168
38308
  return g;
38169
38309
  }
@@ -38825,7 +38965,7 @@ function resolveFuncId(name, summaries) {
38825
38965
  function analyzeDataFlow2(path) {
38826
38966
  let cacheCode = "";
38827
38967
  try {
38828
- cacheCode = readFileSync14(path, "utf8");
38968
+ cacheCode = readFileSync15(path, "utf8");
38829
38969
  } catch {}
38830
38970
  if (cacheCode) {
38831
38971
  const cached = getCache("eagle2", cacheCode);
@@ -38847,7 +38987,7 @@ function analyzeDataFlow2(path) {
38847
38987
  };
38848
38988
  let code;
38849
38989
  try {
38850
- code = cacheCode || readFileSync14(path, "utf8");
38990
+ code = cacheCode || readFileSync15(path, "utf8");
38851
38991
  } catch {
38852
38992
  return result;
38853
38993
  }
@@ -57518,7 +57658,7 @@ registerLanguage(rustAdapter);
57518
57658
  var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
57519
57659
  function git(repo, args) {
57520
57660
  try {
57521
- return execFileSync2("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
57661
+ return execFileSync3("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
57522
57662
  } catch {
57523
57663
  return "";
57524
57664
  }
@@ -57533,8 +57673,8 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57533
57673
  continue;
57534
57674
  const lang = detectLanguage(file)?.language;
57535
57675
  if (lang === "php") {
57536
- const dir = mkdtempSync(join14(tmpdir(), "blitz-incr-"));
57537
- const tmp = join14(dir, "changed.php");
57676
+ const dir = mkdtempSync(join15(tmpdir(), "blitz-incr-"));
57677
+ const tmp = join15(dir, "changed.php");
57538
57678
  writeFileSync4(tmp, code);
57539
57679
  try {
57540
57680
  const r = analyzeDataFlow2(tmp);
@@ -57555,7 +57695,7 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57555
57695
  // src/taint.ts
57556
57696
  init_scanner();
57557
57697
  var import_php_parser4 = __toESM(require_src(), 1);
57558
- import { readFileSync as readFileSync15 } from "node:fs";
57698
+ import { readFileSync as readFileSync16 } from "node:fs";
57559
57699
  var parser4 = new import_php_parser4.Engine({
57560
57700
  parser: { extractDoc: false, php7: true },
57561
57701
  ast: { withPositions: true, withSource: false }
@@ -57853,7 +57993,7 @@ function analyzeTaint(path) {
57853
57993
  const result = { file: path, findings: [], suppressed: 0 };
57854
57994
  let code;
57855
57995
  try {
57856
- code = readFileSync15(path, "utf8");
57996
+ code = readFileSync16(path, "utf8");
57857
57997
  } catch {
57858
57998
  return result;
57859
57999
  }
@@ -57947,15 +58087,15 @@ function taintTree(root, maxFiles = 2000) {
57947
58087
  }
57948
58088
 
57949
58089
  // src/differential.ts
57950
- import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16 } from "node:fs";
57951
- import { join as join15 } from "node:path";
58090
+ import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17 } from "node:fs";
58091
+ import { join as join16 } from "node:path";
57952
58092
  import { tmpdir as tmpdir2 } from "node:os";
57953
- import { execFileSync as execFileSync3 } from "node:child_process";
58093
+ import { execFileSync as execFileSync4 } from "node:child_process";
57954
58094
  init_scanner();
57955
58095
  function runEngine(code, language, file) {
57956
58096
  if (language === "php") {
57957
- const dir = mkdtempSync2(join15(tmpdir2(), "blitz-diff-"));
57958
- const tmp = join15(dir, "old.php");
58097
+ const dir = mkdtempSync2(join16(tmpdir2(), "blitz-diff-"));
58098
+ const tmp = join16(dir, "old.php");
57959
58099
  writeFileSync5(tmp, code);
57960
58100
  try {
57961
58101
  const r = analyzeDataFlow2(tmp);
@@ -58109,8 +58249,8 @@ function languageOf(file) {
58109
58249
  return adapter?.language ?? "php";
58110
58250
  }
58111
58251
  function analyzeDifferentialFiles(oldPath, newPath, language) {
58112
- const oldCode = readFileSync16(oldPath, "utf8");
58113
- const newCode = readFileSync16(newPath, "utf8");
58252
+ const oldCode = readFileSync17(oldPath, "utf8");
58253
+ const newCode = readFileSync17(newPath, "utf8");
58114
58254
  const lang = language ?? languageOf(newPath);
58115
58255
  return analyzeDifferential(oldCode, newCode, lang, newPath);
58116
58256
  }
@@ -58118,7 +58258,7 @@ var SOURCE_EXTS2 = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".p
58118
58258
  function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
58119
58259
  const git = (args) => {
58120
58260
  try {
58121
- return execFileSync3("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
58261
+ return execFileSync4("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
58122
58262
  } catch {
58123
58263
  return "";
58124
58264
  }
@@ -58237,27 +58377,27 @@ function analyzePatch(oldCode, newCode, language = "php") {
58237
58377
  };
58238
58378
  }
58239
58379
  function analyzePatchFiles(oldPath, newPath, language) {
58240
- const oldCode = readFileSync16(oldPath, "utf8");
58241
- const newCode = readFileSync16(newPath, "utf8");
58380
+ const oldCode = readFileSync17(oldPath, "utf8");
58381
+ const newCode = readFileSync17(newPath, "utf8");
58242
58382
  const lang = language ?? languageOf(newPath);
58243
58383
  return analyzePatch(oldCode, newCode, lang);
58244
58384
  }
58245
58385
 
58246
58386
  // src/frameworks.ts
58247
- import { readFileSync as readFileSync17, existsSync as existsSync9 } from "node:fs";
58248
- import { join as join16 } from "node:path";
58387
+ import { readFileSync as readFileSync18, existsSync as existsSync9 } from "node:fs";
58388
+ import { join as join17 } from "node:path";
58249
58389
  import { fileURLToPath as fileURLToPath8 } from "node:url";
58250
- var ROOT4 = join16(fileURLToPath8(new URL(".", import.meta.url)), "..");
58251
- var INTEL2 = join16(ROOT4, "intelligence");
58390
+ var ROOT4 = join17(fileURLToPath8(new URL(".", import.meta.url)), "..");
58391
+ var INTEL2 = join17(ROOT4, "intelligence");
58252
58392
  var cache = null;
58253
58393
  function load3() {
58254
58394
  if (cache)
58255
58395
  return cache;
58256
- const p = join16(INTEL2, "frameworks.json");
58396
+ const p = join17(INTEL2, "frameworks.json");
58257
58397
  if (!existsSync9(p))
58258
58398
  return null;
58259
58399
  try {
58260
- cache = JSON.parse(readFileSync17(p, "utf8"));
58400
+ cache = JSON.parse(readFileSync18(p, "utf8"));
58261
58401
  return cache;
58262
58402
  } catch {
58263
58403
  return null;
@@ -58767,9 +58907,9 @@ function nextSteps(ctx) {
58767
58907
 
58768
58908
  // src/git-history.ts
58769
58909
  init_complex_bugs();
58770
- import { execFileSync as execFileSync4 } from "node:child_process";
58910
+ import { execFileSync as execFileSync5 } from "node:child_process";
58771
58911
  import { existsSync as existsSync11 } from "node:fs";
58772
- import { join as join17 } from "node:path";
58912
+ import { join as join18 } from "node:path";
58773
58913
  function parseDeletions(output) {
58774
58914
  const out = [];
58775
58915
  let commit = "";
@@ -58790,7 +58930,7 @@ function parseDeletions(output) {
58790
58930
  return out;
58791
58931
  }
58792
58932
  function blobBeforeDeletion(root, commit, path) {
58793
- return execFileSync4("git", ["-C", root, "show", `${commit}^:${path}`], {
58933
+ return execFileSync5("git", ["-C", root, "show", `${commit}^:${path}`], {
58794
58934
  encoding: "utf8",
58795
58935
  maxBuffer: 10 * 1024 * 1024
58796
58936
  });
@@ -58798,12 +58938,12 @@ function blobBeforeDeletion(root, commit, path) {
58798
58938
  function scanGitHistory(root, opts) {
58799
58939
  const limit = opts?.limit ?? 30;
58800
58940
  const findings = [];
58801
- if (!existsSync11(join17(root, ".git"))) {
58941
+ if (!existsSync11(join18(root, ".git"))) {
58802
58942
  return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
58803
58943
  }
58804
58944
  if (opts?.secretPattern) {
58805
58945
  try {
58806
- const pick = execFileSync4("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
58946
+ const pick = execFileSync5("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
58807
58947
  const commits = pick.split(`
58808
58948
  `).filter(Boolean).map((l) => {
58809
58949
  const rest = l.startsWith("__COMMIT__ ") ? l.slice("__COMMIT__ ".length) : l;
@@ -58824,7 +58964,7 @@ function scanGitHistory(root, opts) {
58824
58964
  }
58825
58965
  }
58826
58966
  try {
58827
- const log = execFileSync4("git", ["-C", root, "log", "--all", "--diff-filter=D", "--name-status", "--format=__COMMIT__ %H %s", "--no-renames"], { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
58967
+ const log = execFileSync5("git", ["-C", root, "log", "--all", "--diff-filter=D", "--name-status", "--format=__COMMIT__ %H %s", "--no-renames"], { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
58828
58968
  const deletions = parseDeletions(log);
58829
58969
  const seen = new Set;
58830
58970
  let checked = 0;
@@ -58945,13 +59085,13 @@ async function validateLeakedKey(input) {
58945
59085
  }
58946
59086
 
58947
59087
  // src/hackbot-arena.ts
58948
- import { readFileSync as readFileSync18 } from "node:fs";
58949
- import { join as join18, dirname as dirname3 } from "node:path";
59088
+ import { readFileSync as readFileSync19 } from "node:fs";
59089
+ import { join as join19, dirname as dirname3 } from "node:path";
58950
59090
  import { fileURLToPath as fileURLToPath9 } from "node:url";
58951
59091
  var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
58952
59092
  function load4() {
58953
59093
  try {
58954
- return JSON.parse(readFileSync18(join18(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
59094
+ return JSON.parse(readFileSync19(join19(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
58955
59095
  } catch {
58956
59096
  return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
58957
59097
  }
@@ -59113,13 +59253,13 @@ function listSeverityMap() {
59113
59253
  }
59114
59254
 
59115
59255
  // src/datarefresh.ts
59116
- import { readFileSync as readFileSync19 } from "node:fs";
59117
- import { join as join19, dirname as dirname4 } from "node:path";
59256
+ import { readFileSync as readFileSync20 } from "node:fs";
59257
+ import { join as join20, dirname as dirname4 } from "node:path";
59118
59258
  import { fileURLToPath as fileURLToPath10 } from "node:url";
59119
59259
  var __dirname4 = dirname4(fileURLToPath10(import.meta.url));
59120
59260
  function loadJson2(rel) {
59121
59261
  try {
59122
- return JSON.parse(readFileSync19(join19(__dirname4, "..", rel), "utf8"));
59262
+ return JSON.parse(readFileSync20(join20(__dirname4, "..", rel), "utf8"));
59123
59263
  } catch {
59124
59264
  return null;
59125
59265
  }
@@ -59154,18 +59294,18 @@ init_verify_harness();
59154
59294
  init_variants();
59155
59295
 
59156
59296
  // src/benchmark.ts
59157
- import { readFileSync as readFileSync21, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59158
- import { join as join21 } from "node:path";
59297
+ import { readFileSync as readFileSync22, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59298
+ import { join as join22 } from "node:path";
59159
59299
  import { fileURLToPath as fileURLToPath12 } from "node:url";
59160
59300
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
59161
59301
  init_complex_bugs();
59162
- var ROOT5 = join21(fileURLToPath12(new URL(".", import.meta.url)), "..");
59163
- var CORPUS = join21(ROOT5, "bench", "corpus");
59164
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join21(homedir8(), ".blitzstrike", "corpus");
59302
+ var ROOT5 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
59303
+ var CORPUS = join22(ROOT5, "bench", "corpus");
59304
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join22(homedir8(), ".blitzstrike", "corpus");
59165
59305
  function parseCorpusFile(path) {
59166
59306
  let text;
59167
59307
  try {
59168
- text = readFileSync21(path, "utf8");
59308
+ text = readFileSync22(path, "utf8");
59169
59309
  } catch {
59170
59310
  return null;
59171
59311
  }
@@ -59221,7 +59361,7 @@ function loadDir(dir) {
59221
59361
  for (const f of readdirSync5(dir)) {
59222
59362
  if (!f.endsWith(".md"))
59223
59363
  continue;
59224
- const e = parseCorpusFile(join21(dir, f));
59364
+ const e = parseCorpusFile(join22(dir, f));
59225
59365
  if (e)
59226
59366
  entries.push(e);
59227
59367
  }
@@ -59248,7 +59388,7 @@ function captureFalsePositive(input) {
59248
59388
  input.code,
59249
59389
  "```"
59250
59390
  ].filter((l) => l !== null);
59251
- const path = join21(CAPTURED_CORPUS_DIR, `${id}.md`);
59391
+ const path = join22(CAPTURED_CORPUS_DIR, `${id}.md`);
59252
59392
  writeFileSync6(path, lines.join(`
59253
59393
  `) + `
59254
59394
  `, "utf8");
@@ -59270,7 +59410,7 @@ function detect(entry) {
59270
59410
  }
59271
59411
  const lang = entry.language;
59272
59412
  if (lang === "php") {
59273
- const tmp = join21(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59413
+ const tmp = join22(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59274
59414
  writeFileSync6(tmp, entry.code);
59275
59415
  const r = analyzeDataFlow2(tmp);
59276
59416
  rmSync3(tmp, { force: true });
@@ -59339,12 +59479,12 @@ function runBenchmark() {
59339
59479
  }
59340
59480
 
59341
59481
  // src/obligations.ts
59342
- import { readFileSync as readFileSync22, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
59343
- import { join as join22 } from "node:path";
59482
+ import { readFileSync as readFileSync23, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
59483
+ import { join as join23 } from "node:path";
59344
59484
  import { createHash as createHash6 } from "node:crypto";
59345
59485
  import { homedir as homedir9 } from "node:os";
59346
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join22(homedir9(), ".blitzstrike");
59347
- var OBLIGATION_PATH = join22(HOME_DIR4, "obligations.jsonl");
59486
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join23(homedir9(), ".blitzstrike");
59487
+ var OBLIGATION_PATH = join23(HOME_DIR4, "obligations.jsonl");
59348
59488
  function ensureDir3() {
59349
59489
  if (!existsSync12(HOME_DIR4))
59350
59490
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -59357,7 +59497,7 @@ function loadObligations() {
59357
59497
  return [];
59358
59498
  try {
59359
59499
  const out = [];
59360
- for (const line of readFileSync22(OBLIGATION_PATH, "utf8").split(`
59500
+ for (const line of readFileSync23(OBLIGATION_PATH, "utf8").split(`
59361
59501
  `)) {
59362
59502
  const t = line.trim();
59363
59503
  if (!t)
@@ -59461,11 +59601,11 @@ init_framework_security();
59461
59601
  // src/variant-scan.ts
59462
59602
  init_scanner();
59463
59603
  init_complex_bugs();
59464
- import { readFileSync as readFileSync24 } from "node:fs";
59604
+ import { readFileSync as readFileSync25 } from "node:fs";
59465
59605
 
59466
59606
  // src/cross-file.ts
59467
59607
  init_scanner();
59468
- import { readFileSync as readFileSync23 } from "node:fs";
59608
+ import { readFileSync as readFileSync24 } from "node:fs";
59469
59609
  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)/;
59470
59610
  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*\(/;
59471
59611
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -59517,7 +59657,7 @@ function detectCrossFilePrivesc(root) {
59517
59657
  for (const file of files) {
59518
59658
  let code;
59519
59659
  try {
59520
- code = stripComments(readFileSync23(file, "utf8"));
59660
+ code = stripComments(readFileSync24(file, "utf8"));
59521
59661
  } catch {
59522
59662
  continue;
59523
59663
  }
@@ -59558,7 +59698,7 @@ function detectCrossFilePrivesc(root) {
59558
59698
  for (const file of files) {
59559
59699
  let code;
59560
59700
  try {
59561
- code = stripComments(readFileSync23(file, "utf8"));
59701
+ code = stripComments(readFileSync24(file, "utf8"));
59562
59702
  } catch {
59563
59703
  continue;
59564
59704
  }
@@ -59581,7 +59721,7 @@ function detectCrossFilePrivesc(root) {
59581
59721
  for (const file of files) {
59582
59722
  let code;
59583
59723
  try {
59584
- code = stripComments(readFileSync23(file, "utf8"));
59724
+ code = stripComments(readFileSync24(file, "utf8"));
59585
59725
  } catch {
59586
59726
  continue;
59587
59727
  }
@@ -59696,7 +59836,7 @@ function variantScan(root, opts = {}) {
59696
59836
  for (const file of files) {
59697
59837
  let code;
59698
59838
  try {
59699
- code = readFileSync24(file, "utf8");
59839
+ code = readFileSync25(file, "utf8");
59700
59840
  } catch {
59701
59841
  continue;
59702
59842
  }
@@ -61376,7 +61516,7 @@ function listPocTypes() {
61376
61516
  import { execSync as execSync3 } from "node:child_process";
61377
61517
  import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, rmSync as rmSync4, existsSync as existsSync14 } from "node:fs";
61378
61518
  import { tmpdir as tmpdir4 } from "node:os";
61379
- import { join as join23 } from "node:path";
61519
+ import { join as join24 } from "node:path";
61380
61520
  function findForge() {
61381
61521
  try {
61382
61522
  const p = execSync3("which forge 2>/dev/null", { encoding: "utf8", timeout: 3000 }).trim();
@@ -61425,14 +61565,14 @@ async function foundryRun(input) {
61425
61565
  };
61426
61566
  }
61427
61567
  const poc = input.poc_code ?? foundryPoc(input.class_ ?? "missing_access_control", name);
61428
- const dir = mkdtempSync3(join23(tmpdir4(), "blitzstrike-foundry-"));
61568
+ const dir = mkdtempSync3(join24(tmpdir4(), "blitzstrike-foundry-"));
61429
61569
  try {
61430
61570
  try {
61431
61571
  execSync3(`${forge} init --force --no-git ${dir}`, { stdio: "ignore", timeout: 60000 });
61432
61572
  } catch {
61433
- mkdirSync9(join23(dir, "src"), { recursive: true });
61434
- mkdirSync9(join23(dir, "test"), { recursive: true });
61435
- writeFileSync8(join23(dir, "foundry.toml"), `[profile.default]
61573
+ mkdirSync9(join24(dir, "src"), { recursive: true });
61574
+ mkdirSync9(join24(dir, "test"), { recursive: true });
61575
+ writeFileSync8(join24(dir, "foundry.toml"), `[profile.default]
61436
61576
  src = "src"
61437
61577
  out = "out"
61438
61578
  libs = ["lib"]
@@ -61442,12 +61582,12 @@ solc = "0.8.20"
61442
61582
  for (const f of ["Counter.sol", "Counter.t.sol", "Counter.s.sol"]) {
61443
61583
  for (const sub of ["src", "test", "script"]) {
61444
61584
  try {
61445
- rmSync4(join23(dir, sub, f), { force: true });
61585
+ rmSync4(join24(dir, sub, f), { force: true });
61446
61586
  } catch {}
61447
61587
  }
61448
61588
  }
61449
- writeFileSync8(join23(dir, "src", `${name}.sol`), input.contract_code);
61450
- writeFileSync8(join23(dir, "test", `${name}.t.sol`), poc);
61589
+ writeFileSync8(join24(dir, "src", `${name}.sol`), input.contract_code);
61590
+ writeFileSync8(join24(dir, "test", `${name}.t.sol`), poc);
61451
61591
  const { out } = runForge(forge, dir, input.timeout_ms ?? 60000);
61452
61592
  const tests = parseTestResults(out);
61453
61593
  const anyFail = tests.some((t) => t.status === "fail");
@@ -61464,119 +61604,6 @@ solc = "0.8.20"
61464
61604
  }
61465
61605
  }
61466
61606
 
61467
- // src/hunting-intel.ts
61468
- import { execFileSync as execFileSync5 } from "node:child_process";
61469
- import { resolve4 as resolve42, Resolver } from "node:dns/promises";
61470
- import { readFileSync as readFileSync25 } from "node:fs";
61471
- import { join as join24 } from "node:path";
61472
- 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;
61473
- var HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
61474
- function isValidHostname(host) {
61475
- return host.length <= 253 && HOSTNAME_RE.test(host);
61476
- }
61477
- async function localResolve(host) {
61478
- try {
61479
- return await resolve42(host);
61480
- } catch {
61481
- return [];
61482
- }
61483
- }
61484
- async function publicResolve(host) {
61485
- const r = new Resolver;
61486
- r.setServers(["8.8.8.8", "1.1.1.1"]);
61487
- try {
61488
- return await r.resolve4(host);
61489
- } catch {
61490
- return [];
61491
- }
61492
- }
61493
- function probeBlockingPage(host, ip, timeoutMs = 8000) {
61494
- for (const port of ["443", "80"]) {
61495
- const scheme = port === "443" ? "https" : "http";
61496
- try {
61497
- const out = execFileSync5("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
61498
- const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
61499
- if (!preview)
61500
- continue;
61501
- return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
61502
- } catch {}
61503
- }
61504
- return { probed: false, sinkhole: false, preview: "" };
61505
- }
61506
- async function resolveCheck(host) {
61507
- const clean = host.replace(/^https?:\/\//, "").split("/")[0];
61508
- if (!isValidHostname(clean)) {
61509
- return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
61510
- }
61511
- const local = await localResolve(clean);
61512
- const pub = await publicResolve(clean);
61513
- if (local.length === 0 && pub.length === 0) {
61514
- return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
61515
- }
61516
- if (local.length === 0) {
61517
- 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." };
61518
- }
61519
- const firstLocal = local[0];
61520
- const probe = probeBlockingPage(clean, firstLocal);
61521
- if (probe.sinkhole) {
61522
- return {
61523
- host: clean,
61524
- local_ips: local,
61525
- public_ips: pub,
61526
- sinkhole: true,
61527
- verdict: "sinkhole",
61528
- 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}/`
61529
- };
61530
- }
61531
- if (!probe.probed) {
61532
- 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." };
61533
- }
61534
- const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
61535
- return {
61536
- host: clean,
61537
- local_ips: local,
61538
- public_ips: pub,
61539
- sinkhole: false,
61540
- verdict: ipDiff ? "clean-cdn" : "clean",
61541
- 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."
61542
- };
61543
- }
61544
- function loadWatchlist() {
61545
- const raw = readFileSync25(join24(process.cwd(), "intelligence", "watchlist.json"), "utf8");
61546
- const data = JSON.parse(raw);
61547
- return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
61548
- }
61549
- function fmtPayout(min, max) {
61550
- if (min == null || max == null)
61551
- return "n/a";
61552
- const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
61553
- return `${f(min)}–${f(max)}`;
61554
- }
61555
- function watchlist() {
61556
- const { cwe_frequency, cve_watchlist } = loadWatchlist();
61557
- const entries = cwe_frequency.map((e) => {
61558
- const pat = e.pattern ? patternLookup(e.pattern) : null;
61559
- const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
61560
- const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
61561
- return {
61562
- cwe: e.cwe,
61563
- name: e.name,
61564
- rank: e.rank,
61565
- saturated: e.saturated,
61566
- pattern: e.pattern ?? null,
61567
- payout_min: payoutMin,
61568
- payout_max: payoutMax,
61569
- typical_payout: fmtPayout(payoutMin, payoutMax),
61570
- notes: e.notes,
61571
- grounded: pat != null
61572
- };
61573
- }).sort((a, b) => a.rank - b.rank);
61574
- const unsaturated = entries.filter((e) => !e.saturated);
61575
- const topCve = cve_watchlist.filter((c) => (c.epss ?? 0) >= 0.8 || (c.reports ?? 0) >= 100).slice(0, 4).map((c) => c.cve).join(", ");
61576
- 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. FINGERPRINT FIRST: if the target stack matches a watchlist product (Next.js, Kibana, Langflow, Confluence/Jira, PAN-OS, Grafana), test that specific CVE — top EPSS/report CVEs: ${topCve}.`;
61577
- return { cwe_frequency: entries, cve_watchlist, recommendation };
61578
- }
61579
-
61580
61607
  // src/enterprise-benchmark.ts
61581
61608
  import { existsSync as existsSync15, readFileSync as readFileSync26, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
61582
61609
  import { join as join25, resolve } from "node:path";
@@ -180,7 +180,8 @@
180
180
  "class": "rce",
181
181
  "epss": 1.0,
182
182
  "reports": 2669,
183
- "fingerprint": "Confluence (Server/Data Center) \u2014 /login.action, confluence headers"
183
+ "fingerprint": "Confluence (Server/Data Center) \u2014 /login.action, confluence headers",
184
+ "match": "confluence|login\\.action|atlassian"
184
185
  },
185
186
  {
186
187
  "cve": "CVE-2025-29927",
@@ -189,7 +190,8 @@
189
190
  "class": "auth_bypass",
190
191
  "epss": 0.9962,
191
192
  "reports": 132,
192
- "fingerprint": "_next/static/chunks/ bundles; x-middleware-subrequest header"
193
+ "fingerprint": "_next/static/chunks/ bundles; x-middleware-subrequest header",
194
+ "match": "_next/|__next|next\\.js|middleware"
193
195
  },
194
196
  {
195
197
  "cve": "CVE-2025-55182",
@@ -198,7 +200,8 @@
198
200
  "class": "rce",
199
201
  "epss": 0.9956,
200
202
  "reports": 415,
201
- "fingerprint": "_next/ + Server Function / flight endpoints"
203
+ "fingerprint": "_next/ + Server Function / flight endpoints",
204
+ "match": "_next/|server function|react server|rsc"
202
205
  },
203
206
  {
204
207
  "cve": "CVE-2025-3248",
@@ -207,7 +210,8 @@
207
210
  "class": "rce",
208
211
  "epss": 0.9997,
209
212
  "reports": 29,
210
- "fingerprint": "/api/v1/validate/code \u2014 AI/agent tooling"
213
+ "fingerprint": "/api/v1/validate/code \u2014 AI/agent tooling",
214
+ "match": "langflow|validate/code"
211
215
  },
212
216
  {
213
217
  "cve": "CVE-2024-34351",
@@ -216,7 +220,8 @@
216
220
  "class": "ssrf",
217
221
  "epss": 0.0545,
218
222
  "reports": 50,
219
- "fingerprint": "Next.js Server Actions (Host header + relative redirect)"
223
+ "fingerprint": "Next.js Server Actions (Host header + relative redirect)",
224
+ "match": "_next/|server action"
220
225
  },
221
226
  {
222
227
  "cve": "CVE-2025-0133",
@@ -225,7 +230,8 @@
225
230
  "class": "xss",
226
231
  "epss": 0.4352,
227
232
  "reports": 1990,
228
- "fingerprint": "GlobalProtect captive portal"
233
+ "fingerprint": "GlobalProtect captive portal",
234
+ "match": "globalprotect|palo alto|pan-os|pan os"
229
235
  },
230
236
  {
231
237
  "cve": "CVE-2026-0257",
@@ -234,7 +240,8 @@
234
240
  "class": "auth_bypass",
235
241
  "epss": 0.8668,
236
242
  "reports": 27,
237
- "fingerprint": "GlobalProtect portal/gateway"
243
+ "fingerprint": "GlobalProtect portal/gateway",
244
+ "match": "globalprotect|palo alto|pan-os|pan os"
238
245
  },
239
246
  {
240
247
  "cve": "CVE-2025-4123",
@@ -243,7 +250,8 @@
243
250
  "class": "xss",
244
251
  "epss": 0.9781,
245
252
  "reports": 220,
246
- "fingerprint": "Grafana path traversal + open redirect (Image Renderer)"
253
+ "fingerprint": "Grafana path traversal + open redirect (Image Renderer)",
254
+ "match": "grafana|x-grafana"
247
255
  },
248
256
  {
249
257
  "cve": "CVE-2021-20323",
@@ -252,7 +260,8 @@
252
260
  "class": "xss",
253
261
  "epss": 0.3725,
254
262
  "reports": 107,
255
- "fingerprint": "Keycloak auth flows"
263
+ "fingerprint": "Keycloak auth flows",
264
+ "match": "keycloak|kc-|realms/"
256
265
  },
257
266
  {
258
267
  "cve": "CVE-2018-5230",
@@ -261,7 +270,8 @@
261
270
  "class": "xss",
262
271
  "epss": 0.3761,
263
272
  "reports": 4694,
264
- "fingerprint": "Jira /issuecollector custom fields"
273
+ "fingerprint": "Jira /issuecollector custom fields",
274
+ "match": "jira|issuecollector|atlassian"
265
275
  },
266
276
  {
267
277
  "cve": "CVE-2026-33461",
@@ -270,7 +280,8 @@
270
280
  "class": "auth_bypass",
271
281
  "epss": 0.0028,
272
282
  "reports": 8,
273
- "fingerprint": "Kibana internal Fleet endpoints"
283
+ "fingerprint": "Kibana internal Fleet endpoints",
284
+ "match": "kibana|elastic|fleet"
274
285
  },
275
286
  {
276
287
  "cve": "CVE-2026-49093",
@@ -279,7 +290,8 @@
279
290
  "class": "ssrf",
280
291
  "epss": 0.002,
281
292
  "reports": 5,
282
- "fingerprint": "Kibana connectors / allowlist bypass"
293
+ "fingerprint": "Kibana connectors / allowlist bypass",
294
+ "match": "kibana|elastic|connector"
283
295
  },
284
296
  {
285
297
  "cve": "CVE-2026-26938",
@@ -288,7 +300,8 @@
288
300
  "class": "ssti",
289
301
  "epss": 0.0025,
290
302
  "reports": 5,
291
- "fingerprint": "Kibana Workflows"
303
+ "fingerprint": "Kibana Workflows",
304
+ "match": "kibana|elastic|workflow"
292
305
  },
293
306
  {
294
307
  "cve": "CVE-2026-33068",
@@ -297,7 +310,8 @@
297
310
  "class": "priv_esc",
298
311
  "epss": 0.0034,
299
312
  "reports": 5,
300
- "fingerprint": "repo .claude/settings.json"
313
+ "fingerprint": "repo .claude/settings.json",
314
+ "match": "\\.claude/settings"
301
315
  },
302
316
  {
303
317
  "cve": "CVE-2025-25297",
@@ -306,7 +320,8 @@
306
320
  "class": "ssrf",
307
321
  "epss": 0.0062,
308
322
  "reports": 2,
309
- "fingerprint": "Label Studio S3 integration"
323
+ "fingerprint": "Label Studio S3 integration",
324
+ "match": "label ?studio|labelstudio|label-studio"
310
325
  },
311
326
  {
312
327
  "cve": "CVE-2026-33309",
@@ -315,7 +330,8 @@
315
330
  "class": "file_upload",
316
331
  "epss": 0.0142,
317
332
  "reports": 6,
318
- "fingerprint": "Langflow multipart upload filename"
333
+ "fingerprint": "Langflow multipart upload filename",
334
+ "match": "langflow"
319
335
  },
320
336
  {
321
337
  "cve": "CVE-2026-45543",
@@ -324,7 +340,8 @@
324
340
  "class": "missing_authz",
325
341
  "epss": 0.0027,
326
342
  "reports": 5,
327
- "fingerprint": "Nextcloud Forms collaborator removal"
343
+ "fingerprint": "Nextcloud Forms collaborator removal",
344
+ "match": "nextcloud|ocs-provider"
328
345
  },
329
346
  {
330
347
  "cve": "CVE-2017-15277",
@@ -333,7 +350,8 @@
333
350
  "class": "infoleak",
334
351
  "epss": 0.1919,
335
352
  "reports": 5914,
336
- "fingerprint": "image processing (GIF upload)"
353
+ "fingerprint": "image processing (GIF upload)",
354
+ "match": "imagemagick|graphicsmagick"
337
355
  }
338
356
  ]
339
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.97",
3
+ "version": "2.4.99",
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": {