blitzstrike 2.4.61 → 2.4.63

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
@@ -16054,13 +16054,32 @@ function stripCommentsOnly(code) {
16054
16054
  return out.join("");
16055
16055
  }
16056
16056
  function detectDeserialization(code, file, out) {
16057
- if (!MAGIC_METHODS.test(code))
16058
- return;
16059
16057
  const ls = lines2(code);
16060
- const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
16061
- let m;
16062
- while ((m = re.exec(code)) !== null) {
16063
- const ln = lineNo2(code, m.index);
16058
+ if (MAGIC_METHODS.test(code)) {
16059
+ const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
16060
+ let m;
16061
+ while ((m = re.exec(code)) !== null) {
16062
+ const ln = lineNo2(code, m.index);
16063
+ const l = ls[ln - 1] ?? "";
16064
+ const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16065
+ `);
16066
+ if (!SOURCE_TOKENS3.test(l) && !SOURCE_TOKENS3.test(scope))
16067
+ continue;
16068
+ out.push({
16069
+ file,
16070
+ line: ln,
16071
+ type: "deserialization",
16072
+ category: "Deserialization → POP gadget chain",
16073
+ severity: "high",
16074
+ evidence: l.trim(),
16075
+ detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
16076
+ });
16077
+ }
16078
+ }
16079
+ const unsafeRe = /\b(pickle\.loads?|yaml\.load|yaml\.unsafe_load|ObjectInputStream|\.readObject)\s*\(/g;
16080
+ let u;
16081
+ while ((u = unsafeRe.exec(code)) !== null) {
16082
+ const ln = lineNo2(code, u.index);
16064
16083
  const l = ls[ln - 1] ?? "";
16065
16084
  const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16066
16085
  `);
@@ -16070,10 +16089,10 @@ function detectDeserialization(code, file, out) {
16070
16089
  file,
16071
16090
  line: ln,
16072
16091
  type: "deserialization",
16073
- category: "Deserialization → POP gadget chain",
16092
+ category: "Unsafe deserialization",
16074
16093
  severity: "high",
16075
16094
  evidence: l.trim(),
16076
- detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
16095
+ detail: `\`${u[1]}\` of attacker-controlled data — unsafe deserialization can execute arbitrary code directly (pickle/yaml/Java object streams).`
16077
16096
  });
16078
16097
  }
16079
16098
  }
