blitzstrike 2.4.96 → 2.4.98

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/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
@@ -36067,8 +36067,135 @@ function toSarif(findings, opts = {}) {
36067
36067
  // src/orchestrator.ts
36068
36068
  init_version();
36069
36069
 
36070
+ // src/hunting-intel.ts
36071
+ import { execFileSync } from "node:child_process";
36072
+ import { resolve4, Resolver } from "node:dns/promises";
36073
+ import { readFileSync as readFileSync10 } from "node:fs";
36074
+ import { join as join12 } from "node:path";
36075
+ 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;
36076
+ 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;
36077
+ function isValidHostname(host) {
36078
+ return host.length <= 253 && HOSTNAME_RE.test(host);
36079
+ }
36080
+ async function localResolve(host) {
36081
+ try {
36082
+ return await resolve4(host);
36083
+ } catch {
36084
+ return [];
36085
+ }
36086
+ }
36087
+ async function publicResolve(host) {
36088
+ const r = new Resolver;
36089
+ r.setServers(["8.8.8.8", "1.1.1.1"]);
36090
+ try {
36091
+ return await r.resolve4(host);
36092
+ } catch {
36093
+ return [];
36094
+ }
36095
+ }
36096
+ function probeBlockingPage(host, ip, timeoutMs = 8000) {
36097
+ for (const port of ["443", "80"]) {
36098
+ const scheme = port === "443" ? "https" : "http";
36099
+ try {
36100
+ const out = execFileSync("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
36101
+ const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
36102
+ if (!preview)
36103
+ continue;
36104
+ return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
36105
+ } catch {}
36106
+ }
36107
+ return { probed: false, sinkhole: false, preview: "" };
36108
+ }
36109
+ async function resolveCheck(host) {
36110
+ const clean = host.replace(/^https?:\/\//, "").split("/")[0];
36111
+ if (!isValidHostname(clean)) {
36112
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
36113
+ }
36114
+ const local = await localResolve(clean);
36115
+ const pub = await publicResolve(clean);
36116
+ if (local.length === 0 && pub.length === 0) {
36117
+ return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
36118
+ }
36119
+ if (local.length === 0) {
36120
+ 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." };
36121
+ }
36122
+ const firstLocal = local[0];
36123
+ const probe = probeBlockingPage(clean, firstLocal);
36124
+ if (probe.sinkhole) {
36125
+ return {
36126
+ host: clean,
36127
+ local_ips: local,
36128
+ public_ips: pub,
36129
+ sinkhole: true,
36130
+ verdict: "sinkhole",
36131
+ 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}/`
36132
+ };
36133
+ }
36134
+ if (!probe.probed) {
36135
+ 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." };
36136
+ }
36137
+ const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
36138
+ return {
36139
+ host: clean,
36140
+ local_ips: local,
36141
+ public_ips: pub,
36142
+ sinkhole: false,
36143
+ verdict: ipDiff ? "clean-cdn" : "clean",
36144
+ 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."
36145
+ };
36146
+ }
36147
+ function loadWatchlist() {
36148
+ const raw = readFileSync10(join12(process.cwd(), "intelligence", "watchlist.json"), "utf8");
36149
+ const data = JSON.parse(raw);
36150
+ return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
36151
+ }
36152
+ function fmtPayout(min, max) {
36153
+ if (min == null || max == null)
36154
+ return "n/a";
36155
+ const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
36156
+ return `${f(min)}–${f(max)}`;
36157
+ }
36158
+ function watchlist() {
36159
+ const { cwe_frequency, cve_watchlist } = loadWatchlist();
36160
+ const entries = cwe_frequency.map((e) => {
36161
+ const pat = e.pattern ? patternLookup(e.pattern) : null;
36162
+ const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
36163
+ const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
36164
+ return {
36165
+ cwe: e.cwe,
36166
+ name: e.name,
36167
+ rank: e.rank,
36168
+ saturated: e.saturated,
36169
+ pattern: e.pattern ?? null,
36170
+ payout_min: payoutMin,
36171
+ payout_max: payoutMax,
36172
+ typical_payout: fmtPayout(payoutMin, payoutMax),
36173
+ notes: e.notes,
36174
+ grounded: pat != null
36175
+ };
36176
+ }).sort((a, b) => a.rank - b.rank);
36177
+ const unsaturated = entries.filter((e) => !e.saturated);
36178
+ const topCve = cve_watchlist.filter((c) => (c.epss ?? 0) >= 0.8 || (c.reports ?? 0) >= 100).slice(0, 4).map((c) => c.cve).join(", ");
36179
+ 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}.`;
36180
+ return { cwe_frequency: entries, cve_watchlist, recommendation };
36181
+ }
36182
+ function watchlistCveMatches(techs, headers = {}, body = "") {
36183
+ const { cve_watchlist } = loadWatchlist();
36184
+ const haystack = `${techs.join(" ")} ${Object.keys(headers).join(" ")} ${Object.values(headers).join(" ")} ${body}`.toLowerCase();
36185
+ return cve_watchlist.filter((c) => {
36186
+ const m = c.match;
36187
+ if (!m)
36188
+ return false;
36189
+ try {
36190
+ return new RegExp(m, "i").test(haystack);
36191
+ } catch {
36192
+ return false;
36193
+ }
36194
+ });
36195
+ }
36196
+
36070
36197
  // src/live-recon.ts
36071
- import { resolve4 } from "node:dns/promises";
36198
+ import { resolve4 as resolve42 } from "node:dns/promises";
36072
36199
  import { connect } from "node:net";
36073
36200
  var HTTP_TIMEOUT_MS = 20000;
36074
36201
  function isLikelyCdn(ip) {
@@ -36284,7 +36411,7 @@ async function dnsBrute(domain) {
36284
36411
  const hosts = COMMON_SUBDOMAINS.map((p) => `${p}.${base}`).filter((h) => !seen.has(h));
36285
36412
  const settled = await Promise.allSettled(hosts.map(async (host) => {
36286
36413
  try {
36287
- const ips = await resolve4(host);
36414
+ const ips = await resolve42(host);
36288
36415
  return ips.length > 0 ? { host, ips } : null;
36289
36416
  } catch {
36290
36417
  return null;
@@ -36501,6 +36628,7 @@ async function liveRecon(target, includeActive = false) {
36501
36628
  const waf = detectWaf(root.headers, root.body);
36502
36629
  const techs = detectTech(root.headers, root.body);
36503
36630
  const versions = extractVersion(root.headers, root.body);
36631
+ const suggestedCves = watchlistCveMatches(techs, root.headers, root.body);
36504
36632
  const { links, scripts } = crawlLinks(base, root.body);
36505
36633
  const { robots, sitemap } = await fetchRobotsAndSitemap(base);
36506
36634
  const allUrls = [...links, ...scripts, ...robots, ...sitemap];
@@ -36534,6 +36662,9 @@ async function liveRecon(target, includeActive = false) {
36534
36662
  for (const v of versions) {
36535
36663
  nextSteps.push(`Version ${v.product}/${v.version} detected — check cve_correlation + nvd_lookup for known CVEs affecting ${v.product} ${v.version}.`);
36536
36664
  }
36665
+ if (suggestedCves.length > 0) {
36666
+ 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("; ")}.`);
36667
+ }
36537
36668
  if (crawl.endpoints.length > 0)
36538
36669
  nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
36539
36670
  if (params.length > 0)
@@ -36560,6 +36691,7 @@ async function liveRecon(target, includeActive = false) {
36560
36691
  waf,
36561
36692
  tech: { detected: techs, server: fingerprint.server },
36562
36693
  versions,
36694
+ suggested_cves: suggestedCves.map((c) => ({ cve: c.cve, name: c.name, product: c.product, epss: c.epss, reports: c.reports })),
36563
36695
  crawl,
36564
36696
  params,
36565
36697
  subdomains,
@@ -36636,9 +36768,9 @@ function loadChains() {
36636
36768
  if (_chainsCache)
36637
36769
  return _chainsCache;
36638
36770
  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"));
36771
+ const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
36772
+ const path = join13(dir, "chains.json");
36773
+ const raw = JSON.parse(readFileSync11(path, "utf8"));
36642
36774
  _chainsCache = raw.chains ?? [];
36643
36775
  return _chainsCache;
36644
36776
  } catch {
@@ -37419,8 +37551,8 @@ var CHAIN_ID_TO_ESCALATION = {
37419
37551
  };
37420
37552
  function loadChainLinks() {
37421
37553
  try {
37422
- const dir = join12(fileURLToPath6(new URL(".", import.meta.url)), "..");
37423
- const data = JSON.parse(readFileSync10(join12(dir, "intelligence", "chain_links.json"), "utf8"));
37554
+ const dir = join13(fileURLToPath6(new URL(".", import.meta.url)), "..");
37555
+ const data = JSON.parse(readFileSync11(join13(dir, "intelligence", "chain_links.json"), "utf8"));
37424
37556
  return data.links ?? [];
37425
37557
  } catch {
37426
37558
  return [];
@@ -37491,7 +37623,7 @@ async function runAutonomous(target, scope = "", mode = "bug-bounty", maxPasses
37491
37623
  }
37492
37624
 
37493
37625
  // src/ops.ts
37494
- import { execSync, execFileSync } from "node:child_process";
37626
+ import { execSync, execFileSync as execFileSync2 } from "node:child_process";
37495
37627
  function decomposeBatches(tasks) {
37496
37628
  const remaining = [...tasks];
37497
37629
  const done = new Set;
@@ -37536,13 +37668,13 @@ function worktree(action, path, branch) {
37536
37668
  if (!path)
37537
37669
  return { action, ok: false, error: "path is required for worktree add" };
37538
37670
  const b = branch ?? `blitz-${Date.now().toString(36)}`;
37539
- const out = execFileSync("git", ["worktree", "add", "-b", b, path], { encoding: "utf8" }).trim();
37671
+ const out = execFileSync2("git", ["worktree", "add", "-b", b, path], { encoding: "utf8" }).trim();
37540
37672
  return { action, ok: true, branch: b, path, out };
37541
37673
  }
37542
37674
  if (action === "remove") {
37543
37675
  if (!path)
37544
37676
  return { action, ok: false, error: "path is required for worktree remove" };
37545
- execFileSync("git", ["worktree", "remove", path, "--force"], { encoding: "utf8" });
37677
+ execFileSync2("git", ["worktree", "remove", path, "--force"], { encoding: "utf8" });
37546
37678
  return { action, ok: true, path };
37547
37679
  }
37548
37680
  return { action, ok: false, error: "unknown action (use add | list | remove)" };
@@ -37576,18 +37708,18 @@ function compactState(phase, findings) {
37576
37708
  }
37577
37709
 
37578
37710
  // 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";
37711
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "node:fs";
37712
+ import { join as join14 } from "node:path";
37581
37713
  import { fileURLToPath as fileURLToPath7 } from "node:url";
37582
37714
  function tasksDir() {
37583
- return join13(fileURLToPath7(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37715
+ return join14(fileURLToPath7(new URL(".", import.meta.url)), "..", ".blitzstrike", "tasks");
37584
37716
  }
37585
37717
  function taskPath(id) {
37586
- return join13(tasksDir(), `${id}.json`);
37718
+ return join14(tasksDir(), `${id}.json`);
37587
37719
  }
37588
37720
  function load2(id) {
37589
37721
  try {
37590
- return JSON.parse(readFileSync11(taskPath(id), "utf8"));
37722
+ return JSON.parse(readFileSync12(taskPath(id), "utf8"));
37591
37723
  } catch {
37592
37724
  return null;
37593
37725
  }
@@ -37925,18 +38057,18 @@ function scaffoldEngagement(target, scope = [], out_of_scope = []) {
37925
38057
  }
37926
38058
 
37927
38059
  // src/incremental.ts
37928
- import { execFileSync as execFileSync2 } from "node:child_process";
38060
+ import { execFileSync as execFileSync3 } from "node:child_process";
37929
38061
  import { writeFileSync as writeFileSync4, mkdtempSync, rmSync } from "node:fs";
37930
- import { join as join14 } from "node:path";
38062
+ import { join as join15 } from "node:path";
37931
38063
  import { tmpdir } from "node:os";
37932
38064
 
37933
38065
  // src/eagle2.ts
37934
38066
  var import_php_parser2 = __toESM(require_src(), 1);
37935
- import { readFileSync as readFileSync14 } from "node:fs";
38067
+ import { readFileSync as readFileSync15 } from "node:fs";
37936
38068
 
37937
38069
  // src/dataflow.ts
37938
38070
  init_scanner();
37939
- import { readFileSync as readFileSync12 } from "node:fs";
38071
+ import { readFileSync as readFileSync13 } from "node:fs";
37940
38072
  var SOURCES = {
37941
38073
  $_GET: { id: "http_get", label: "HTTP GET parameter", attacker_controlled: true },
37942
38074
  $_POST: { id: "http_post", label: "HTTP POST body", attacker_controlled: true },
@@ -38021,7 +38153,7 @@ function traceDataFlow(path) {
38021
38153
  return result;
38022
38154
  let text;
38023
38155
  try {
38024
- text = readFileSync12(path, "utf8");
38156
+ text = readFileSync13(path, "utf8");
38025
38157
  } catch {
38026
38158
  return result;
38027
38159
  }
@@ -38102,7 +38234,7 @@ init_scanner();
38102
38234
 
38103
38235
  // src/callgraph.ts
38104
38236
  var import_php_parser = __toESM(require_src(), 1);
38105
- import { readFileSync as readFileSync13 } from "node:fs";
38237
+ import { readFileSync as readFileSync14 } from "node:fs";
38106
38238
  var parser = new import_php_parser.Engine({
38107
38239
  parser: { extractDoc: false, php7: true },
38108
38240
  ast: { withPositions: true, withSource: false }
@@ -38163,7 +38295,7 @@ function buildCallGraph(path) {
38163
38295
  const g = { nodes: new Map, edges: [] };
38164
38296
  let code;
38165
38297
  try {
38166
- code = readFileSync13(path, "utf8");
38298
+ code = readFileSync14(path, "utf8");
38167
38299
  } catch {
38168
38300
  return g;
38169
38301
  }
@@ -38825,7 +38957,7 @@ function resolveFuncId(name, summaries) {
38825
38957
  function analyzeDataFlow2(path) {
38826
38958
  let cacheCode = "";
38827
38959
  try {
38828
- cacheCode = readFileSync14(path, "utf8");
38960
+ cacheCode = readFileSync15(path, "utf8");
38829
38961
  } catch {}
38830
38962
  if (cacheCode) {
38831
38963
  const cached = getCache("eagle2", cacheCode);
@@ -38847,7 +38979,7 @@ function analyzeDataFlow2(path) {
38847
38979
  };
38848
38980
  let code;
38849
38981
  try {
38850
- code = cacheCode || readFileSync14(path, "utf8");
38982
+ code = cacheCode || readFileSync15(path, "utf8");
38851
38983
  } catch {
38852
38984
  return result;
38853
38985
  }
@@ -57518,7 +57650,7 @@ registerLanguage(rustAdapter);
57518
57650
  var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
57519
57651
  function git(repo, args) {
57520
57652
  try {
57521
- return execFileSync2("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
57653
+ return execFileSync3("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
57522
57654
  } catch {
57523
57655
  return "";
57524
57656
  }
@@ -57533,8 +57665,8 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57533
57665
  continue;
57534
57666
  const lang = detectLanguage(file)?.language;
57535
57667
  if (lang === "php") {
57536
- const dir = mkdtempSync(join14(tmpdir(), "blitz-incr-"));
57537
- const tmp = join14(dir, "changed.php");
57668
+ const dir = mkdtempSync(join15(tmpdir(), "blitz-incr-"));
57669
+ const tmp = join15(dir, "changed.php");
57538
57670
  writeFileSync4(tmp, code);
57539
57671
  try {
57540
57672
  const r = analyzeDataFlow2(tmp);
@@ -57555,7 +57687,7 @@ function analyzeChangedFiles(repo, base = "HEAD~1", head = "HEAD") {
57555
57687
  // src/taint.ts
57556
57688
  init_scanner();
57557
57689
  var import_php_parser4 = __toESM(require_src(), 1);
57558
- import { readFileSync as readFileSync15 } from "node:fs";
57690
+ import { readFileSync as readFileSync16 } from "node:fs";
57559
57691
  var parser4 = new import_php_parser4.Engine({
57560
57692
  parser: { extractDoc: false, php7: true },
57561
57693
  ast: { withPositions: true, withSource: false }
@@ -57853,7 +57985,7 @@ function analyzeTaint(path) {
57853
57985
  const result = { file: path, findings: [], suppressed: 0 };
57854
57986
  let code;
57855
57987
  try {
57856
- code = readFileSync15(path, "utf8");
57988
+ code = readFileSync16(path, "utf8");
57857
57989
  } catch {
57858
57990
  return result;
57859
57991
  }
@@ -57947,15 +58079,15 @@ function taintTree(root, maxFiles = 2000) {
57947
58079
  }
57948
58080
 
57949
58081
  // 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";
58082
+ import { writeFileSync as writeFileSync5, rmSync as rmSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17 } from "node:fs";
58083
+ import { join as join16 } from "node:path";
57952
58084
  import { tmpdir as tmpdir2 } from "node:os";
57953
- import { execFileSync as execFileSync3 } from "node:child_process";
58085
+ import { execFileSync as execFileSync4 } from "node:child_process";
57954
58086
  init_scanner();
57955
58087
  function runEngine(code, language, file) {
57956
58088
  if (language === "php") {
57957
- const dir = mkdtempSync2(join15(tmpdir2(), "blitz-diff-"));
57958
- const tmp = join15(dir, "old.php");
58089
+ const dir = mkdtempSync2(join16(tmpdir2(), "blitz-diff-"));
58090
+ const tmp = join16(dir, "old.php");
57959
58091
  writeFileSync5(tmp, code);
57960
58092
  try {
57961
58093
  const r = analyzeDataFlow2(tmp);
@@ -58109,8 +58241,8 @@ function languageOf(file) {
58109
58241
  return adapter?.language ?? "php";
58110
58242
  }
58111
58243
  function analyzeDifferentialFiles(oldPath, newPath, language) {
58112
- const oldCode = readFileSync16(oldPath, "utf8");
58113
- const newCode = readFileSync16(newPath, "utf8");
58244
+ const oldCode = readFileSync17(oldPath, "utf8");
58245
+ const newCode = readFileSync17(newPath, "utf8");
58114
58246
  const lang = language ?? languageOf(newPath);
58115
58247
  return analyzeDifferential(oldCode, newCode, lang, newPath);
58116
58248
  }
@@ -58118,7 +58250,7 @@ var SOURCE_EXTS2 = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".p
58118
58250
  function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
58119
58251
  const git = (args) => {
58120
58252
  try {
58121
- return execFileSync3("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
58253
+ return execFileSync4("git", args.split(/\s+/).filter(Boolean), { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
58122
58254
  } catch {
58123
58255
  return "";
58124
58256
  }
@@ -58237,27 +58369,27 @@ function analyzePatch(oldCode, newCode, language = "php") {
58237
58369
  };
58238
58370
  }
58239
58371
  function analyzePatchFiles(oldPath, newPath, language) {
58240
- const oldCode = readFileSync16(oldPath, "utf8");
58241
- const newCode = readFileSync16(newPath, "utf8");
58372
+ const oldCode = readFileSync17(oldPath, "utf8");
58373
+ const newCode = readFileSync17(newPath, "utf8");
58242
58374
  const lang = language ?? languageOf(newPath);
58243
58375
  return analyzePatch(oldCode, newCode, lang);
58244
58376
  }
58245
58377
 
58246
58378
  // src/frameworks.ts
58247
- import { readFileSync as readFileSync17, existsSync as existsSync9 } from "node:fs";
58248
- import { join as join16 } from "node:path";
58379
+ import { readFileSync as readFileSync18, existsSync as existsSync9 } from "node:fs";
58380
+ import { join as join17 } from "node:path";
58249
58381
  import { fileURLToPath as fileURLToPath8 } from "node:url";
58250
- var ROOT4 = join16(fileURLToPath8(new URL(".", import.meta.url)), "..");
58251
- var INTEL2 = join16(ROOT4, "intelligence");
58382
+ var ROOT4 = join17(fileURLToPath8(new URL(".", import.meta.url)), "..");
58383
+ var INTEL2 = join17(ROOT4, "intelligence");
58252
58384
  var cache = null;
58253
58385
  function load3() {
58254
58386
  if (cache)
58255
58387
  return cache;
58256
- const p = join16(INTEL2, "frameworks.json");
58388
+ const p = join17(INTEL2, "frameworks.json");
58257
58389
  if (!existsSync9(p))
58258
58390
  return null;
58259
58391
  try {
58260
- cache = JSON.parse(readFileSync17(p, "utf8"));
58392
+ cache = JSON.parse(readFileSync18(p, "utf8"));
58261
58393
  return cache;
58262
58394
  } catch {
58263
58395
  return null;
@@ -58767,9 +58899,9 @@ function nextSteps(ctx) {
58767
58899
 
58768
58900
  // src/git-history.ts
58769
58901
  init_complex_bugs();
58770
- import { execFileSync as execFileSync4 } from "node:child_process";
58902
+ import { execFileSync as execFileSync5 } from "node:child_process";
58771
58903
  import { existsSync as existsSync11 } from "node:fs";
58772
- import { join as join17 } from "node:path";
58904
+ import { join as join18 } from "node:path";
58773
58905
  function parseDeletions(output) {
58774
58906
  const out = [];
58775
58907
  let commit = "";
@@ -58790,7 +58922,7 @@ function parseDeletions(output) {
58790
58922
  return out;
58791
58923
  }
58792
58924
  function blobBeforeDeletion(root, commit, path) {
58793
- return execFileSync4("git", ["-C", root, "show", `${commit}^:${path}`], {
58925
+ return execFileSync5("git", ["-C", root, "show", `${commit}^:${path}`], {
58794
58926
  encoding: "utf8",
58795
58927
  maxBuffer: 10 * 1024 * 1024
58796
58928
  });
@@ -58798,12 +58930,12 @@ function blobBeforeDeletion(root, commit, path) {
58798
58930
  function scanGitHistory(root, opts) {
58799
58931
  const limit = opts?.limit ?? 30;
58800
58932
  const findings = [];
58801
- if (!existsSync11(join17(root, ".git"))) {
58933
+ if (!existsSync11(join18(root, ".git"))) {
58802
58934
  return { root, is_git_repo: false, total: 0, findings: [], error: "not a git repository (no .git dir)" };
58803
58935
  }
58804
58936
  if (opts?.secretPattern) {
58805
58937
  try {
58806
- const pick = execFileSync4("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
58938
+ const pick = execFileSync5("git", ["-C", root, "log", "-S", opts.secretPattern, "--all", "--format=__COMMIT__ %H %s"], { encoding: "utf8", maxBuffer: 5 * 1024 * 1024 });
58807
58939
  const commits = pick.split(`
58808
58940
  `).filter(Boolean).map((l) => {
58809
58941
  const rest = l.startsWith("__COMMIT__ ") ? l.slice("__COMMIT__ ".length) : l;
@@ -58824,7 +58956,7 @@ function scanGitHistory(root, opts) {
58824
58956
  }
58825
58957
  }
58826
58958
  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 });
58959
+ 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
58960
  const deletions = parseDeletions(log);
58829
58961
  const seen = new Set;
58830
58962
  let checked = 0;
@@ -58945,13 +59077,13 @@ async function validateLeakedKey(input) {
58945
59077
  }
58946
59078
 
58947
59079
  // src/hackbot-arena.ts
58948
- import { readFileSync as readFileSync18 } from "node:fs";
58949
- import { join as join18, dirname as dirname3 } from "node:path";
59080
+ import { readFileSync as readFileSync19 } from "node:fs";
59081
+ import { join as join19, dirname as dirname3 } from "node:path";
58950
59082
  import { fileURLToPath as fileURLToPath9 } from "node:url";
58951
59083
  var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
58952
59084
  function load4() {
58953
59085
  try {
58954
- return JSON.parse(readFileSync18(join18(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
59086
+ return JSON.parse(readFileSync19(join19(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
58955
59087
  } catch {
58956
59088
  return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
58957
59089
  }
@@ -59113,13 +59245,13 @@ function listSeverityMap() {
59113
59245
  }
59114
59246
 
59115
59247
  // src/datarefresh.ts
59116
- import { readFileSync as readFileSync19 } from "node:fs";
59117
- import { join as join19, dirname as dirname4 } from "node:path";
59248
+ import { readFileSync as readFileSync20 } from "node:fs";
59249
+ import { join as join20, dirname as dirname4 } from "node:path";
59118
59250
  import { fileURLToPath as fileURLToPath10 } from "node:url";
59119
59251
  var __dirname4 = dirname4(fileURLToPath10(import.meta.url));
59120
59252
  function loadJson2(rel) {
59121
59253
  try {
59122
- return JSON.parse(readFileSync19(join19(__dirname4, "..", rel), "utf8"));
59254
+ return JSON.parse(readFileSync20(join20(__dirname4, "..", rel), "utf8"));
59123
59255
  } catch {
59124
59256
  return null;
59125
59257
  }
@@ -59154,18 +59286,18 @@ init_verify_harness();
59154
59286
  init_variants();
59155
59287
 
59156
59288
  // 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";
59289
+ import { readFileSync as readFileSync22, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59290
+ import { join as join22 } from "node:path";
59159
59291
  import { fileURLToPath as fileURLToPath12 } from "node:url";
59160
59292
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
59161
59293
  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");
59294
+ var ROOT5 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
59295
+ var CORPUS = join22(ROOT5, "bench", "corpus");
59296
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join22(homedir8(), ".blitzstrike", "corpus");
59165
59297
  function parseCorpusFile(path) {
59166
59298
  let text;
59167
59299
  try {
59168
- text = readFileSync21(path, "utf8");
59300
+ text = readFileSync22(path, "utf8");
59169
59301
  } catch {
59170
59302
  return null;
59171
59303
  }
@@ -59221,7 +59353,7 @@ function loadDir(dir) {
59221
59353
  for (const f of readdirSync5(dir)) {
59222
59354
  if (!f.endsWith(".md"))
59223
59355
  continue;
59224
- const e = parseCorpusFile(join21(dir, f));
59356
+ const e = parseCorpusFile(join22(dir, f));
59225
59357
  if (e)
59226
59358
  entries.push(e);
59227
59359
  }
@@ -59248,7 +59380,7 @@ function captureFalsePositive(input) {
59248
59380
  input.code,
59249
59381
  "```"
59250
59382
  ].filter((l) => l !== null);
59251
- const path = join21(CAPTURED_CORPUS_DIR, `${id}.md`);
59383
+ const path = join22(CAPTURED_CORPUS_DIR, `${id}.md`);
59252
59384
  writeFileSync6(path, lines.join(`
59253
59385
  `) + `
59254
59386
  `, "utf8");
@@ -59270,7 +59402,7 @@ function detect(entry) {
59270
59402
  }
59271
59403
  const lang = entry.language;
59272
59404
  if (lang === "php") {
59273
- const tmp = join21(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59405
+ const tmp = join22(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
59274
59406
  writeFileSync6(tmp, entry.code);
59275
59407
  const r = analyzeDataFlow2(tmp);
59276
59408
  rmSync3(tmp, { force: true });
@@ -59339,12 +59471,12 @@ function runBenchmark() {
59339
59471
  }
59340
59472
 
59341
59473
  // 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";
59474
+ import { readFileSync as readFileSync23, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync12, writeFileSync as writeFileSync7 } from "node:fs";
59475
+ import { join as join23 } from "node:path";
59344
59476
  import { createHash as createHash6 } from "node:crypto";
59345
59477
  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");
59478
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join23(homedir9(), ".blitzstrike");
59479
+ var OBLIGATION_PATH = join23(HOME_DIR4, "obligations.jsonl");
59348
59480
  function ensureDir3() {
59349
59481
  if (!existsSync12(HOME_DIR4))
59350
59482
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -59357,7 +59489,7 @@ function loadObligations() {
59357
59489
  return [];
59358
59490
  try {
59359
59491
  const out = [];
59360
- for (const line of readFileSync22(OBLIGATION_PATH, "utf8").split(`
59492
+ for (const line of readFileSync23(OBLIGATION_PATH, "utf8").split(`
59361
59493
  `)) {
59362
59494
  const t = line.trim();
59363
59495
  if (!t)
@@ -59461,11 +59593,11 @@ init_framework_security();
59461
59593
  // src/variant-scan.ts
59462
59594
  init_scanner();
59463
59595
  init_complex_bugs();
59464
- import { readFileSync as readFileSync24 } from "node:fs";
59596
+ import { readFileSync as readFileSync25 } from "node:fs";
59465
59597
 
59466
59598
  // src/cross-file.ts
59467
59599
  init_scanner();
59468
- import { readFileSync as readFileSync23 } from "node:fs";
59600
+ import { readFileSync as readFileSync24 } from "node:fs";
59469
59601
  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
59602
  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
59603
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -59517,7 +59649,7 @@ function detectCrossFilePrivesc(root) {
59517
59649
  for (const file of files) {
59518
59650
  let code;
59519
59651
  try {
59520
- code = stripComments(readFileSync23(file, "utf8"));
59652
+ code = stripComments(readFileSync24(file, "utf8"));
59521
59653
  } catch {
59522
59654
  continue;
59523
59655
  }
@@ -59558,7 +59690,7 @@ function detectCrossFilePrivesc(root) {
59558
59690
  for (const file of files) {
59559
59691
  let code;
59560
59692
  try {
59561
- code = stripComments(readFileSync23(file, "utf8"));
59693
+ code = stripComments(readFileSync24(file, "utf8"));
59562
59694
  } catch {
59563
59695
  continue;
59564
59696
  }
@@ -59581,7 +59713,7 @@ function detectCrossFilePrivesc(root) {
59581
59713
  for (const file of files) {
59582
59714
  let code;
59583
59715
  try {
59584
- code = stripComments(readFileSync23(file, "utf8"));
59716
+ code = stripComments(readFileSync24(file, "utf8"));
59585
59717
  } catch {
59586
59718
  continue;
59587
59719
  }
@@ -59696,7 +59828,7 @@ function variantScan(root, opts = {}) {
59696
59828
  for (const file of files) {
59697
59829
  let code;
59698
59830
  try {
59699
- code = readFileSync24(file, "utf8");
59831
+ code = readFileSync25(file, "utf8");
59700
59832
  } catch {
59701
59833
  continue;
59702
59834
  }
@@ -60943,6 +61075,11 @@ var HUNTING_DOCTRINE = [
60943
61075
  id: "rotation",
60944
61076
  title: "20-Minute Rotation",
60945
61077
  rule: "Every 20 minutes ask 'am I making progress?'. No → rotate to the next endpoint / subdomain / class. Fresh context finds more bugs than brute force."
61078
+ },
61079
+ {
61080
+ id: "dedup_before_report",
61081
+ title: "Search for Duplicates Before Reporting",
61082
+ rule: "The #1 cause of 'Duplicate'/'N/A' rejections is not searching first. Before writing the report, search Hacktivity (and the program's prior public reports) for the same endpoint/tech/vuln class; an already-reported open S3 bucket, CORS misconfig, or XSS param is not worth a second report. Only submit what is genuinely new."
60946
61083
  }
60947
61084
  ];
60948
61085
  function huntingDoctrine() {
@@ -61371,7 +61508,7 @@ function listPocTypes() {
61371
61508
  import { execSync as execSync3 } from "node:child_process";
61372
61509
  import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, rmSync as rmSync4, existsSync as existsSync14 } from "node:fs";
61373
61510
  import { tmpdir as tmpdir4 } from "node:os";
61374
- import { join as join23 } from "node:path";
61511
+ import { join as join24 } from "node:path";
61375
61512
  function findForge() {
61376
61513
  try {
61377
61514
  const p = execSync3("which forge 2>/dev/null", { encoding: "utf8", timeout: 3000 }).trim();
@@ -61420,14 +61557,14 @@ async function foundryRun(input) {
61420
61557
  };
61421
61558
  }
61422
61559
  const poc = input.poc_code ?? foundryPoc(input.class_ ?? "missing_access_control", name);
61423
- const dir = mkdtempSync3(join23(tmpdir4(), "blitzstrike-foundry-"));
61560
+ const dir = mkdtempSync3(join24(tmpdir4(), "blitzstrike-foundry-"));
61424
61561
  try {
61425
61562
  try {
61426
61563
  execSync3(`${forge} init --force --no-git ${dir}`, { stdio: "ignore", timeout: 60000 });
61427
61564
  } catch {
61428
- mkdirSync9(join23(dir, "src"), { recursive: true });
61429
- mkdirSync9(join23(dir, "test"), { recursive: true });
61430
- writeFileSync8(join23(dir, "foundry.toml"), `[profile.default]
61565
+ mkdirSync9(join24(dir, "src"), { recursive: true });
61566
+ mkdirSync9(join24(dir, "test"), { recursive: true });
61567
+ writeFileSync8(join24(dir, "foundry.toml"), `[profile.default]
61431
61568
  src = "src"
61432
61569
  out = "out"
61433
61570
  libs = ["lib"]
@@ -61437,12 +61574,12 @@ solc = "0.8.20"
61437
61574
  for (const f of ["Counter.sol", "Counter.t.sol", "Counter.s.sol"]) {
61438
61575
  for (const sub of ["src", "test", "script"]) {
61439
61576
  try {
61440
- rmSync4(join23(dir, sub, f), { force: true });
61577
+ rmSync4(join24(dir, sub, f), { force: true });
61441
61578
  } catch {}
61442
61579
  }
61443
61580
  }
61444
- writeFileSync8(join23(dir, "src", `${name}.sol`), input.contract_code);
61445
- writeFileSync8(join23(dir, "test", `${name}.t.sol`), poc);
61581
+ writeFileSync8(join24(dir, "src", `${name}.sol`), input.contract_code);
61582
+ writeFileSync8(join24(dir, "test", `${name}.t.sol`), poc);
61446
61583
  const { out } = runForge(forge, dir, input.timeout_ms ?? 60000);
61447
61584
  const tests = parseTestResults(out);
61448
61585
  const anyFail = tests.some((t) => t.status === "fail");
@@ -61459,118 +61596,6 @@ solc = "0.8.20"
61459
61596
  }
61460
61597
  }
61461
61598
 
61462
- // src/hunting-intel.ts
61463
- import { execFileSync as execFileSync5 } from "node:child_process";
61464
- import { resolve4 as resolve42, Resolver } from "node:dns/promises";
61465
- import { readFileSync as readFileSync25 } from "node:fs";
61466
- import { join as join24 } from "node:path";
61467
- 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;
61468
- 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;
61469
- function isValidHostname(host) {
61470
- return host.length <= 253 && HOSTNAME_RE.test(host);
61471
- }
61472
- async function localResolve(host) {
61473
- try {
61474
- return await resolve42(host);
61475
- } catch {
61476
- return [];
61477
- }
61478
- }
61479
- async function publicResolve(host) {
61480
- const r = new Resolver;
61481
- r.setServers(["8.8.8.8", "1.1.1.1"]);
61482
- try {
61483
- return await r.resolve4(host);
61484
- } catch {
61485
- return [];
61486
- }
61487
- }
61488
- function probeBlockingPage(host, ip, timeoutMs = 8000) {
61489
- for (const port of ["443", "80"]) {
61490
- const scheme = port === "443" ? "https" : "http";
61491
- try {
61492
- const out = execFileSync5("curl", ["-sk", "-m", "6", "--resolve", `${host}:${port}:${ip}`, `${scheme}://${host}/`], { encoding: "utf8", timeout: timeoutMs, stdio: ["ignore", "pipe", "ignore"] });
61493
- const preview = out.replace(/\s+/g, " ").trim().slice(0, 300);
61494
- if (!preview)
61495
- continue;
61496
- return { probed: true, sinkhole: BLOCKING_KEYWORDS.test(preview), preview };
61497
- } catch {}
61498
- }
61499
- return { probed: false, sinkhole: false, preview: "" };
61500
- }
61501
- async function resolveCheck(host) {
61502
- const clean = host.replace(/^https?:\/\//, "").split("/")[0];
61503
- if (!isValidHostname(clean)) {
61504
- return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "invalid", evidence: "not a valid hostname — rejected before DNS/subprocess" };
61505
- }
61506
- const local = await localResolve(clean);
61507
- const pub = await publicResolve(clean);
61508
- if (local.length === 0 && pub.length === 0) {
61509
- return { host: clean, local_ips: [], public_ips: [], sinkhole: false, verdict: "unresolvable", evidence: "no A record from local or public DNS" };
61510
- }
61511
- if (local.length === 0) {
61512
- 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." };
61513
- }
61514
- const firstLocal = local[0];
61515
- const probe = probeBlockingPage(clean, firstLocal);
61516
- if (probe.sinkhole) {
61517
- return {
61518
- host: clean,
61519
- local_ips: local,
61520
- public_ips: pub,
61521
- sinkhole: true,
61522
- verdict: "sinkhole",
61523
- 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}/`
61524
- };
61525
- }
61526
- if (!probe.probed) {
61527
- 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." };
61528
- }
61529
- const ipDiff = new Set(local).size !== new Set(pub).size || !pub.every((ip) => local.includes(ip));
61530
- return {
61531
- host: clean,
61532
- local_ips: local,
61533
- public_ips: pub,
61534
- sinkhole: false,
61535
- verdict: ipDiff ? "clean-cdn" : "clean",
61536
- 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."
61537
- };
61538
- }
61539
- function loadWatchlist() {
61540
- const raw = readFileSync25(join24(process.cwd(), "intelligence", "watchlist.json"), "utf8");
61541
- const data = JSON.parse(raw);
61542
- return { cwe_frequency: data.cwe_frequency ?? [], cve_watchlist: data.cve_watchlist ?? [] };
61543
- }
61544
- function fmtPayout(min, max) {
61545
- if (min == null || max == null)
61546
- return "n/a";
61547
- const f = (n) => n >= 1000 ? `$${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K` : `$${n}`;
61548
- return `${f(min)}–${f(max)}`;
61549
- }
61550
- function watchlist() {
61551
- const { cwe_frequency, cve_watchlist } = loadWatchlist();
61552
- const entries = cwe_frequency.map((e) => {
61553
- const pat = e.pattern ? patternLookup(e.pattern) : null;
61554
- const payoutMin = e.payout_min ?? pat?.payout_min ?? null;
61555
- const payoutMax = e.payout_max ?? pat?.payout_max ?? null;
61556
- return {
61557
- cwe: e.cwe,
61558
- name: e.name,
61559
- rank: e.rank,
61560
- saturated: e.saturated,
61561
- pattern: e.pattern ?? null,
61562
- payout_min: payoutMin,
61563
- payout_max: payoutMax,
61564
- typical_payout: fmtPayout(payoutMin, payoutMax),
61565
- notes: e.notes,
61566
- grounded: pat != null
61567
- };
61568
- }).sort((a, b) => a.rank - b.rank);
61569
- const unsaturated = entries.filter((e) => !e.saturated);
61570
- 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.`;
61571
- return { cwe_frequency: entries, cve_watchlist, recommendation };
61572
- }
61573
-
61574
61599
  // src/enterprise-benchmark.ts
61575
61600
  import { existsSync as existsSync15, readFileSync as readFileSync26, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
61576
61601
  import { join as join25, resolve } from "node:path";