blitzstrike 2.4.70 → 2.4.72

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
@@ -16265,6 +16265,8 @@ function detectComplexBugs(code, file) {
16265
16265
  detectGrpcReflection(code, file, out);
16266
16266
  detectDependencyConfusion(code, file, out);
16267
16267
  detectMlSupplyChain(code, file, out);
16268
+ detectRustUnsafe(code, file, out);
16269
+ detectRustFormatString(code, file, out);
16268
16270
  out.push(...detectWrongSanitizer(code, file));
16269
16271
  out.push(...detectMissingAuthz(code, file));
16270
16272
  const seen = new Set;
@@ -16753,6 +16755,46 @@ function detectMlSupplyChain(code, file, out) {
16753
16755
  }
16754
16756
  }
16755
16757
  }
16758
+ function detectRustUnsafe(code, file, out) {
16759
+ if (!/\.rs$/i.test(file))
16760
+ return;
16761
+ const ls = lines2(code);
16762
+ const clean = stripCommentsOnly(code);
16763
+ const re = /std::mem::transmute|\btransmute\s*\(|\bassume_init\s*\(|from_raw_parts(?:_mut)?\s*\(|ptr::(copy|copy_nonoverlapping|read|write|swap)\s*\(|from_utf8_unchecked\s*\(|\bunreachable_unchecked\s*\(/g;
16764
+ let m;
16765
+ while ((m = re.exec(clean)) !== null) {
16766
+ const ln = lineNo2(code, m.index);
16767
+ out.push({
16768
+ file,
16769
+ line: ln,
16770
+ type: "rust_unsafe",
16771
+ category: "Rust unsafe / memory-unsafety surface",
16772
+ severity: "medium",
16773
+ evidence: (ls[ln - 1] ?? "").trim(),
16774
+ detail: `\`${m[0]}\` bypasses the borrow checker's memory-safety guarantees — type confusion (transmute), uninitialized reads (assume_init), or unchecked pointer/slice construction (from_raw_parts / ptr::*) can lead to out-of-bounds access, use-after-free, or UB (CWE-119/787/416). Verify the input is bounds-checked and no attacker-controlled length/index reaches it.`
16775
+ });
16776
+ }
16777
+ }
16778
+ function detectRustFormatString(code, file, out) {
16779
+ if (!/\.rs$/i.test(file))
16780
+ return;
16781
+ const ls = lines2(code);
16782
+ const clean = stripCommentsOnly(code);
16783
+ const re = /\b(format!|println!|print!|eprintln!|eprint!)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
16784
+ let m;
16785
+ while ((m = re.exec(clean)) !== null) {
16786
+ const ln = lineNo2(code, m.index);
16787
+ out.push({
16788
+ file,
16789
+ line: ln,
16790
+ type: "rust_format_string",
16791
+ category: "Rust format string injection",
16792
+ severity: "high",
16793
+ evidence: (ls[ln - 1] ?? "").trim(),
16794
+ detail: `\`${m[1]}\` passes a variable (\`${m[2]}\`) as the FORMAT string instead of a literal — if attacker-controlled, format specifiers (positional args, width, precision) can read or corrupt memory (CWE-134). Use \`${m[1]}!("{}", value)\` with the input as an argument, never as the format string.`
16795
+ });
16796
+ }
16797
+ }
16756
16798
  var SOURCE_TOKENS3, MAGIC_METHODS, SECRET_FIELD_RE, SECRET_PLACEHOLDER_RE, ENTROPY_EXCLUDE_VAR;
16757
16799
  var init_complex_bugs = __esm(() => {
16758
16800
  init_security_state();
@@ -16948,17 +16990,31 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
16948
16990
  file: "train.py",
16949
16991
  positive: `import torch\\nmodel = torch.load(downloaded_model)\\n`,
16950
16992
  negative: `import torch\\nmodel = torch.load("model.pt", weights_only=True)\\n`
16993
+ },
16994
+ {
16995
+ id: "rust-unsafe",
16996
+ detector: "rust_unsafe",
16997
+ file: "lib.rs",
16998
+ positive: `fn f() { let x: &[u8] = std::mem::transmute(user_data); }\\n`,
16999
+ negative: `fn f() { let x: &[u8] = user_data.as_bytes(); }\\n`
17000
+ },
17001
+ {
17002
+ id: "rust-format-string",
17003
+ detector: "rust_format_string",
17004
+ file: "main.rs",
17005
+ positive: `fn f() { println!(user_input); }\\n`,
17006
+ negative: `fn f() { println!("{}", user_input); }\\n`
16951
17007
  }
16952
17008
  ];
16953
17009
  });
16954
17010
 
16955
17011
  // src/variants.ts
16956
- import { readFileSync as readFileSync19 } from "node:fs";
16957
- import { join as join19, dirname as dirname4 } from "node:path";
16958
- import { fileURLToPath as fileURLToPath10 } from "node:url";
17012
+ import { readFileSync as readFileSync20 } from "node:fs";
17013
+ import { join as join20, dirname as dirname5 } from "node:path";
17014
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
16959
17015
  function loadVariants() {
16960
17016
  try {
16961
- const doc = JSON.parse(readFileSync19(join19(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
17017
+ const doc = JSON.parse(readFileSync20(join20(__dirname5, "..", "intelligence", "variants.json"), "utf8"));
16962
17018
  return doc.variants ?? [];
16963
17019
  } catch {
16964
17020
  return [];
@@ -17009,18 +17065,18 @@ function variantCoverageText() {
17009
17065
  return lines.join(`
17010
17066
  `);
17011
17067
  }
17012
- var __dirname4, CROSS_FILE_ONLY;
17068
+ var __dirname5, CROSS_FILE_ONLY;
17013
17069
  var init_variants = __esm(() => {
17014
17070
  init_complex_bugs();
17015
- __dirname4 = dirname4(fileURLToPath10(import.meta.url));
17071
+ __dirname5 = dirname5(fileURLToPath11(import.meta.url));
17016
17072
  CROSS_FILE_ONLY = new Set(["priv_esc"]);
17017
17073
  });
17018
17074
 
17019
17075
  // src/sync.ts
17020
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync25, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
17021
- import { join as join23 } from "node:path";
17076
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync26, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
17077
+ import { join as join24 } from "node:path";
17022
17078
  import { homedir as homedir10 } from "node:os";
17023
- import { fileURLToPath as fileURLToPath12 } from "node:url";
17079
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
17024
17080
  import { spawnSync as spawnSync4 } from "node:child_process";
17025
17081
  function dirExists(p) {
17026
17082
  try {
@@ -17035,7 +17091,7 @@ function countFiles(dir) {
17035
17091
  const walk = (d) => {
17036
17092
  for (const e of readdirSync7(d, { withFileTypes: true })) {
17037
17093
  if (e.isDirectory())
17038
- walk(join23(d, e.name));
17094
+ walk(join24(d, e.name));
17039
17095
  else
17040
17096
  n++;
17041
17097
  }
@@ -17049,8 +17105,8 @@ function countFiles(dir) {
17049
17105
  function syncData() {
17050
17106
  if (!dirExists(DATA_ROOT2))
17051
17107
  mkdirSync9(DATA_ROOT2, { recursive: true });
17052
- const payloadsPath = join23(DATA_ROOT2, "payloads");
17053
- const templatesPath = join23(DATA_ROOT2, "templates");
17108
+ const payloadsPath = join24(DATA_ROOT2, "payloads");
17109
+ const templatesPath = join24(DATA_ROOT2, "templates");
17054
17110
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
17055
17111
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
17056
17112
  if (hasPayloads && hasTemplates) {
@@ -17060,8 +17116,8 @@ function syncData() {
17060
17116
  data_root: DATA_ROOT2
17061
17117
  };
17062
17118
  }
17063
- const localPayloads = join23(ROOT7, "payloads");
17064
- const localTemplates = join23(ROOT7, "templates");
17119
+ const localPayloads = join24(ROOT7, "payloads");
17120
+ const localTemplates = join24(ROOT7, "templates");
17065
17121
  let payloadsSynced = false;
17066
17122
  let templatesSynced = false;
17067
17123
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -17074,7 +17130,7 @@ function syncData() {
17074
17130
  }
17075
17131
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
17076
17132
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
17077
- const tmp = join23(DATA_ROOT2, ".tmp-clone");
17133
+ const tmp = join24(DATA_ROOT2, ".tmp-clone");
17078
17134
  if (dirExists(tmp))
17079
17135
  rmSync4(tmp, { recursive: true, force: true });
17080
17136
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -17082,11 +17138,11 @@ function syncData() {
17082
17138
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
17083
17139
  if (sparse.status === 0) {
17084
17140
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
17085
- copyDir(join23(tmp, "payloads"), payloadsPath);
17141
+ copyDir(join24(tmp, "payloads"), payloadsPath);
17086
17142
  payloadsSynced = true;
17087
17143
  }
17088
17144
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
17089
- copyDir(join23(tmp, "templates"), templatesPath);
17145
+ copyDir(join24(tmp, "templates"), templatesPath);
17090
17146
  templatesSynced = true;
17091
17147
  }
17092
17148
  }
@@ -17108,18 +17164,18 @@ function copyDir(src, dst) {
17108
17164
  function manualCopy(src, dst) {
17109
17165
  mkdirSync9(dst, { recursive: true });
17110
17166
  for (const e of readdirSync7(src, { withFileTypes: true })) {
17111
- const s = join23(src, e.name);
17112
- const d = join23(dst, e.name);
17167
+ const s = join24(src, e.name);
17168
+ const d = join24(dst, e.name);
17113
17169
  if (e.isDirectory())
17114
17170
  manualCopy(s, d);
17115
17171
  else
17116
- writeFileSync8(d, readFileSync25(s));
17172
+ writeFileSync8(d, readFileSync26(s));
17117
17173
  }
17118
17174
  }
17119
17175
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
17120
17176
  var init_sync = __esm(() => {
17121
- ROOT7 = join23(fileURLToPath12(new URL(".", import.meta.url)), "..");
17122
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join23(homedir10(), ".blitzstrike", "data");
17177
+ ROOT7 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
17178
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join24(homedir10(), ".blitzstrike", "data");
17123
17179
  });
17124
17180
 
17125
17181
  // src/update.ts
@@ -17164,15 +17220,15 @@ var init_update = __esm(() => {
17164
17220
  });
17165
17221
 
17166
17222
  // src/release.ts
17167
- import { readFileSync as readFileSync27, existsSync as existsSync13 } from "node:fs";
17168
- import { join as join24 } from "node:path";
17169
- import { fileURLToPath as fileURLToPath13 } from "node:url";
17223
+ import { readFileSync as readFileSync28, existsSync as existsSync13 } from "node:fs";
17224
+ import { join as join25 } from "node:path";
17225
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
17170
17226
  import { execSync as execSync2 } from "node:child_process";
17171
17227
  function validateRelease() {
17172
17228
  const checks = [];
17173
17229
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
17174
17230
  try {
17175
- const changelog = readFileSync27(join24(ROOT8, "CHANGELOG.md"), "utf8");
17231
+ const changelog = readFileSync28(join25(ROOT8, "CHANGELOG.md"), "utf8");
17176
17232
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
17177
17233
  const changelogVersion = m?.[1] ?? null;
17178
17234
  checks.push({
@@ -17204,22 +17260,22 @@ function validateRelease() {
17204
17260
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
17205
17261
  }
17206
17262
  function lockfilePresent() {
17207
- return existsSync13(join24(ROOT8, "package-lock.json")) || existsSync13(join24(ROOT8, "bun.lock")) || existsSync13(join24(ROOT8, "bun.lockb"));
17263
+ return existsSync13(join25(ROOT8, "package-lock.json")) || existsSync13(join25(ROOT8, "bun.lock")) || existsSync13(join25(ROOT8, "bun.lockb"));
17208
17264
  }
17209
17265
  var ROOT8;
17210
17266
  var init_release = __esm(() => {
17211
17267
  init_version();
17212
- ROOT8 = join24(fileURLToPath13(new URL(".", import.meta.url)), "..");
17268
+ ROOT8 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
17213
17269
  });
17214
17270
 
17215
17271
  // src/dependency.ts
17216
- import { readFileSync as readFileSync28 } from "node:fs";
17217
- import { join as join25 } from "node:path";
17218
- import { fileURLToPath as fileURLToPath14 } from "node:url";
17272
+ import { readFileSync as readFileSync29 } from "node:fs";
17273
+ import { join as join26 } from "node:path";
17274
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
17219
17275
  function checkDependencies() {
17220
17276
  let pkg = {};
17221
17277
  try {
17222
- pkg = JSON.parse(readFileSync28(join25(ROOT9, "package.json"), "utf8"));
17278
+ pkg = JSON.parse(readFileSync29(join26(ROOT9, "package.json"), "utf8"));
17223
17279
  } catch {
17224
17280
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
17225
17281
  }
@@ -17242,14 +17298,14 @@ function checkDependencies() {
17242
17298
  }
17243
17299
  var ROOT9;
17244
17300
  var init_dependency = __esm(() => {
17245
- ROOT9 = join25(fileURLToPath14(new URL(".", import.meta.url)), "..");
17301
+ ROOT9 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
17246
17302
  });
17247
17303
 
17248
17304
  // src/cli.ts
17249
- import { readFileSync as readFileSync29, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17250
- import { join as join26, dirname as dirname5 } from "node:path";
17305
+ import { readFileSync as readFileSync30, existsSync as existsSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17306
+ import { join as join27, dirname as dirname6 } from "node:path";
17251
17307
  import { homedir as homedir11 } from "node:os";
17252
- import { fileURLToPath as fileURLToPath15 } from "node:url";
17308
+ import { fileURLToPath as fileURLToPath16 } from "node:url";
17253
17309
  import { spawnSync as spawnSync5 } from "node:child_process";
17254
17310
  function which(cmd) {
17255
17311
  return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
@@ -17292,7 +17348,7 @@ async function runDoctor() {
17292
17348
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17293
17349
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17294
17350
  });
17295
- const dataOk = existsSync14(join26(ROOT10, "chains.json")) && existsSync14(join26(ROOT10, "tools-catalog.json"));
17351
+ const dataOk = existsSync14(join27(ROOT10, "chains.json")) && existsSync14(join27(ROOT10, "tools-catalog.json"));
17296
17352
  issues.push({
17297
17353
  name: "Data layers (chains + tools-catalog)",
17298
17354
  status: dataOk ? "ok" : "fail",
@@ -17343,28 +17399,28 @@ BlitzStrike — ${verdict}`);
17343
17399
  function resolveCommand() {
17344
17400
  if (which("blitzstrike"))
17345
17401
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17346
- const src = join26(ROOT10, "src", "index.ts");
17402
+ const src = join27(ROOT10, "src", "index.ts");
17347
17403
  if (which("bun") && existsSync14(src))
17348
17404
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17349
17405
  if (which("npx"))
17350
17406
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17351
17407
  if (which("bunx"))
17352
17408
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17353
- const dist = join26(ROOT10, "dist", "index.js");
17409
+ const dist = join27(ROOT10, "dist", "index.js");
17354
17410
  if (existsSync14(dist))
17355
17411
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17356
17412
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17357
17413
  }
17358
17414
  function readJson(p) {
17359
17415
  try {
17360
- return JSON.parse(readFileSync29(p, "utf8"));
17416
+ return JSON.parse(readFileSync30(p, "utf8"));
17361
17417
  } catch {
17362
17418
  return null;
17363
17419
  }
17364
17420
  }
17365
17421
  function jsonMcpServersWrite(p) {
17366
17422
  return (command, args) => {
17367
- const dir = dirname5(p);
17423
+ const dir = dirname6(p);
17368
17424
  if (!existsSync14(dir))
17369
17425
  mkdirSync10(dir, { recursive: true });
17370
17426
  const existing = readJson(p) ?? {};
@@ -17377,7 +17433,7 @@ function jsonMcpServersWrite(p) {
17377
17433
  }
17378
17434
  function opencodeWrite(p) {
17379
17435
  return (command, args) => {
17380
- const dir = dirname5(p);
17436
+ const dir = dirname6(p);
17381
17437
  if (!existsSync14(dir))
17382
17438
  mkdirSync10(dir, { recursive: true });
17383
17439
  const existing = readJson(p) ?? {};
@@ -17390,26 +17446,26 @@ function opencodeWrite(p) {
17390
17446
  };
17391
17447
  }
17392
17448
  function copyOpenCodeAgents() {
17393
- const srcDir = join26(ROOT10, "opencode-agents");
17394
- const dstDir = join26(homedir11(), ".config", "opencode", "agents");
17449
+ const srcDir = join27(ROOT10, "opencode-agents");
17450
+ const dstDir = join27(homedir11(), ".config", "opencode", "agents");
17395
17451
  if (!existsSync14(srcDir))
17396
17452
  return;
17397
17453
  if (!existsSync14(dstDir))
17398
17454
  mkdirSync10(dstDir, { recursive: true });
17399
17455
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17400
- const src = join26(srcDir, f);
17401
- const dst = join26(dstDir, f);
17456
+ const src = join27(srcDir, f);
17457
+ const dst = join27(dstDir, f);
17402
17458
  if (existsSync14(src)) {
17403
- writeFileSync9(dst, readFileSync29(src, "utf8"));
17459
+ writeFileSync9(dst, readFileSync30(src, "utf8"));
17404
17460
  }
17405
17461
  }
17406
17462
  }
17407
17463
  function codexWrite(p) {
17408
17464
  return (command, args) => {
17409
- const dir = dirname5(p);
17465
+ const dir = dirname6(p);
17410
17466
  if (!existsSync14(dir))
17411
17467
  mkdirSync10(dir, { recursive: true });
17412
- let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
17468
+ let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
17413
17469
  if (!existing.trimEnd().endsWith(`
17414
17470
  `))
17415
17471
  existing += `
@@ -17425,10 +17481,10 @@ args = ${JSON.stringify(args)}
17425
17481
  }
17426
17482
  function hermesWrite(p) {
17427
17483
  return (command, args) => {
17428
- const dir = dirname5(p);
17484
+ const dir = dirname6(p);
17429
17485
  if (!existsSync14(dir))
17430
17486
  mkdirSync10(dir, { recursive: true });
17431
- let existing = existsSync14(p) ? readFileSync29(p, "utf8") : "";
17487
+ let existing = existsSync14(p) ? readFileSync30(p, "utf8") : "";
17432
17488
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17433
17489
  if (!existing.trimEnd().endsWith(`
17434
17490
  `))
@@ -17455,74 +17511,74 @@ mcp_servers:
17455
17511
  function detectAgents() {
17456
17512
  const home = homedir11();
17457
17513
  const agents = [];
17458
- const claudeJson = join26(home, ".claude.json");
17514
+ const claudeJson = join27(home, ".claude.json");
17459
17515
  agents.push({
17460
17516
  name: "Claude Code",
17461
17517
  detectPath: claudeJson,
17462
17518
  installed: () => existsSync14(claudeJson) || which("claude"),
17463
17519
  write: jsonMcpServersWrite(claudeJson)
17464
17520
  });
17465
- const claudeDesktop = join26(home, ".config", "Claude", "claude_desktop_config.json");
17521
+ const claudeDesktop = join27(home, ".config", "Claude", "claude_desktop_config.json");
17466
17522
  agents.push({
17467
17523
  name: "Claude Desktop",
17468
17524
  detectPath: claudeDesktop,
17469
17525
  installed: () => existsSync14(claudeDesktop),
17470
17526
  write: jsonMcpServersWrite(claudeDesktop)
17471
17527
  });
17472
- const cursor = join26(home, ".cursor", "mcp.json");
17528
+ const cursor = join27(home, ".cursor", "mcp.json");
17473
17529
  agents.push({
17474
17530
  name: "Cursor",
17475
17531
  detectPath: cursor,
17476
- installed: () => existsSync14(join26(home, ".cursor")) || which("cursor"),
17532
+ installed: () => existsSync14(join27(home, ".cursor")) || which("cursor"),
17477
17533
  write: jsonMcpServersWrite(cursor)
17478
17534
  });
17479
- const opencode = join26(home, ".config", "opencode", "opencode.jsonc");
17535
+ const opencode = join27(home, ".config", "opencode", "opencode.jsonc");
17480
17536
  agents.push({
17481
17537
  name: "OpenCode",
17482
17538
  detectPath: opencode,
17483
- installed: () => existsSync14(join26(home, ".config", "opencode")) || which("opencode"),
17539
+ installed: () => existsSync14(join27(home, ".config", "opencode")) || which("opencode"),
17484
17540
  write: opencodeWrite(opencode)
17485
17541
  });
17486
- const codex = join26(home, ".codex", "config.toml");
17542
+ const codex = join27(home, ".codex", "config.toml");
17487
17543
  agents.push({
17488
17544
  name: "Codex",
17489
17545
  detectPath: codex,
17490
- installed: () => existsSync14(join26(home, ".codex")) || which("codex"),
17546
+ installed: () => existsSync14(join27(home, ".codex")) || which("codex"),
17491
17547
  write: codexWrite(codex)
17492
17548
  });
17493
- const hermes = join26(home, ".hermes", "config.yaml");
17549
+ const hermes = join27(home, ".hermes", "config.yaml");
17494
17550
  agents.push({
17495
17551
  name: "Hermes",
17496
17552
  detectPath: hermes,
17497
- installed: () => existsSync14(join26(home, ".hermes")) || which("hermes"),
17553
+ installed: () => existsSync14(join27(home, ".hermes")) || which("hermes"),
17498
17554
  write: hermesWrite(hermes)
17499
17555
  });
17500
- const gemini = join26(home, ".gemini", "settings.json");
17556
+ const gemini = join27(home, ".gemini", "settings.json");
17501
17557
  agents.push({
17502
17558
  name: "Gemini CLI",
17503
17559
  detectPath: gemini,
17504
- installed: () => existsSync14(join26(home, ".gemini")) || which("gemini"),
17560
+ installed: () => existsSync14(join27(home, ".gemini")) || which("gemini"),
17505
17561
  write: jsonMcpServersWrite(gemini)
17506
17562
  });
17507
- const windsurf = join26(home, ".codeium", "windsurf", "mcp_config.json");
17563
+ const windsurf = join27(home, ".codeium", "windsurf", "mcp_config.json");
17508
17564
  agents.push({
17509
17565
  name: "Windsurf",
17510
17566
  detectPath: windsurf,
17511
- installed: () => existsSync14(join26(home, ".codeium")) || which("windsurf"),
17567
+ installed: () => existsSync14(join27(home, ".codeium")) || which("windsurf"),
17512
17568
  write: jsonMcpServersWrite(windsurf)
17513
17569
  });
17514
- const copilot = join26(home, ".copilot", "mcp.json");
17570
+ const copilot = join27(home, ".copilot", "mcp.json");
17515
17571
  agents.push({
17516
17572
  name: "Copilot",
17517
17573
  detectPath: copilot,
17518
- installed: () => existsSync14(join26(home, ".copilot")),
17574
+ installed: () => existsSync14(join27(home, ".copilot")),
17519
17575
  write: jsonMcpServersWrite(copilot)
17520
17576
  });
17521
- const cline = join26(home, ".cline", "mcp_settings.json");
17577
+ const cline = join27(home, ".cline", "mcp_settings.json");
17522
17578
  agents.push({
17523
17579
  name: "Cline",
17524
17580
  detectPath: cline,
17525
- installed: () => existsSync14(join26(home, ".cline")),
17581
+ installed: () => existsSync14(join27(home, ".cline")),
17526
17582
  write: jsonMcpServersWrite(cline)
17527
17583
  });
17528
17584
  return agents;
@@ -17640,7 +17696,7 @@ var init_cli = __esm(() => {
17640
17696
  init_release();
17641
17697
  init_dependency();
17642
17698
  init_mcp_status();
17643
- ROOT10 = join26(fileURLToPath15(new URL(".", import.meta.url)), "..");
17699
+ ROOT10 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
17644
17700
  });
17645
17701
 
17646
17702
  // node_modules/zod/v3/helpers/util.js
@@ -32278,7 +32334,7 @@ class StdioServerTransport {
32278
32334
 
32279
32335
  // src/server.ts
32280
32336
  init_scanner();
32281
- import { readFileSync as readFileSync26 } from "node:fs";
32337
+ import { readFileSync as readFileSync27 } from "node:fs";
32282
32338
 
32283
32339
  // src/recon.ts
32284
32340
  var TIMEOUT_MS = 25000;
@@ -56680,10 +56736,97 @@ var javaAdapter = {
56680
56736
  return { sources, sinks, sanitizers, calls, assigns };
56681
56737
  }
56682
56738
  };
56739
+ var RUST_SOURCES = [
56740
+ [/env::args\s*\(|args_os\s*\(/, "cli_argument"],
56741
+ [/stdin\s*\(\s*\)|read_line\s*\(/, "raw_body"],
56742
+ [/\b(?:Query|Form|Json|Path)\b|web::(?:Query|Json|Form|Path)|req\.(?:query|params|param|form|query_string)/, "http_parameter"],
56743
+ [/env::var\s*\(|env::var_os\s*\(/, "env_variable"]
56744
+ ];
56745
+ var RUST_SINKS = [
56746
+ [/Command::new|process::Command/, "command_execution", "CWE-78"],
56747
+ [/sqlx::query(?:_as)?\s*\(|sqlx::raw_sql|\.query\s*\(|\.execute\s*\(|rusqlite|diesel::/, "sql_execution", "CWE-89"],
56748
+ [/reqwest::get|reqwest::Client|hyper::|\.get\s*\(|\.post\s*\(|\.put\s*\(|\.request\s*\(/, "http_request", "CWE-918"],
56749
+ [/std::fs::(read_to_string|read|write|remove_file|copy|rename)|File::open\s*\(|fs::(read|write|remove_file|copy)\s*\(/, "path_traversal", "CWE-22"],
56750
+ [/serde_json::from_(str|slice)|bincode::deserialize|serde_yaml::from_str|toml::from_str|\.deserialize\s*\(/, "deserialization", "CWE-502"]
56751
+ ];
56752
+ var RUST_SANITIZERS = [
56753
+ [/canonicalize\s*\(/, ["path_traversal"]],
56754
+ [/query(_as)?!\s*\(/, ["sql_execution"]]
56755
+ ];
56756
+ var rustAdapter = {
56757
+ language: "rust",
56758
+ extensions: [".rs"],
56759
+ parse(code, _file) {
56760
+ const sources = [];
56761
+ const sinks = [];
56762
+ const sanitizers = [];
56763
+ const calls = [];
56764
+ const assigns = [];
56765
+ const assignRe = /let\s+(?:mut\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?);/g;
56766
+ let m;
56767
+ while ((m = assignRe.exec(code)) !== null) {
56768
+ const varName = m[1];
56769
+ const rhs = m[2];
56770
+ const line = code.slice(0, m.index).split(`
56771
+ `).length;
56772
+ const src = RUST_SOURCES.find(([re]) => re.test(rhs));
56773
+ if (src)
56774
+ sources.push({ variable: varName, kind: src[1], line, attacker_controlled: src[1] !== "cli_argument" && src[1] !== "env_variable" });
56775
+ const rhsVars = [];
56776
+ const vre = /([a-z_][a-z0-9_]*)/g;
56777
+ let vm;
56778
+ while ((vm = vre.exec(rhs)) !== null) {
56779
+ const v = vm[1];
56780
+ if (!RUST_SOURCES.some(([re]) => re.test(v)))
56781
+ rhsVars.push(v);
56782
+ }
56783
+ assigns.push({ target: varName, sources: rhsVars.slice(0, 8), line });
56784
+ }
56785
+ const extractorRe = /\b(?:Query|Form|Json|Path)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
56786
+ while ((m = extractorRe.exec(code)) !== null) {
56787
+ const line = code.slice(0, m.index).split(`
56788
+ `).length;
56789
+ sources.push({ variable: m[1], kind: "http_parameter", line, attacker_controlled: true });
56790
+ assigns.push({ target: m[1], sources: [], line });
56791
+ }
56792
+ const lines = code.split(`
56793
+ `);
56794
+ for (let i = 0;i < lines.length; i++) {
56795
+ const line = lines[i];
56796
+ for (const [re, kind, cwe] of RUST_SINKS) {
56797
+ if (!re.test(line))
56798
+ continue;
56799
+ if (kind === "sql_execution" && /query(_as)?!\s*\(/.test(line))
56800
+ continue;
56801
+ const argVars = [];
56802
+ const vre = /([a-z_][a-z0-9_]*)/g;
56803
+ let vm;
56804
+ while ((vm = vre.exec(line)) !== null) {
56805
+ const id = vm[1];
56806
+ if (/^(new|let|mut|arg|args|command|process|std|sqlx|query|query_as|execute|reqwest|client|get|post|put|request|fs|file|open|read_to_string|read|write|remove_file|copy|rename|serde_json|from_str|from_slice|bincode|deserialize|toml|yaml|format|unwrap|expect|env|args|stdin|read_line)$/.test(id))
56807
+ continue;
56808
+ argVars.push(id);
56809
+ }
56810
+ for (const v of argVars.slice(0, 8))
56811
+ sinks.push({ variable: v, kind, line: i + 1, cwe });
56812
+ }
56813
+ for (const [re, kinds] of RUST_SANITIZERS) {
56814
+ if (re.test(line)) {
56815
+ const vre = /([a-z_][a-z0-9_]*)/g;
56816
+ let vm;
56817
+ while ((vm = vre.exec(line)) !== null)
56818
+ sanitizers.push({ variable: vm[1], neutralizes: kinds, line: i + 1 });
56819
+ }
56820
+ }
56821
+ }
56822
+ return { sources, sinks, sanitizers, calls, assigns };
56823
+ }
56824
+ };
56683
56825
  registerLanguage(phpAdapter);
56684
56826
  registerLanguage(jsAdapter);
56685
56827
  registerLanguage(pythonAdapter);
56686
56828
  registerLanguage(javaAdapter);
56829
+ registerLanguage(rustAdapter);
56687
56830
 
56688
56831
  // src/incremental.ts
56689
56832
  var SOURCE_EXTS = [".php", ".phtml", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
@@ -58011,7 +58154,14 @@ var ALIASES = {
58011
58154
  "ai supply chain": "ml_supply_chain",
58012
58155
  "model poisoning": "ml_supply_chain",
58013
58156
  "ml security": "ml_supply_chain",
58014
- "ai ml security": "ml_supply_chain"
58157
+ "ai ml security": "ml_supply_chain",
58158
+ "rust unsafe": "rust_unsafe",
58159
+ "unsafe rust": "rust_unsafe",
58160
+ "rust memory safety": "rust_unsafe",
58161
+ transmute: "rust_unsafe",
58162
+ "rust format string": "rust_format_string",
58163
+ "format string": "rust_format_string",
58164
+ "format string injection": "rust_format_string"
58015
58165
  };
58016
58166
  function normalize(term) {
58017
58167
  return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
@@ -58279,6 +58429,103 @@ async function validateLeakedKey(input) {
58279
58429
  };
58280
58430
  }
58281
58431
 
58432
+ // src/hackbot-arena.ts
58433
+ import { readFileSync as readFileSync18 } from "node:fs";
58434
+ import { join as join18, dirname as dirname3 } from "node:path";
58435
+ import { fileURLToPath as fileURLToPath9 } from "node:url";
58436
+ var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
58437
+ function load4() {
58438
+ try {
58439
+ return JSON.parse(readFileSync18(join18(__dirname3, "..", "intelligence", "hackbot-arena.json"), "utf8"));
58440
+ } catch {
58441
+ return { total: 0, flag_format: "FLAG{nusasec-<32 hex>}", labs: [] };
58442
+ }
58443
+ }
58444
+ var VECTOR_MAP = [
58445
+ { re: /jwt|algorithm confusion|rs256|hs256/i, vectors: ["jwt"], detectors: ["missing_authz"], chains: ["jwt"], tools: ["jwt_analyze", "live_recon"] },
58446
+ { re: /ssrf|server-side request/i, vectors: ["ssrf"], detectors: ["ssrf"], chains: ["ssrf"], tools: ["live_recon", "blind_oracle", "active_scan"] },
58447
+ { re: /sql injection|sqli|postgresql|sql/i, vectors: ["sql_injection"], detectors: ["sql_injection"], chains: ["sql_injection"], tools: ["live_recon", "blind_oracle", "active_scan"] },
58448
+ { re: /ssti|template injection|liquid|jinja|twig/i, vectors: ["ssti"], detectors: ["ssti"], chains: ["ssti"], tools: ["live_recon", "blind_oracle"] },
58449
+ { re: /command injection|os command|command execution/i, vectors: ["command_injection"], detectors: ["command_injection"], chains: ["command_injection"], tools: ["live_recon", "blind_oracle"] },
58450
+ { re: /idor|bola|authorization|broken function|role|membership|ownership|entitlement|directory search|excessive data|data exposure|harvest/i, vectors: ["broken_object_level_authorization"], detectors: ["missing_authz"], chains: ["idor"], tools: ["live_recon", "active_scan", "param_precedence"] },
58451
+ { re: /graphql/i, vectors: ["graphql_exposure"], detectors: ["graphql_exposure", "sql_injection"], chains: ["graphql"], tools: ["live_recon", "active_scan"] },
58452
+ { re: /cache/i, vectors: ["web_cache_deception"], detectors: [], chains: ["cache_deception"], tools: ["cache_gap", "live_recon"] },
58453
+ { re: /credential|key|token|bearer|secret|leak|websocket|rum|api key/i, vectors: ["hardcoded_secret"], detectors: ["hardcoded_secret"], chains: ["secret_leak"], tools: ["live_recon", "drive_devtools", "scan_git_history"] },
58454
+ { re: /rate.limit|batching|otp/i, vectors: ["business_logic"], detectors: [], chains: ["rate_limit_bypass"], tools: ["race_test", "active_scan"] },
58455
+ { re: /mass assignment|self.regist|admin account|user creation|scope/i, vectors: ["mass_assignment"], detectors: ["mass_assignment", "missing_authz"], chains: ["mass_assignment"], tools: ["live_recon", "active_scan"] },
58456
+ { re: /deserialization|rce|workflow|execution/i, vectors: ["code_execution"], detectors: ["deserialization"], chains: ["deserialization"], tools: ["live_recon", "active_scan"] }
58457
+ ];
58458
+ function mapLab(lab) {
58459
+ const out = { vectors: [], detectors: [], chains: [], tools: [] };
58460
+ for (const m of VECTOR_MAP) {
58461
+ if (m.re.test(lab.vuln_class)) {
58462
+ out.vectors.push(...m.vectors);
58463
+ out.detectors.push(...m.detectors);
58464
+ out.chains.push(...m.chains);
58465
+ out.tools.push(...m.tools);
58466
+ }
58467
+ }
58468
+ out.vectors = [...new Set(out.vectors)];
58469
+ out.detectors = [...new Set(out.detectors)];
58470
+ out.chains = [...new Set(out.chains)];
58471
+ out.tools = [...new Set(out.tools)];
58472
+ return out;
58473
+ }
58474
+ function findByLab(labId) {
58475
+ const id = labId.toLowerCase().replace(/[^a-z0-9]/g, "");
58476
+ return load4().labs.find((l) => l.id.toLowerCase() === id || l.id.toLowerCase() === `labs${id}`) ?? null;
58477
+ }
58478
+ function hackbotArenaList() {
58479
+ const doc = load4();
58480
+ return {
58481
+ arena: "Hackbot-Arena (NusaSec)",
58482
+ total: doc.total,
58483
+ flag_format: doc.flag_format,
58484
+ note: "Port = 8080 + lab number (labs01 -> 8081 … labs30 -> 8110). Each lab has a solver/ reference exploit + judge.success_when solve criteria.",
58485
+ labs: doc.labs.map((l) => ({ id: l.id, name: l.name, difficulty: l.difficulty, port: l.port, vuln_class: l.vuln_class, stack: l.stack }))
58486
+ };
58487
+ }
58488
+ function hackbotArenaBrief(labId) {
58489
+ const lab = findByLab(labId);
58490
+ if (!lab)
58491
+ return { error: `unknown lab '${labId}'`, hint: "use hackbot_arena_list() to see all 30 labs" };
58492
+ const plan = mapLab(lab);
58493
+ return {
58494
+ id: lab.id,
58495
+ name: lab.name,
58496
+ difficulty: lab.difficulty,
58497
+ target: `http://localhost:${lab.port}`,
58498
+ vuln_class: lab.vuln_class,
58499
+ stack: lab.stack,
58500
+ flag_format: load4().flag_format,
58501
+ attack_plan: {
58502
+ vectors: plan.vectors,
58503
+ detectors: plan.detectors,
58504
+ chains: plan.chains,
58505
+ tools: plan.tools
58506
+ },
58507
+ hint: `Start with live_recon(target) to fingerprint, then drive the ${plan.chains.join(" + ") || "detected"} chain. Verify every step live (strike_verify / browser_validate) and recover the flag. Flag format: ${load4().flag_format}.`
58508
+ };
58509
+ }
58510
+ function hackbotArenaCoverage() {
58511
+ const doc = load4();
58512
+ const rows = doc.labs.map((l) => {
58513
+ const plan = mapLab(l);
58514
+ return { id: l.id, name: l.name, vuln_class: l.vuln_class, vectors: plan.vectors, detectors: plan.detectors, chains: plan.chains };
58515
+ });
58516
+ const withVector = rows.filter((r) => r.vectors.length > 0).length;
58517
+ const withDetector = rows.filter((r) => r.detectors.length > 0).length;
58518
+ return {
58519
+ arena: "Hackbot-Arena (NusaSec)",
58520
+ total_labs: doc.total,
58521
+ labs_with_mapped_vector: withVector,
58522
+ labs_with_detector_coverage: withDetector,
58523
+ coverage_ratio: Number((withVector / doc.total).toFixed(2)),
58524
+ note: "A mapped vector/detector means Blitz Strike has the deterministic tooling to drive that vuln class; the agent still has to execute the chain + recover the flag.",
58525
+ rows
58526
+ };
58527
+ }
58528
+
58282
58529
  // src/severity.ts
58283
58530
  var VRT_MAP = {
58284
58531
  sql_injection: { vrt: "injection > sql_injection", priority: "P1", severity: "critical", rationale: "SQL injection reaches sensitive data or enables admin takeover." },
@@ -58302,7 +58549,9 @@ var VRT_MAP = {
58302
58549
  oauth_misconfig: { vrt: "authentication > oauth_misconfiguration", priority: "P2", severity: "high", rationale: "An OAuth/open-redirect misconfiguration can leak authorization codes → account takeover." },
58303
58550
  grpc_reflection: { vrt: "misconfiguration > debug_information_exposure", priority: "P5", severity: "low", rationale: "gRPC reflection exposes the API contract but is not directly exploitable." },
58304
58551
  dependency_confusion: { vrt: "insecure_design > dependency_confusion", priority: "P2", severity: "high", rationale: "An internal package resolves from a squattable public registry → attacker-controlled code in the build." },
58305
- ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." }
58552
+ ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." },
58553
+ rust_unsafe: { vrt: "insecure_design > unsafe_memory_operations", priority: "P3", severity: "medium", rationale: "transmute/from_raw_parts/assume_init bypass the borrow checker — a hint that needs bounds verification; memory corruption if attacker-controlled." },
58554
+ rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." }
58306
58555
  };
58307
58556
  function severityCalibrate(input) {
58308
58557
  const type = input.type?.toLowerCase() ?? "";
@@ -58341,13 +58590,13 @@ function listSeverityMap() {
58341
58590
  }
58342
58591
 
58343
58592
  // src/datarefresh.ts
58344
- import { readFileSync as readFileSync18 } from "node:fs";
58345
- import { join as join18, dirname as dirname3 } from "node:path";
58346
- import { fileURLToPath as fileURLToPath9 } from "node:url";
58347
- var __dirname3 = dirname3(fileURLToPath9(import.meta.url));
58593
+ import { readFileSync as readFileSync19 } from "node:fs";
58594
+ import { join as join19, dirname as dirname4 } from "node:path";
58595
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
58596
+ var __dirname4 = dirname4(fileURLToPath10(import.meta.url));
58348
58597
  function loadJson2(rel) {
58349
58598
  try {
58350
- return JSON.parse(readFileSync18(join18(__dirname3, "..", rel), "utf8"));
58599
+ return JSON.parse(readFileSync19(join19(__dirname4, "..", rel), "utf8"));
58351
58600
  } catch {
58352
58601
  return null;
58353
58602
  }
@@ -58382,18 +58631,18 @@ init_verify_harness();
58382
58631
  init_variants();
58383
58632
 
58384
58633
  // src/benchmark.ts
58385
- import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
58386
- import { join as join20 } from "node:path";
58387
- import { fileURLToPath as fileURLToPath11 } from "node:url";
58634
+ import { readFileSync as readFileSync21, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
58635
+ import { join as join21 } from "node:path";
58636
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
58388
58637
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
58389
58638
  init_complex_bugs();
58390
- var ROOT5 = join20(fileURLToPath11(new URL(".", import.meta.url)), "..");
58391
- var CORPUS = join20(ROOT5, "bench", "corpus");
58392
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join20(homedir8(), ".blitzstrike", "corpus");
58639
+ var ROOT5 = join21(fileURLToPath12(new URL(".", import.meta.url)), "..");
58640
+ var CORPUS = join21(ROOT5, "bench", "corpus");
58641
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join21(homedir8(), ".blitzstrike", "corpus");
58393
58642
  function parseCorpusFile(path) {
58394
58643
  let text;
58395
58644
  try {
58396
- text = readFileSync20(path, "utf8");
58645
+ text = readFileSync21(path, "utf8");
58397
58646
  } catch {
58398
58647
  return null;
58399
58648
  }
@@ -58449,7 +58698,7 @@ function loadDir(dir) {
58449
58698
  for (const f of readdirSync5(dir)) {
58450
58699
  if (!f.endsWith(".md"))
58451
58700
  continue;
58452
- const e = parseCorpusFile(join20(dir, f));
58701
+ const e = parseCorpusFile(join21(dir, f));
58453
58702
  if (e)
58454
58703
  entries.push(e);
58455
58704
  }
@@ -58476,7 +58725,7 @@ function captureFalsePositive(input) {
58476
58725
  input.code,
58477
58726
  "```"
58478
58727
  ].filter((l) => l !== null);
58479
- const path = join20(CAPTURED_CORPUS_DIR, `${id}.md`);
58728
+ const path = join21(CAPTURED_CORPUS_DIR, `${id}.md`);
58480
58729
  writeFileSync6(path, lines.join(`
58481
58730
  `) + `
58482
58731
  `, "utf8");
@@ -58498,7 +58747,7 @@ function detect(entry) {
58498
58747
  }
58499
58748
  const lang = entry.language;
58500
58749
  if (lang === "php") {
58501
- const tmp = join20(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
58750
+ const tmp = join21(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
58502
58751
  writeFileSync6(tmp, entry.code);
58503
58752
  const r = analyzeDataFlow2(tmp);
58504
58753
  rmSync3(tmp, { force: true });
@@ -58567,12 +58816,12 @@ function runBenchmark() {
58567
58816
  }
58568
58817
 
58569
58818
  // src/obligations.ts
58570
- import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
58571
- import { join as join21 } from "node:path";
58819
+ import { readFileSync as readFileSync22, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync7 } from "node:fs";
58820
+ import { join as join22 } from "node:path";
58572
58821
  import { createHash as createHash6 } from "node:crypto";
58573
58822
  import { homedir as homedir9 } from "node:os";
58574
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join21(homedir9(), ".blitzstrike");
58575
- var OBLIGATION_PATH = join21(HOME_DIR4, "obligations.jsonl");
58823
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join22(homedir9(), ".blitzstrike");
58824
+ var OBLIGATION_PATH = join22(HOME_DIR4, "obligations.jsonl");
58576
58825
  function ensureDir3() {
58577
58826
  if (!existsSync11(HOME_DIR4))
58578
58827
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -58585,7 +58834,7 @@ function loadObligations() {
58585
58834
  return [];
58586
58835
  try {
58587
58836
  const out = [];
58588
- for (const line of readFileSync21(OBLIGATION_PATH, "utf8").split(`
58837
+ for (const line of readFileSync22(OBLIGATION_PATH, "utf8").split(`
58589
58838
  `)) {
58590
58839
  const t = line.trim();
58591
58840
  if (!t)
@@ -58689,11 +58938,11 @@ init_framework_security();
58689
58938
  // src/variant-scan.ts
58690
58939
  init_scanner();
58691
58940
  init_complex_bugs();
58692
- import { readFileSync as readFileSync23 } from "node:fs";
58941
+ import { readFileSync as readFileSync24 } from "node:fs";
58693
58942
 
58694
58943
  // src/cross-file.ts
58695
58944
  init_scanner();
58696
- import { readFileSync as readFileSync22 } from "node:fs";
58945
+ import { readFileSync as readFileSync23 } from "node:fs";
58697
58946
  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)/;
58698
58947
  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*\(/;
58699
58948
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -58745,7 +58994,7 @@ function detectCrossFilePrivesc(root) {
58745
58994
  for (const file of files) {
58746
58995
  let code;
58747
58996
  try {
58748
- code = stripComments(readFileSync22(file, "utf8"));
58997
+ code = stripComments(readFileSync23(file, "utf8"));
58749
58998
  } catch {
58750
58999
  continue;
58751
59000
  }
@@ -58786,7 +59035,7 @@ function detectCrossFilePrivesc(root) {
58786
59035
  for (const file of files) {
58787
59036
  let code;
58788
59037
  try {
58789
- code = stripComments(readFileSync22(file, "utf8"));
59038
+ code = stripComments(readFileSync23(file, "utf8"));
58790
59039
  } catch {
58791
59040
  continue;
58792
59041
  }
@@ -58809,7 +59058,7 @@ function detectCrossFilePrivesc(root) {
58809
59058
  for (const file of files) {
58810
59059
  let code;
58811
59060
  try {
58812
- code = stripComments(readFileSync22(file, "utf8"));
59061
+ code = stripComments(readFileSync23(file, "utf8"));
58813
59062
  } catch {
58814
59063
  continue;
58815
59064
  }
@@ -58924,7 +59173,7 @@ function variantScan(root, opts = {}) {
58924
59173
  for (const file of files) {
58925
59174
  let code;
58926
59175
  try {
58927
- code = readFileSync23(file, "utf8");
59176
+ code = readFileSync24(file, "utf8");
58928
59177
  } catch {
58929
59178
  continue;
58930
59179
  }
@@ -59584,17 +59833,17 @@ function analyzeJwt(token, opts = {}) {
59584
59833
  }
59585
59834
 
59586
59835
  // src/enterprise-benchmark.ts
59587
- import { existsSync as existsSync12, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59588
- import { join as join22, resolve } from "node:path";
59836
+ import { existsSync as existsSync12, readFileSync as readFileSync25, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
59837
+ import { join as join23, resolve } from "node:path";
59589
59838
  init_complex_bugs();
59590
59839
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
59591
59840
  var ENTERPRISE_FIXTURES = [
59592
- { id: "php-laravel", dir: join22(ROOT6, "php-laravel"), expected_vulns: 4 },
59593
- { id: "js-express", dir: join22(ROOT6, "js-express"), expected_vulns: 2 },
59594
- { id: "py-django", dir: join22(ROOT6, "py-django"), expected_vulns: 2 },
59595
- { id: "java-spring", dir: join22(ROOT6, "java-spring"), expected_vulns: 2 },
59596
- { id: "wp-batch-route", dir: join22(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59597
- { id: "php-complex", dir: join22(ROOT6, "php-complex"), expected_vulns: 4 }
59841
+ { id: "php-laravel", dir: join23(ROOT6, "php-laravel"), expected_vulns: 4 },
59842
+ { id: "js-express", dir: join23(ROOT6, "js-express"), expected_vulns: 2 },
59843
+ { id: "py-django", dir: join23(ROOT6, "py-django"), expected_vulns: 2 },
59844
+ { id: "java-spring", dir: join23(ROOT6, "java-spring"), expected_vulns: 2 },
59845
+ { id: "wp-batch-route", dir: join23(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59846
+ { id: "php-complex", dir: join23(ROOT6, "php-complex"), expected_vulns: 4 }
59598
59847
  ];
59599
59848
  function sourceFiles(root) {
59600
59849
  const out = [];
@@ -59606,7 +59855,7 @@ function sourceFiles(root) {
59606
59855
  return;
59607
59856
  }
59608
59857
  for (const e of entries) {
59609
- const p = join22(dir, e);
59858
+ const p = join23(dir, e);
59610
59859
  let st;
59611
59860
  try {
59612
59861
  st = statSync3(p);
@@ -59627,7 +59876,7 @@ function detectFixture(dir) {
59627
59876
  for (const f of sourceFiles(dir)) {
59628
59877
  let code;
59629
59878
  try {
59630
- code = readFileSync24(f, "utf8");
59879
+ code = readFileSync25(f, "utf8");
59631
59880
  } catch {
59632
59881
  continue;
59633
59882
  }
@@ -59758,11 +60007,29 @@ var JAVA_SINKS2 = {
59758
60007
  xpath_injection: true,
59759
60008
  ldap_injection: true
59760
60009
  };
60010
+ var RUST_SINKS2 = {
60011
+ sql_execution: true,
60012
+ code_execution: false,
60013
+ command_execution: true,
60014
+ file_operations: true,
60015
+ file_inclusion: false,
60016
+ deserialization: true,
60017
+ http_request: true,
60018
+ redirect: true,
60019
+ html_render: false,
60020
+ xml_processing: false,
60021
+ archive_extraction: false,
60022
+ path_traversal: true,
60023
+ template_injection: false,
60024
+ xpath_injection: false,
60025
+ ldap_injection: false
60026
+ };
59761
60027
  var SINK_BY_LANG = {
59762
60028
  php: PHP_SINKS2,
59763
60029
  javascript: JS_SINKS2,
59764
60030
  python: PY_SINKS2,
59765
- java: JAVA_SINKS2
60031
+ java: JAVA_SINKS2,
60032
+ rust: RUST_SINKS2
59766
60033
  };
59767
60034
  function coverageMatrix() {
59768
60035
  const langs = listLanguages();
@@ -59822,7 +60089,7 @@ function createServer() {
59822
60089
  ` + `- A hard block (Cloudflare/WAF/IP-block/captcha) is NOT the end: mark the finding blocked + note the required vantage (residential / authorized / correct-SNI / --resolve direct-to-origin), then CONTINUE to the next lead (origin bypass, non-prod cluster, mobile APK, subdomain).
59823
60090
  ` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
59824
60091
 
59825
- ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
60092
+ ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + "DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Ground any detector hit empirically with pattern_lookup(id) — it returns the class's CWE, real-world signature, escalation chains, citable references, per-class bypass techniques, and the high-value assets (crown jewels) that class hits, so a raw hit becomes a class-aware finding. Resolve free-form terminology first with synonym_lookup(term) (IDOR/BOLA/SQLi/prompt-injection → canonical id). For an llm_injection hit, its pattern carries a validation discipline — the RUN-TWICE RULE (reproduce token-for-token across two fresh sessions), NON-GUESSABLE ANCHOR (must leak a real key/URL, not a plausible guess), and OOB PROOF (a verifiable callback log) — apply all three before reporting. BEFORE generating a report, run submission_gate(...) and only ship a finding that passes all 7 questions (reproducible, in-scope, real-impact, no-privileged-assumption, verified-live, evidence-redacted, severity-derived); one false = kill it. Calibrate severity with severity_calibrate(type, cvss) to get a P1–P5 tier + VRT-style category. Start each engagement with engagement_scaffold(target, scope) for the folder + scope.md structure, and check data_refresh() to confirm the data layers are consistent. Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.\n" + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors/recall) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). intelligence(action=recall, tech, target) is the per-engagement pattern recall — it returns which patterns historically CONFIRMED on that tech (prioritize them) versus which only ever produced false positives (avoid them), so a new engagement starts from the empirical playbook of past ones. LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report. NEWEST DETECTORS — understand AND try them: variant_scan also emits priv_esc (privilege escalation — a role/capability/user change fed by request input, traced cross-file via reverse call-graph, e.g. set_role($id, sanitize_key($_POST['um-role'])) or update_user_meta(...,'wp_capabilities', $_POST[...])) and flags uploadAllow=>array('all') unrestricted-upload configs as file_upload (the file-manager/connector shape). TO TRY a priv_esc hit: submit the role field with 'administrator' (or send wp_capabilities meta) and confirm the account actually gains admin. TO TRY an unrestricted-upload hit: POST a .php/.phtml and confirm it lands web-reachable + executes. variant_scan also emits hardcoded_secret (CWE-798) in three tiers: a secret field (api_key/db_password/access_token/aws_*) assigned a literal (HIGH), a known secret FORMAT (JWT eyJ…/AWS AKIA…/GitHub ghp_…/Slack xoxb-…/…) regardless of name (HIGH), or a high-entropy suspect token (MEDIUM — verify before reporting). Verify a frontend/JS secret live via drive_devtools (inspect the JS bundle / network response and confirm the key is actually exposed), and for a backend secret confirm it is reachable/valid. A static hit is a HYPOTHESIS until verified live — never report unverified. For a repo, also scan_git_history(root) — secrets DELETED from HEAD still live in .git history (the removed-later-is-not-fixed trap), and it feeds every recovered pre-deletion blob through the same secret detector. Once you recover a credential, prove it is still ACTIVE with validate_leaked_key(apiBase, endpoints, key|blobUrl) — a READ-ONLY differential test (401-without-key vs 200-with-key = auth bypass PROVEN). Two supply-chain detectors: dependency_confusion (a manifest/build file resolving a package from a public registry — pypi.org/npmjs.org --extra-index-url, or a scoped @company/pkg with no private registry) and ml_supply_chain (torch.load without weights_only=True / joblib.load / np.load(allow_pickle=True) / keras load_model on an untrusted model → pickle RCE). Rust is supported end-to-end: the taint engine (blitz_scan / taint_file) maps Rust sinks (std::process::Command, sqlx/diesel, reqwest/hyper, std::fs, serde/bincode) to the classic classes, plus two Rust-specific detectors — rust_unsafe (transmute / assume_init / from_raw_parts / ptr::* — memory-unsafety surface, CWE-119) and rust_format_string (println!/format! with a variable as the format string — CWE-134). The sqlx query! compile-time macro is treated as safe (suppressed).\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
59826
60093
  ` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
59827
60094
  ` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
59828
60095
  ` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
@@ -60292,6 +60559,29 @@ ${statusLine}` : statusLine },
60292
60559
  }, async (input) => {
60293
60560
  return { content: [{ type: "text", text: JSON.stringify(await validateLeakedKey(input)) }] };
60294
60561
  });
60562
+ server.registerTool("hackbot_arena_list", {
60563
+ title: "List Hackbot Arena benchmark labs",
60564
+ description: "BENCHMARK: list the 30 Hackbot Arena (NusaSec) dockerized web-security labs — a local benchmark of realistic vulnerability chains with deterministic flags. Returns id/name/difficulty/port/vuln_class per lab (evaluator view; no flags or solutions).",
60565
+ inputSchema: {}
60566
+ }, async () => {
60567
+ return { content: [{ type: "text", text: JSON.stringify(hackbotArenaList()) }] };
60568
+ });
60569
+ server.registerTool("hackbot_arena_brief", {
60570
+ title: "Attack-plan brief for one Hackbot Arena lab",
60571
+ description: "BENCHMARK: given a lab id (e.g. labs03), return the target URL, vuln class, stack, flag format, and the DERIVED Blitz Strike attack plan (vectors/detectors/chains/tools) to solve it. This is the agent brief — it never contains the flag, judge criteria, or reference solution.",
60572
+ inputSchema: {
60573
+ labId: string2().describe("Lab id (e.g. labs03, labs20)")
60574
+ }
60575
+ }, async ({ labId }) => {
60576
+ return { content: [{ type: "text", text: JSON.stringify(hackbotArenaBrief(labId)) }] };
60577
+ });
60578
+ server.registerTool("hackbot_arena_coverage", {
60579
+ title: "Blitz Strike coverage vs Hackbot Arena",
60580
+ description: "BENCHMARK: map all 30 Hackbot Arena labs against Blitz Strike's detector/vector coverage — which vuln classes Blitz Strike has deterministic tooling for, per lab, plus the derived chains.",
60581
+ inputSchema: {}
60582
+ }, async () => {
60583
+ return { content: [{ type: "text", text: JSON.stringify(hackbotArenaCoverage()) }] };
60584
+ });
60295
60585
  server.registerTool("fofa_search", {
60296
60586
  title: "FOFA asset search",
60297
60587
  description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
@@ -60677,7 +60967,7 @@ ${statusLine}` : statusLine },
60677
60967
  let src = code;
60678
60968
  if (!src && path) {
60679
60969
  try {
60680
- src = readFileSync26(path, "utf8");
60970
+ src = readFileSync27(path, "utf8");
60681
60971
  } catch {
60682
60972
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
60683
60973
  }
@@ -61295,7 +61585,7 @@ ${statusLine}` : statusLine },
61295
61585
  description: "Route confusion (separate class from source→sink taint): detect attacker-controlled dispatch (call_user_func/forward/dispatch), dynamic method calls ($obj->$m()), dynamic include (LFI), batch/proxy forwarding without per-route auth re-check, and route matching without path normalization.",
61296
61586
  inputSchema: { path: string2().describe("Source file path (PHP)") }
61297
61587
  }, async ({ path }) => {
61298
- const code = readFileSync26(path, "utf8");
61588
+ const code = readFileSync27(path, "utf8");
61299
61589
  const findings = detectRouteConfusion(code, path);
61300
61590
  return {
61301
61591
  content: [{
@@ -61309,7 +61599,7 @@ ${statusLine}` : statusLine },
61309
61599
  description: "Complex-bug detection beyond taint: deserialization→POP gadget chain (unserialize + magic method), type juggling (loose ==/!= vs hash/secret), mass assignment (extract/parse_str without a safe flag), prototype pollution (unsafe merge of request data), CRLF/header injection (user input into a header), path confusion (user input into a file path without canonicalization), SSRF (user-controlled URL with no host allowlist), XXE (user XML parsed without disabling entities), SSTI (user input into a template render with no sandbox), wrong-context sanitization (sanitize_text_field() into a SQL query), and missing authorization/nonce on WordPress entry points (wp_ajax_* / admin_post_* / register_rest_route without current_user_can, nonce verification, or permission_callback).",
61310
61600
  inputSchema: { path: string2().describe("Source file path") }
61311
61601
  }, async ({ path }) => {
61312
- const code = readFileSync26(path, "utf8");
61602
+ const code = readFileSync27(path, "utf8");
61313
61603
  const findings = detectComplexBugs(code, path);
61314
61604
  return {
61315
61605
  content: [{
@@ -61395,7 +61685,7 @@ ${statusLine}` : statusLine },
61395
61685
  let lang = language;
61396
61686
  if (path && !code) {
61397
61687
  try {
61398
- src = readFileSync26(path, "utf8");
61688
+ src = readFileSync27(path, "utf8");
61399
61689
  } catch {
61400
61690
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
61401
61691
  }
@@ -61644,15 +61934,15 @@ async function serve() {
61644
61934
 
61645
61935
  // src/index.ts
61646
61936
  init_cli();
61647
- import { readFileSync as readFileSync30, existsSync as existsSync15 } from "node:fs";
61648
- import { join as join27 } from "node:path";
61649
- import { fileURLToPath as fileURLToPath16 } from "node:url";
61650
- var ROOT11 = join27(fileURLToPath16(new URL(".", import.meta.url)), "..");
61937
+ import { readFileSync as readFileSync31, existsSync as existsSync15 } from "node:fs";
61938
+ import { join as join28 } from "node:path";
61939
+ import { fileURLToPath as fileURLToPath17 } from "node:url";
61940
+ var ROOT11 = join28(fileURLToPath17(new URL(".", import.meta.url)), "..");
61651
61941
  function readVersion() {
61652
61942
  try {
61653
- const p = join27(ROOT11, "package.json");
61943
+ const p = join28(ROOT11, "package.json");
61654
61944
  if (existsSync15(p))
61655
- return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
61945
+ return JSON.parse(readFileSync31(p, "utf8")).version ?? "1.0.0";
61656
61946
  } catch {}
61657
61947
  return "1.0.0";
61658
61948
  }