@@ -16087,7 +16106,7 @@ function detectTypeJuggling(code, file, out) {
16087
16106
  const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
16088
16107
  `);
16089
16108
  const hasSource = SOURCE_TOKENS3.test(l) || SOURCE_TOKENS3.test(scope);
16090
- const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /\b(secret|token|nonce|hash)\b/i.test(l);
16109
+ const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /(secret|token|nonce|hash)\b/i.test(l);
16091
16110
  if (!hasSource || !hasHash)
16092
16111
  continue;
16093
16112
  out.push({
@@ -16138,6 +16157,34 @@ function detectMassAssignment(code, file, out) {
16138
16157
  detail: "`parse_str()` of user input into the symbol table — variable injection."
16139
16158
  });
16140
16159
  }
16160
+ const bindRe = /foreach\s*\(\s*\$_(POST|GET|REQUEST|COOKIE)\b[^)]*\)\s*\{[^}]*\$this->\$[A-Za-z_]\w*\s*=/g;
16161
+ while ((m = bindRe.exec(code)) !== null) {
16162
+ const ln = lineNo2(code, m.index);
16163
+ const l = ls[ln - 1] ?? "";
16164
+ out.push({
16165
+ file,
16166
+ line: ln,
16167
+ type: "mass_assignment",
16168
+ category: "Mass assignment (dynamic field binding)",
16169
+ severity: "high",
16170
+ evidence: l.trim(),
16171
+ detail: "Request keys are bound to object properties without an allowlist — attacker can set privileged fields (role, is_admin)."
16172
+ });
16173
+ }
16174
+ const createRe = /\.create\s*\(\s*(req\.body|req\.query|request\.body|request\.data|\$_(POST|REQUEST))\b/g;
16175
+ while ((m = createRe.exec(code)) !== null) {
16176
+ const ln = lineNo2(code, m.index);
16177
+ const l = ls[ln - 1] ?? "";
16178
+ out.push({
16179
+ file,
16180
+ line: ln,
16181
+ type: "mass_assignment",
16182
+ category: "Mass assignment (unrestricted model create)",
16183
+ severity: "high",
16184
+ evidence: l.trim(),
16185
+ detail: "A model is created directly from request data without an allowlist — attacker can set privileged fields."
16186
+ });
16187
+ }
16141
16188
  }
16142
16189
  function detectComplexBugs(code, file) {
16143
16190
  const out = [];
@@ -16171,7 +16218,7 @@ function detectComplexBugs(code, file) {
16171
16218
  }
16172
16219
  function detectPrototypePollution(code, file, out) {
16173
16220
  const ls = lines2(code);
16174
- const mergeRe = /\b(Object\.assign|_\.merge|_\.defaultsDeep|\.extend|\.merge|\.defaultsDeep|mergeDeep|deepMerge|_.set)\s*\(/g;
16221
+ const mergeRe = /(Object\.assign|_\.merge|_\.defaultsDeep|\.extend|\.merge|\.defaultsDeep|mergeDeep|deepMerge|_\.set)\s*\(/g;
16175
16222
  let m;
16176
16223
  while ((m = mergeRe.exec(code)) !== null) {
16177
16224
  const ln = lineNo2(code, m.index);
@@ -16193,7 +16240,7 @@ function detectPrototypePollution(code, file, out) {
16193
16240
  }
16194
16241
  function detectCrlfInjection(code, file, out) {
16195
16242
  const ls = lines2(code);
16196
- const headerRe = /\b(header|setHeader|addHeader|writeHead|appendHeader)\s*\(/g;
16243
+ const headerRe = /\b(header|setHeader|addHeader|writeHead|appendHeader|setcookie|setCookie|set_cookie)\s*\(/g;
16197
16244
  let m;
16198
16245
  while ((m = headerRe.exec(code)) !== null) {
16199
16246
  const ln = lineNo2(code, m.index);
@@ -16246,7 +16293,7 @@ function detectPathConfusion(code, file, out) {
16246
16293
  }
16247
16294
  function detectSsrF(code, file, out) {
16248
16295
  const ls = lines2(code);
16249
- const re = /\b(file_get_contents|fopen|file|readfile|curl_setopt|curl_init|fetch|axios|requests\.(get|post|put)|urllib\.request|http_get|wp_remote_(get|post)|new\s+URL\s*\(|got\(|http\.(get|request))\s*\(/g;
16296
+ const re = /\b(file_get_contents|fopen|file|readfile|curl_setopt|curl_init|fetch|axios\.(get|post|put|request|delete|head)|requests\.(get|post|put)|urllib\.request|http_get|wp_remote_(get|post)|new\s+URL\s*\(|got\(|http\.(get|request))\s*\(/g;
16250
16297
  let m;
16251
16298
  while ((m = re.exec(code)) !== null) {
16252
16299
  const ln = lineNo2(code, m.index);
@@ -16274,7 +16321,7 @@ function detectSsrF(code, file, out) {
16274
16321
  }
16275
16322
  function detectXxe(code, file, out) {
16276
16323
  const ls = lines2(code);
16277
- const re = /\b(simplexml_load_string|simplexml_load_file|new\s+SimpleXMLElement|->loadXML\s*\(|->load\s*\(|XMLReader\s*->\s*XML\s*\(|libxml_parse)\s*\(?/g;
16324
+ const re = /\b(simplexml_load_string|simplexml_load_file|new\s+SimpleXMLElement|->loadXML\s*\(|->load\s*\(|XMLReader\s*->\s*XML\s*\(|libxml_parse|DocumentBuilderFactory|SAXParserFactory|SAXReader)\s*\(?/g;
16278
16325
  let m;
16279
16326
  while ((m = re.exec(code)) !== null) {
16280
16327
  const ln = lineNo2(code, m.index);
@@ -16299,7 +16346,7 @@ function detectXxe(code, file, out) {
16299
16346
  }
16300
16347
  function detectSsti(code, file, out) {
16301
16348
  const ls = lines2(code);
16302
- const re = /\b(render_template_string|Template\s*\(|res\.render\s*\(|ejs\.(render|compile)\s*\(|_\.template\s*\(|render_string|twig\.render|Jinja2\(|jinja2\.Template)\s*\(?/g;
16349
+ const re = /(render_template_string|Template\s*\(|res\.render\s*\(|ejs\.(render|compile)\s*\(|_\.template\s*\(|render_string|twig->render|createTemplate\s*\(|Jinja2\(|jinja2\.Template|jinja2\.from_string|jinja2\.Environment)\s*\(?/g;
16303
16350
  let m;
16304
16351
  while ((m = re.exec(code)) !== null) {
16305
16352
  const ln = lineNo2(code, m.index);
@@ -16322,14 +16369,14 @@ function detectSsti(code, file, out) {
16322
16369
  function detectFileUploadRce(code, file, out) {
16323
16370
  code = stripCommentsOnly(code);
16324
16371
  const ls = lines2(code);
16325
- const re = /\b(move_uploaded_file|wp_upload_bits|->move\s*\(|storeAs\s*\(|->storeAs\s*\(|putFile\s*\()\s*\(/g;
16372
+ const re = /\b(move_uploaded_file|wp_upload_bits|->move|storeAs|->storeAs|putFile|\.save|uploadFile)\s*\(/g;
16326
16373
  let m;
16327
16374
  while ((m = re.exec(code)) !== null) {
16328
16375
  const ln = lineNo2(code, m.index);
16329
16376
  const l = ls[ln - 1] ?? "";
16330
16377
  const scope = ls.slice(Math.max(0, ln - 30), ln + 3).join(`
16331
16378
  `);
16332
- if (!/\$_FILES\b|tmp_name|getUploadedFile|uploadedFile|\['name'\]/i.test(scope))
16379
+ if (!/\$_FILES\b|tmp_name|getUploadedFile|uploadedFile|req\.files|\['name'\]|\.name/i.test(scope))
16333
16380
  continue;
16334
16381
  if (/(wp_check_filetype|PATHINFO_EXTENSION|wp_get_mime_types|allowed_?(types|extensions|exts)|mime_?(types|extensions)|in_array\s*\([^)]*\$(?:allowed|whitelist|ext)|wp_handle_upload\s*\(\s*[^)]*test_form\s*=>\s*true)/i.test(scope))
16335
16382
  continue;
@@ -16367,7 +16414,7 @@ function detectUnrestrictedUpload(code, file, out) {
16367
16414
  }
16368
16415
  function detectSqlInjection(code, file, out) {
16369
16416
  const ls = lines2(code);
16370
- const sinkRe = /\$wpdb->(get_results|get_col|get_var|get_row|query)\s*\(|->query\s*\(|DB::unprepared\s*\(|->whereRaw\s*\(|->selectRaw\s*\(/g;
16417
+ const sinkRe = /\$wpdb->(get_results|get_col|get_var|get_row|query)\s*\(|->query\s*\(|->execute\s*\(|mysqli_query\s*\(|pg_query\s*\(|DB::unprepared\s*\(|[->.]whereRaw\s*\(|[->.]selectRaw\s*\(/g;
16371
16418
  let m;
16372
16419
  while ((m = sinkRe.exec(code)) !== null) {
16373
16420
  const ln = lineNo2(code, m.index);
@@ -16378,7 +16425,7 @@ function detectSqlInjection(code, file, out) {
16378
16425
  continue;
16379
16426
  if (SOURCE_TOKENS3.test(scope))
16380
16427
  continue;
16381
- const inlineConcat = /\.\s*\$[A-Za-z_]\w*/.test(l);
16428
+ const inlineConcat = /(\.\s*\$[A-Za-z_]\w*|\+\s*[A-Za-z_$]\w*)/.test(l);
16382
16429
  const assignConcat = /(?:^|\n)\s*\$[A-Za-z_]\w*\s*(?:\.=|=)\s*[^;\n]*["'][^"']*["']\s*\.\s*\$[A-Za-z_]\w*/.test(scope);
16383
16430
  if (!inlineConcat && !assignConcat)
16384
16431
  continue;
@@ -16547,7 +16594,7 @@ function detectOauthMisconfig(code, file, out) {
16547
16594
  function detectGrpcReflection(code, file, out) {
16548
16595
  const ls = lines2(code);
16549
16596
  const clean = stripCommentsOnly(code);
16550
- const re = /\b(enable_server_reflection|RegisterReflectionService|reflection\.enable|grpc_reflection|reflection\.Register)\b/gi;
16597
+ const re = /grpc_reflection|enable_server_reflection|RegisterReflectionService|reflection\.enable|ServerReflection|ReflectionServicer|reflection\.Register/gi;
16551
16598
  let m;
16552
16599
  while ((m = re.exec(clean)) !== null) {
16553
16600
  const ln = lineNo2(code, m.index);
@@ -16567,7 +16614,7 @@ var init_complex_bugs = __esm(() => {
16567
16614
  init_security_state();
16568
16615
  init_framework_security();
16569
16616
  init_secrets();
16570
- SOURCE_TOKENS3 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data|body|headers|cookies|params)|getParameter\(|req\.(query|body|params|headers|cookies)/;
16617
+ SOURCE_TOKENS3 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data|body|headers|cookies|params|args|form|values|json)|getParameter\(|req\.(query|body|params|headers|cookies|args|form)|php:\/\/input/;
16571
16618
  MAGIC_METHODS = /\b(__destruct|__wakeup|__toString|__invoke|__call|__get|__set|__isset|__unset|__sleep)\s*\(/;
16572
16619
  SECRET_FIELD_RE = new RegExp(`\\b(${SECRET_FIELDS})\\s*(?:::|=>|=|:)\\s*`, "gi");
16573
16620
  SECRET_PLACEHOLDER_RE = /^(null|true|false|undefined|your_|xxx+|changeme|example|placeholder|test|secret|password|token|key|api|smtp|localhost|root|admin|pass|change|me|none|na|foo|bar|dummy)$/i;
@@ -16747,11 +16794,75 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
16747
16794
  ];
16748
16795
  });
16749
16796
 
16797
+ // src/variants.ts
16798
+ import { readFileSync as readFileSync19 } from "node:fs";
16799
+ import { join as join18, dirname as dirname4 } from "node:path";
16800
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
16801
+ function loadVariants() {
16802
+ try {
16803
+ const doc = JSON.parse(readFileSync19(join18(__dirname4, "..", "intelligence", "variants.json"), "utf8"));
16804
+ return doc.variants ?? [];
16805
+ } catch {
16806
+ return [];
16807
+ }
16808
+ }
16809
+ function scanVariants() {
16810
+ const variants = loadVariants().filter((v) => !CROSS_FILE_ONLY.has(v.class));
16811
+ const results = variants.map((v, i) => {
16812
+ const findings = detectComplexBugs(v.code, `variant-${i}.php`).filter((f) => f.type === v.class);
16813
+ return { id: `variant-${i}`, class: v.class, fired: findings.length > 0, note: v.note };
16814
+ });
16815
+ const per_class = {};
16816
+ for (const r of results) {
16817
+ const c = per_class[r.class] ??= { total: 0, fired: 0, missed: 0, coverage: 0 };
16818
+ c.total++;
16819
+ if (r.fired)
16820
+ c.fired++;
16821
+ else
16822
+ c.missed++;
16823
+ c.coverage = c.fired / c.total;
16824
+ }
16825
+ const fired = results.filter((r) => r.fired).length;
16826
+ return {
16827
+ total: results.length,
16828
+ fired,
16829
+ missed: results.length - fired,
16830
+ coverage: results.length > 0 ? fired / results.length : 0,
16831
+ per_class,
16832
+ results
16833
+ };
16834
+ }
16835
+ function variantCoverageText() {
16836
+ const r = scanVariants();
16837
+ const lines = [];
16838
+ lines.push(`Blitz Strike variant corpus`);
16839
+ lines.push(` ${r.fired}/${r.total} shapes detected (${(r.coverage * 100).toFixed(0)}%) · ${r.missed} missed`);
16840
+ lines.push(``);
16841
+ for (const [cls, c] of Object.entries(r.per_class)) {
16842
+ const mark = c.missed === 0 ? "OK " : "GAP";
16843
+ lines.push(` [${mark}] ${cls.padEnd(20)} ${c.fired}/${c.total}`);
16844
+ }
16845
+ if (r.missed > 0) {
16846
+ lines.push(``);
16847
+ for (const res of r.results.filter((x) => !x.fired)) {
16848
+ lines.push(` missed: ${res.class} — ${res.note}`);
16849
+ }
16850
+ }
16851
+ return lines.join(`
16852
+ `);
16853
+ }
16854
+ var __dirname4, CROSS_FILE_ONLY;
16855
+ var init_variants = __esm(() => {
16856
+ init_complex_bugs();
16857
+ __dirname4 = dirname4(fileURLToPath10(import.meta.url));
16858
+ CROSS_FILE_ONLY = new Set(["priv_esc"]);
16859
+ });
16860
+
16750
16861
  // src/sync.ts
16751
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync24, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
16752
- import { join as join21 } from "node:path";
16862
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync25, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
16863
+ import { join as join22 } from "node:path";
16753
16864
  import { homedir as homedir10 } from "node:os";
16754
- import { fileURLToPath as fileURLToPath11 } from "node:url";
16865
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
16755
16866
  import { spawnSync as spawnSync4 } from "node:child_process";
16756
16867
  function dirExists(p) {
16757
16868
  try {
@@ -16766,7 +16877,7 @@ function countFiles(dir) {
16766
16877
  const walk = (d) => {
16767
16878
  for (const e of readdirSync7(d, { withFileTypes: true })) {
16768
16879
  if (e.isDirectory())
16769
- walk(join21(d, e.name));
16880
+ walk(join22(d, e.name));
16770
16881
  else
16771
16882
  n++;
16772
16883
  }
@@ -16780,8 +16891,8 @@ function countFiles(dir) {
16780
16891
  function syncData() {
16781
16892
  if (!dirExists(DATA_ROOT2))
16782
16893
  mkdirSync9(DATA_ROOT2, { recursive: true });
16783
- const payloadsPath = join21(DATA_ROOT2, "payloads");
16784
- const templatesPath = join21(DATA_ROOT2, "templates");
16894
+ const payloadsPath = join22(DATA_ROOT2, "payloads");
16895
+ const templatesPath = join22(DATA_ROOT2, "templates");
16785
16896
  const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
16786
16897
  const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
16787
16898
  if (hasPayloads && hasTemplates) {
@@ -16791,8 +16902,8 @@ function syncData() {
16791
16902
  data_root: DATA_ROOT2
16792
16903
  };
16793
16904
  }
16794
- const localPayloads = join21(ROOT7, "payloads");
16795
- const localTemplates = join21(ROOT7, "templates");
16905
+ const localPayloads = join22(ROOT7, "payloads");
16906
+ const localTemplates = join22(ROOT7, "templates");
16796
16907
  let payloadsSynced = false;
16797
16908
  let templatesSynced = false;
16798
16909
  if (!hasPayloads && dirExists(localPayloads)) {
@@ -16805,7 +16916,7 @@ function syncData() {
16805
16916
  }
16806
16917
  const gitOk = spawnSync4("git", ["--version"]).status === 0;
16807
16918
  if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
16808
- const tmp = join21(DATA_ROOT2, ".tmp-clone");
16919
+ const tmp = join22(DATA_ROOT2, ".tmp-clone");
16809
16920
  if (dirExists(tmp))
16810
16921
  rmSync4(tmp, { recursive: true, force: true });
16811
16922
  const r = spawnSync4("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
@@ -16813,11 +16924,11 @@ function syncData() {
16813
16924
  const sparse = spawnSync4("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
16814
16925
  if (sparse.status === 0) {
16815
16926
  if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
16816
- copyDir(join21(tmp, "payloads"), payloadsPath);
16927
+ copyDir(join22(tmp, "payloads"), payloadsPath);
16817
16928
  payloadsSynced = true;
16818
16929
  }
16819
16930
  if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
16820
- copyDir(join21(tmp, "templates"), templatesPath);
16931
+ copyDir(join22(tmp, "templates"), templatesPath);
16821
16932
  templatesSynced = true;
16822
16933
  }
16823
16934
  }
@@ -16839,18 +16950,18 @@ function copyDir(src, dst) {
16839
16950
  function manualCopy(src, dst) {
16840
16951
  mkdirSync9(dst, { recursive: true });
16841
16952
  for (const e of readdirSync7(src, { withFileTypes: true })) {
16842
- const s = join21(src, e.name);
16843
- const d = join21(dst, e.name);
16953
+ const s = join22(src, e.name);
16954
+ const d = join22(dst, e.name);
16844
16955
  if (e.isDirectory())
16845
16956
  manualCopy(s, d);
16846
16957
  else
16847
- writeFileSync8(d, readFileSync24(s));
16958
+ writeFileSync8(d, readFileSync25(s));
16848
16959
  }
16849
16960
  }
16850
16961
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
16851
16962
  var init_sync = __esm(() => {
16852
- ROOT7 = join21(fileURLToPath11(new URL(".", import.meta.url)), "..");
16853
- DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join21(homedir10(), ".blitzstrike", "data");
16963
+ ROOT7 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
16964
+ DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join22(homedir10(), ".blitzstrike", "data");
16854
16965
  });
16855
16966
 
16856
16967
  // src/update.ts
@@ -16895,15 +17006,15 @@ var init_update = __esm(() => {
16895
17006
  });
16896
17007
 
16897
17008
  // src/release.ts
16898
- import { readFileSync as readFileSync26, existsSync as existsSync12 } from "node:fs";
16899
- import { join as join22 } from "node:path";
16900
- import { fileURLToPath as fileURLToPath12 } from "node:url";
17009
+ import { readFileSync as readFileSync27, existsSync as existsSync12 } from "node:fs";
17010
+ import { join as join23 } from "node:path";
17011
+ import { fileURLToPath as fileURLToPath13 } from "node:url";
16901
17012
  import { execSync as execSync2 } from "node:child_process";
16902
17013
  function validateRelease() {
16903
17014
  const checks = [];
16904
17015
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
16905
17016
  try {
16906
- const changelog = readFileSync26(join22(ROOT8, "CHANGELOG.md"), "utf8");
17017
+ const changelog = readFileSync27(join23(ROOT8, "CHANGELOG.md"), "utf8");
16907
17018
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
16908
17019
  const changelogVersion = m?.[1] ?? null;
16909
17020
  checks.push({
@@ -16935,22 +17046,22 @@ function validateRelease() {
16935
17046
  return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
16936
17047
  }
16937
17048
  function lockfilePresent() {
16938
- return existsSync12(join22(ROOT8, "package-lock.json")) || existsSync12(join22(ROOT8, "bun.lock")) || existsSync12(join22(ROOT8, "bun.lockb"));
17049
+ return existsSync12(join23(ROOT8, "package-lock.json")) || existsSync12(join23(ROOT8, "bun.lock")) || existsSync12(join23(ROOT8, "bun.lockb"));
16939
17050
  }
16940
17051
  var ROOT8;
16941
17052
  var init_release = __esm(() => {
16942
17053
  init_version();
16943
- ROOT8 = join22(fileURLToPath12(new URL(".", import.meta.url)), "..");
17054
+ ROOT8 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
16944
17055
  });
16945
17056
 
16946
17057
  // src/dependency.ts
16947
- import { readFileSync as readFileSync27 } from "node:fs";
16948
- import { join as join23 } from "node:path";
16949
- import { fileURLToPath as fileURLToPath13 } from "node:url";
17058
+ import { readFileSync as readFileSync28 } from "node:fs";
17059
+ import { join as join24 } from "node:path";
17060
+ import { fileURLToPath as fileURLToPath14 } from "node:url";
16950
17061
  function checkDependencies() {
16951
17062
  let pkg = {};
16952
17063
  try {
16953
- pkg = JSON.parse(readFileSync27(join23(ROOT9, "package.json"), "utf8"));
17064
+ pkg = JSON.parse(readFileSync28(join24(ROOT9, "package.json"), "utf8"));
16954
17065
  } catch {
16955
17066
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
16956
17067
  }
@@ -16973,14 +17084,14 @@ function checkDependencies() {
16973
17084
  }
16974
17085
  var ROOT9;
16975
17086
  var init_dependency = __esm(() => {
16976
- ROOT9 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
17087
+ ROOT9 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
16977
17088
  });
16978
17089
 
16979
17090
  // src/cli.ts
16980
- import { readFileSync as readFileSync28, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
16981
- import { join as join24, dirname as dirname4 } from "node:path";
17091
+ import { readFileSync as readFileSync29, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
17092
+ import { join as join25, dirname as dirname5 } from "node:path";
16982
17093
  import { homedir as homedir11 } from "node:os";
16983
- import { fileURLToPath as fileURLToPath14 } from "node:url";
17094
+ import { fileURLToPath as fileURLToPath15 } from "node:url";
16984
17095
  import { spawnSync as spawnSync5 } from "node:child_process";
16985
17096
  function which(cmd) {
16986
17097
  return spawnSync5("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", cmd]).status === 0;
@@ -17023,7 +17134,7 @@ async function runDoctor() {
17023
17134
  detail: h1 ? `X-HackerOne-Research: ${h1}` : "H1_USERNAME not set",
17024
17135
  fix: h1 ? undefined : "export H1_USERNAME=<your-hackerone-username> (adds X-HackerOne-Research to outbound requests)"
17025
17136
  });
17026
- const dataOk = existsSync13(join24(ROOT10, "chains.json")) && existsSync13(join24(ROOT10, "tools-catalog.json"));
17137
+ const dataOk = existsSync13(join25(ROOT10, "chains.json")) && existsSync13(join25(ROOT10, "tools-catalog.json"));
17027
17138
  issues.push({
17028
17139
  name: "Data layers (chains + tools-catalog)",
17029
17140
  status: dataOk ? "ok" : "fail",
@@ -17074,28 +17185,28 @@ BlitzStrike — ${verdict}`);
17074
17185
  function resolveCommand() {
17075
17186
  if (which("blitzstrike"))
17076
17187
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
17077
- const src = join24(ROOT10, "src", "index.ts");
17188
+ const src = join25(ROOT10, "src", "index.ts");
17078
17189
  if (which("bun") && existsSync13(src))
17079
17190
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
17080
17191
  if (which("npx"))
17081
17192
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17082
17193
  if (which("bunx"))
17083
17194
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
17084
- const dist = join24(ROOT10, "dist", "index.js");
17195
+ const dist = join25(ROOT10, "dist", "index.js");
17085
17196
  if (existsSync13(dist))
17086
17197
  return { command: "bun", args: [dist, "serve", "--mcp"] };
17087
17198
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
17088
17199
  }
17089
17200
  function readJson(p) {
17090
17201
  try {
17091
- return JSON.parse(readFileSync28(p, "utf8"));
17202
+ return JSON.parse(readFileSync29(p, "utf8"));
17092
17203
  } catch {
17093
17204
  return null;
17094
17205
  }
17095
17206
  }
17096
17207
  function jsonMcpServersWrite(p) {
17097
17208
  return (command, args) => {
17098
- const dir = dirname4(p);
17209
+ const dir = dirname5(p);
17099
17210
  if (!existsSync13(dir))
17100
17211
  mkdirSync10(dir, { recursive: true });
17101
17212
  const existing = readJson(p) ?? {};
@@ -17108,7 +17219,7 @@ function jsonMcpServersWrite(p) {
17108
17219
  }
17109
17220
  function opencodeWrite(p) {
17110
17221
  return (command, args) => {
17111
- const dir = dirname4(p);
17222
+ const dir = dirname5(p);
17112
17223
  if (!existsSync13(dir))
17113
17224
  mkdirSync10(dir, { recursive: true });
17114
17225
  const existing = readJson(p) ?? {};
@@ -17121,26 +17232,26 @@ function opencodeWrite(p) {
17121
17232
  };
17122
17233
  }
17123
17234
  function copyOpenCodeAgents() {
17124
- const srcDir = join24(ROOT10, "opencode-agents");
17125
- const dstDir = join24(homedir11(), ".config", "opencode", "agents");
17235
+ const srcDir = join25(ROOT10, "opencode-agents");
17236
+ const dstDir = join25(homedir11(), ".config", "opencode", "agents");
17126
17237
  if (!existsSync13(srcDir))
17127
17238
  return;
17128
17239
  if (!existsSync13(dstDir))
17129
17240
  mkdirSync10(dstDir, { recursive: true });
17130
17241
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
17131
- const src = join24(srcDir, f);
17132
- const dst = join24(dstDir, f);
17242
+ const src = join25(srcDir, f);
17243
+ const dst = join25(dstDir, f);
17133
17244
  if (existsSync13(src)) {
17134
- writeFileSync9(dst, readFileSync28(src, "utf8"));
17245
+ writeFileSync9(dst, readFileSync29(src, "utf8"));
17135
17246
  }
17136
17247
  }
17137
17248
  }
17138
17249
  function codexWrite(p) {
17139
17250
  return (command, args) => {
17140
- const dir = dirname4(p);
17251
+ const dir = dirname5(p);
17141
17252
  if (!existsSync13(dir))
17142
17253
  mkdirSync10(dir, { recursive: true });
17143
- let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
17254
+ let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17144
17255
  if (!existing.trimEnd().endsWith(`
17145
17256
  `))
17146
17257
  existing += `
@@ -17156,10 +17267,10 @@ args = ${JSON.stringify(args)}
17156
17267
  }
17157
17268
  function hermesWrite(p) {
17158
17269
  return (command, args) => {
17159
- const dir = dirname4(p);
17270
+ const dir = dirname5(p);
17160
17271
  if (!existsSync13(dir))
17161
17272
  mkdirSync10(dir, { recursive: true });
17162
- let existing = existsSync13(p) ? readFileSync28(p, "utf8") : "";
17273
+ let existing = existsSync13(p) ? readFileSync29(p, "utf8") : "";
17163
17274
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
17164
17275
  if (!existing.trimEnd().endsWith(`
17165
17276
  `))
@@ -17186,74 +17297,74 @@ mcp_servers:
17186
17297
  function detectAgents() {
17187
17298
  const home = homedir11();
17188
17299
  const agents = [];
17189
- const claudeJson = join24(home, ".claude.json");
17300
+ const claudeJson = join25(home, ".claude.json");
17190
17301
  agents.push({
17191
17302
  name: "Claude Code",
17192
17303
  detectPath: claudeJson,
17193
17304
  installed: () => existsSync13(claudeJson) || which("claude"),
17194
17305
  write: jsonMcpServersWrite(claudeJson)
17195
17306
  });
17196
- const claudeDesktop = join24(home, ".config", "Claude", "claude_desktop_config.json");
17307
+ const claudeDesktop = join25(home, ".config", "Claude", "claude_desktop_config.json");
17197
17308
  agents.push({
17198
17309
  name: "Claude Desktop",
17199
17310
  detectPath: claudeDesktop,
17200
17311
  installed: () => existsSync13(claudeDesktop),
17201
17312
  write: jsonMcpServersWrite(claudeDesktop)
17202
17313
  });
17203
- const cursor = join24(home, ".cursor", "mcp.json");
17314
+ const cursor = join25(home, ".cursor", "mcp.json");
17204
17315
  agents.push({
17205
17316
  name: "Cursor",
17206
17317
  detectPath: cursor,
17207
- installed: () => existsSync13(join24(home, ".cursor")) || which("cursor"),
17318
+ installed: () => existsSync13(join25(home, ".cursor")) || which("cursor"),
17208
17319
  write: jsonMcpServersWrite(cursor)
17209
17320
  });
17210
- const opencode = join24(home, ".config", "opencode", "opencode.jsonc");
17321
+ const opencode = join25(home, ".config", "opencode", "opencode.jsonc");
17211
17322
  agents.push({
17212
17323
  name: "OpenCode",
17213
17324
  detectPath: opencode,
17214
- installed: () => existsSync13(join24(home, ".config", "opencode")) || which("opencode"),
17325
+ installed: () => existsSync13(join25(home, ".config", "opencode")) || which("opencode"),
17215
17326
  write: opencodeWrite(opencode)
17216
17327
  });
17217
- const codex = join24(home, ".codex", "config.toml");
17328
+ const codex = join25(home, ".codex", "config.toml");
17218
17329
  agents.push({
17219
17330
  name: "Codex",
17220
17331
  detectPath: codex,
17221
- installed: () => existsSync13(join24(home, ".codex")) || which("codex"),
17332
+ installed: () => existsSync13(join25(home, ".codex")) || which("codex"),
17222
17333
  write: codexWrite(codex)
17223
17334
  });
17224
- const hermes = join24(home, ".hermes", "config.yaml");
17335
+ const hermes = join25(home, ".hermes", "config.yaml");
17225
17336
  agents.push({
17226
17337
  name: "Hermes",
17227
17338
  detectPath: hermes,
17228
- installed: () => existsSync13(join24(home, ".hermes")) || which("hermes"),
17339
+ installed: () => existsSync13(join25(home, ".hermes")) || which("hermes"),
17229
17340
  write: hermesWrite(hermes)
17230
17341
  });
17231
- const gemini = join24(home, ".gemini", "settings.json");
17342
+ const gemini = join25(home, ".gemini", "settings.json");
17232
17343
  agents.push({
17233
17344
  name: "Gemini CLI",
17234
17345
  detectPath: gemini,
17235
- installed: () => existsSync13(join24(home, ".gemini")) || which("gemini"),
17346
+ installed: () => existsSync13(join25(home, ".gemini")) || which("gemini"),
17236
17347
  write: jsonMcpServersWrite(gemini)
17237
17348
  });
17238
- const windsurf = join24(home, ".codeium", "windsurf", "mcp_config.json");
17349
+ const windsurf = join25(home, ".codeium", "windsurf", "mcp_config.json");
17239
17350
  agents.push({
17240
17351
  name: "Windsurf",
17241
17352
  detectPath: windsurf,
17242
- installed: () => existsSync13(join24(home, ".codeium")) || which("windsurf"),
17353
+ installed: () => existsSync13(join25(home, ".codeium")) || which("windsurf"),
17243
17354
  write: jsonMcpServersWrite(windsurf)
17244
17355
  });
17245
- const copilot = join24(home, ".copilot", "mcp.json");
17356
+ const copilot = join25(home, ".copilot", "mcp.json");
17246
17357
  agents.push({
17247
17358
  name: "Copilot",
17248
17359
  detectPath: copilot,
17249
- installed: () => existsSync13(join24(home, ".copilot")),
17360
+ installed: () => existsSync13(join25(home, ".copilot")),
17250
17361
  write: jsonMcpServersWrite(copilot)
17251
17362
  });
17252
- const cline = join24(home, ".cline", "mcp_settings.json");
17363
+ const cline = join25(home, ".cline", "mcp_settings.json");
17253
17364
  agents.push({
17254
17365
  name: "Cline",
17255
17366
  detectPath: cline,
17256
- installed: () => existsSync13(join24(home, ".cline")),
17367
+ installed: () => existsSync13(join25(home, ".cline")),
17257
17368
  write: jsonMcpServersWrite(cline)
17258
17369
  });
17259
17370
  return agents;
@@ -17371,7 +17482,7 @@ var init_cli = __esm(() => {
17371
17482
  init_release();
17372
17483
  init_dependency();
17373
17484
  init_mcp_status();
17374
- ROOT10 = join24(fileURLToPath14(new URL(".", import.meta.url)), "..");
17485
+ ROOT10 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
17375
17486
  });
17376
17487
 
17377
17488
  // node_modules/zod/v3/helpers/util.js
@@ -32009,7 +32120,7 @@ class StdioServerTransport {
32009
32120
 
32010
32121
  // src/server.ts
32011
32122
  init_scanner();
32012
- import { readFileSync as readFileSync25 } from "node:fs";
32123
+ import { readFileSync as readFileSync26 } from "node:fs";
32013
32124
 
32014
32125
  // src/recon.ts
32015
32126
  var TIMEOUT_MS = 25000;
@@ -57675,20 +57786,21 @@ function dataRefresh() {
57675
57786
 
57676
57787
  // src/server.ts
57677
57788
  init_verify_harness();
57789
+ init_variants();
57678
57790
 
57679
57791
  // src/benchmark.ts
57680
- import { readFileSync as readFileSync19, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57681
- import { join as join18 } from "node:path";
57682
- import { fileURLToPath as fileURLToPath10 } from "node:url";
57792
+ import { readFileSync as readFileSync20, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
57793
+ import { join as join19 } from "node:path";
57794
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
57683
57795
  import { tmpdir as tmpdir3, homedir as homedir8 } from "node:os";
57684
57796
  init_complex_bugs();
57685
- var ROOT5 = join18(fileURLToPath10(new URL(".", import.meta.url)), "..");
57686
- var CORPUS = join18(ROOT5, "bench", "corpus");
57687
- var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join18(homedir8(), ".blitzstrike", "corpus");
57797
+ var ROOT5 = join19(fileURLToPath11(new URL(".", import.meta.url)), "..");
57798
+ var CORPUS = join19(ROOT5, "bench", "corpus");
57799
+ var CAPTURED_CORPUS_DIR = process.env.BLITZSTRIKE_CORPUS_DIR ?? join19(homedir8(), ".blitzstrike", "corpus");
57688
57800
  function parseCorpusFile(path) {
57689
57801
  let text;
57690
57802
  try {
57691
- text = readFileSync19(path, "utf8");
57803
+ text = readFileSync20(path, "utf8");
57692
57804
  } catch {
57693
57805
  return null;
57694
57806
  }
@@ -57744,7 +57856,7 @@ function loadDir(dir) {
57744
57856
  for (const f of readdirSync5(dir)) {
57745
57857
  if (!f.endsWith(".md"))
57746
57858
  continue;
57747
- const e = parseCorpusFile(join18(dir, f));
57859
+ const e = parseCorpusFile(join19(dir, f));
57748
57860
  if (e)
57749
57861
  entries.push(e);
57750
57862
  }
@@ -57771,7 +57883,7 @@ function captureFalsePositive(input) {
57771
57883
  input.code,
57772
57884
  "```"
57773
57885
  ].filter((l) => l !== null);
57774
- const path = join18(CAPTURED_CORPUS_DIR, `${id}.md`);
57886
+ const path = join19(CAPTURED_CORPUS_DIR, `${id}.md`);
57775
57887
  writeFileSync6(path, lines.join(`
57776
57888
  `) + `
57777
57889
  `, "utf8");
@@ -57793,7 +57905,7 @@ function detect(entry) {
57793
57905
  }
57794
57906
  const lang = entry.language;
57795
57907
  if (lang === "php") {
57796
- const tmp = join18(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57908
+ const tmp = join19(tmpdir3(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
57797
57909
  writeFileSync6(tmp, entry.code);
57798
57910
  const r = analyzeDataFlow2(tmp);
57799
57911
  rmSync3(tmp, { force: true });
@@ -57862,12 +57974,12 @@ function runBenchmark() {
57862
57974
  }
57863
57975
 
57864
57976
  // src/obligations.ts
57865
- import { readFileSync as readFileSync20, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
57866
- import { join as join19 } from "node:path";
57977
+ import { readFileSync as readFileSync21, appendFileSync as appendFileSync4, mkdirSync as mkdirSync8, existsSync as existsSync10, writeFileSync as writeFileSync7 } from "node:fs";
57978
+ import { join as join20 } from "node:path";
57867
57979
  import { createHash as createHash6 } from "node:crypto";
57868
57980
  import { homedir as homedir9 } from "node:os";
57869
- var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join19(homedir9(), ".blitzstrike");
57870
- var OBLIGATION_PATH = join19(HOME_DIR4, "obligations.jsonl");
57981
+ var HOME_DIR4 = process.env.BLITZSTRIKE_HOME ?? join20(homedir9(), ".blitzstrike");
57982
+ var OBLIGATION_PATH = join20(HOME_DIR4, "obligations.jsonl");
57871
57983
  function ensureDir3() {
57872
57984
  if (!existsSync10(HOME_DIR4))
57873
57985
  mkdirSync8(HOME_DIR4, { recursive: true });
@@ -57880,7 +57992,7 @@ function loadObligations() {
57880
57992
  return [];
57881
57993
  try {
57882
57994
  const out = [];
57883
- for (const line of readFileSync20(OBLIGATION_PATH, "utf8").split(`
57995
+ for (const line of readFileSync21(OBLIGATION_PATH, "utf8").split(`
57884
57996
  `)) {
57885
57997
  const t = line.trim();
57886
57998
  if (!t)
@@ -57984,11 +58096,11 @@ init_framework_security();
57984
58096
  // src/variant-scan.ts
57985
58097
  init_scanner();
57986
58098
  init_complex_bugs();
57987
- import { readFileSync as readFileSync22 } from "node:fs";
58099
+ import { readFileSync as readFileSync23 } from "node:fs";
57988
58100
 
57989
58101
  // src/cross-file.ts
57990
58102
  init_scanner();
57991
- import { readFileSync as readFileSync21 } from "node:fs";
58103
+ import { readFileSync as readFileSync22 } from "node:fs";
57992
58104
  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)/;
57993
58105
  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*\(/;
57994
58106
  var ROLE_META_RE = /update_user_meta\s*\(\s*[^,]+,\s*['"](wp_capabilities|role|roles|capabilities|wp_user_level)['"]/;
@@ -58040,7 +58152,7 @@ function detectCrossFilePrivesc(root) {
58040
58152
  for (const file of files) {
58041
58153
  let code;
58042
58154
  try {
58043
- code = stripComments(readFileSync21(file, "utf8"));
58155
+ code = stripComments(readFileSync22(file, "utf8"));
58044
58156
  } catch {
58045
58157
  continue;
58046
58158
  }
@@ -58081,7 +58193,7 @@ function detectCrossFilePrivesc(root) {
58081
58193
  for (const file of files) {
58082
58194
  let code;
58083
58195
  try {
58084
- code = stripComments(readFileSync21(file, "utf8"));
58196
+ code = stripComments(readFileSync22(file, "utf8"));
58085
58197
  } catch {
58086
58198
  continue;
58087
58199
  }
@@ -58104,7 +58216,7 @@ function detectCrossFilePrivesc(root) {
58104
58216
  for (const file of files) {
58105
58217
  let code;
58106
58218
  try {
58107
- code = stripComments(readFileSync21(file, "utf8"));
58219
+ code = stripComments(readFileSync22(file, "utf8"));
58108
58220
  } catch {
58109
58221
  continue;
58110
58222
  }
@@ -58219,7 +58331,7 @@ function variantScan(root, opts = {}) {
58219
58331
  for (const file of files) {
58220
58332
  let code;
58221
58333
  try {
58222
- code = readFileSync22(file, "utf8");
58334
+ code = readFileSync23(file, "utf8");
58223
58335
  } catch {
58224
58336
  continue;
58225
58337
  }
@@ -58879,17 +58991,17 @@ function analyzeJwt(token, opts = {}) {
58879
58991
  }
58880
58992
 
58881
58993
  // src/enterprise-benchmark.ts
58882
- import { existsSync as existsSync11, readFileSync as readFileSync23, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
58883
- import { join as join20, resolve } from "node:path";
58994
+ import { existsSync as existsSync11, readFileSync as readFileSync24, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
58995
+ import { join as join21, resolve } from "node:path";
58884
58996
  init_complex_bugs();
58885
58997
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
58886
58998
  var ENTERPRISE_FIXTURES = [
58887
- { id: "php-laravel", dir: join20(ROOT6, "php-laravel"), expected_vulns: 4 },
58888
- { id: "js-express", dir: join20(ROOT6, "js-express"), expected_vulns: 2 },
58889
- { id: "py-django", dir: join20(ROOT6, "py-django"), expected_vulns: 2 },
58890
- { id: "java-spring", dir: join20(ROOT6, "java-spring"), expected_vulns: 2 },
58891
- { id: "wp-batch-route", dir: join20(ROOT6, "wp-batch-route"), expected_vulns: 3 },
58892
- { id: "php-complex", dir: join20(ROOT6, "php-complex"), expected_vulns: 4 }
58999
+ { id: "php-laravel", dir: join21(ROOT6, "php-laravel"), expected_vulns: 4 },
59000
+ { id: "js-express", dir: join21(ROOT6, "js-express"), expected_vulns: 2 },
59001
+ { id: "py-django", dir: join21(ROOT6, "py-django"), expected_vulns: 2 },
59002
+ { id: "java-spring", dir: join21(ROOT6, "java-spring"), expected_vulns: 2 },
59003
+ { id: "wp-batch-route", dir: join21(ROOT6, "wp-batch-route"), expected_vulns: 3 },
59004
+ { id: "php-complex", dir: join21(ROOT6, "php-complex"), expected_vulns: 4 }
58893
59005
  ];
58894
59006
  function sourceFiles(root) {
58895
59007
  const out = [];
@@ -58901,7 +59013,7 @@ function sourceFiles(root) {
58901
59013
  return;
58902
59014
  }
58903
59015
  for (const e of entries) {
58904
- const p = join20(dir, e);
59016
+ const p = join21(dir, e);
58905
59017
  let st;
58906
59018
  try {
58907
59019
  st = statSync3(p);
@@ -58922,7 +59034,7 @@ function detectFixture(dir) {
58922
59034
  for (const f of sourceFiles(dir)) {
58923
59035
  let code;
58924
59036
  try {
58925
- code = readFileSync23(f, "utf8");
59037
+ code = readFileSync24(f, "utf8");
58926
59038
  } catch {
58927
59039
  continue;
58928
59040
  }
@@ -59117,7 +59229,7 @@ function createServer() {
59117
59229
  ` + `- 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).
59118
59230
  ` + `- 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.
59119
59231
 
59120
- ` + "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, and citable references, so a raw hit becomes a class-aware finding. 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) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). 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.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
59232
+ ` + "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. 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) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). 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.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
59121
59233
  ` + `- 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.
59122
59234
  ` + `- 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.
59123
59235
  ` + "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."
@@ -59542,6 +59654,13 @@ ${statusLine}` : statusLine },
59542
59654
  }, async () => {
59543
59655
  return { content: [{ type: "text", text: JSON.stringify(runVerificationHarness()) }] };
59544
59656
  });
59657
+ server.registerTool("scan_variants", {
59658
+ title: "Variant corpus coverage scan",
59659
+ description: "VERIFY: sweep every real-world code shape (intelligence/variants.json) against its class's detector and report per-class coverage. A shape that does not fire is a coverage gap (false negative) — use this to find detector blind spots, then fix the detector.",
59660
+ inputSchema: {}
59661
+ }, async () => {
59662
+ return { content: [{ type: "text", text: JSON.stringify(scanVariants()) }] };
59663
+ });
59545
59664
  server.registerTool("fofa_search", {
59546
59665
  title: "FOFA asset search",
59547
59666
  description: "STRIKE: search the FOFA asset index (needs FOFA_EMAIL + FOFA_KEY).",
@@ -59926,7 +60045,7 @@ ${statusLine}` : statusLine },
59926
60045
  let src = code;
59927
60046
  if (!src && path) {
59928
60047
  try {
59929
- src = readFileSync25(path, "utf8");
60048
+ src = readFileSync26(path, "utf8");
59930
60049
  } catch {
59931
60050
  return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
59932
60051
  }
@@ -60544,7 +60663,7 @@ ${statusLine}` : statusLine },
60544
60663
  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.",
60545
60664
  inputSchema: { path: string2().describe("Source file path (PHP)") }
60546
60665
  }, async ({ path }) => {
60547
- const code = readFileSync25(path, "utf8");
60666
+ const code = readFileSync26(path, "utf8");
60548
60667
  const findings = detectRouteConfusion(code, path);
60549
60668
  return {
60550
60669
  content: [{
@@ -60558,7 +60677,7 @@ ${statusLine}` : statusLine },
60558
60677
  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).",
60559
60678
  inputSchema: { path: string2().describe("Source file path") }
60560
60679
  }, async ({ path }) => {
60561
- const code = readFileSync25(path, "utf8");
60680
+ const code = readFileSync26(path, "utf8");
60562
60681
  const findings = detectComplexBugs(code, path);
60563
60682
  return {
60564
60683
  content: [{
@@ -60644,7 +60763,7 @@ ${statusLine}` : statusLine },
60644
60763
  let lang = language;
60645
60764
  if (path && !code) {
60646
60765
  try {
60647
- src = readFileSync25(path, "utf8");
60766
+ src = readFileSync26(path, "utf8");
60648
60767
  } catch {
60649
60768
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
60650
60769
  }
@@ -60885,15 +61004,15 @@ async function serve() {
60885
61004
 
60886
61005
  // src/index.ts
60887
61006
  init_cli();
60888
- import { readFileSync as readFileSync29, existsSync as existsSync14 } from "node:fs";
60889
- import { join as join25 } from "node:path";
60890
- import { fileURLToPath as fileURLToPath15 } from "node:url";
60891
- var ROOT11 = join25(fileURLToPath15(new URL(".", import.meta.url)), "..");
61007
+ import { readFileSync as readFileSync30, existsSync as existsSync14 } from "node:fs";
61008
+ import { join as join26 } from "node:path";
61009
+ import { fileURLToPath as fileURLToPath16 } from "node:url";
61010
+ var ROOT11 = join26(fileURLToPath16(new URL(".", import.meta.url)), "..");
60892
61011
  function readVersion() {
60893
61012
  try {
60894
- const p = join25(ROOT11, "package.json");
61013
+ const p = join26(ROOT11, "package.json");
60895
61014
  if (existsSync14(p))
60896
- return JSON.parse(readFileSync29(p, "utf8")).version ?? "1.0.0";
61015
+ return JSON.parse(readFileSync30(p, "utf8")).version ?? "1.0.0";
60897
61016
  } catch {}
60898
61017
  return "1.0.0";
60899
61018
  }
@@ -60964,9 +61083,13 @@ async function main() {
60964
61083
  }
60965
61084
  if (args[0] === "verify") {
60966
61085
  await Promise.resolve().then(() => init_verify_harness());
61086
+ await Promise.resolve().then(() => init_variants());
60967
61087
  console.log(verificationReportText());
61088
+ console.log(`
61089
+ ` + variantCoverageText());
60968
61090
  const r = runVerificationHarness();
60969
- if (r.failed > 0)
61091
+ const v = scanVariants();
61092
+ if (r.failed > 0 || v.missed > 0)
60970
61093
  process.exit(1);
60971
61094
  return;
60972
61095
  }