blitzstrike 2.4.33 → 2.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to Blitz Strike are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.4.35] — 2026-09-14
9
+
10
+ ### Added — Variant Mining (confirm once, weaponize everywhere)
11
+
12
+ - New `src/variant-scan.ts`: mass-scan an install base (plugin/theme/repo tree)
13
+ with ALL detectors (taint, complex-bugs incl. wrong-context sanitization +
14
+ missing authz/nonce, route-confusion) and report hits grouped by a DERIVED
15
+ signature (`<detector>:<type>`).
16
+ - New `variant_scan` tool: pass `signature` to mine for ONE bug family — the
17
+ pattern that turns one confirmed CVE into hundreds of variants across 10k+
18
+ files.
19
+
20
+ ## [2.4.34] — 2026-09-14
21
+
22
+ ### Added — Framework Security Knowledge Graph (WordPress authz/nonce)
23
+
24
+ - New `src/framework-security.ts`: WordPress security primitives encoded as DATA
25
+ (auth, nonce, entry-point hooks, sanitizers), exposed via `framework_knowledge`.
26
+ - `detectMissingAuthz` (wired into `complex_scan`) finds missing capability /
27
+ nonce checks on WP entry points — `wp_ajax_*` / `wp_ajax_nopriv_*` /
28
+ `admin_post_*` / `register_rest_route` without `current_user_can`,
29
+ `wp_verify_nonce`, or `permission_callback` (auth bypass + CSRF + privesc,
30
+ the class that dominates real WordPress CVE reports).
31
+
8
32
  ## [2.4.33] — 2026-09-14
9
33
 
10
34
  ### Added — Security-State Lattice (beyond binary taint)
@@ -0,0 +1,15 @@
1
+ - id: cb-authz-safe
2
+ - language: php
3
+ - vulnerable: false
4
+ - detector: complex_bugs
5
+
6
+ ```php
7
+ <?php
8
+ add_action('wp_ajax_delete_user', 'safe_handler');
9
+ function safe_handler() {
10
+ check_ajax_referer('nonce');
11
+ if (!current_user_can('manage_options')) { wp_die('no'); }
12
+ $id = intval($_POST['id']);
13
+ $wpdb->query($wpdb->prepare("DELETE FROM users WHERE id=%d", $id));
14
+ }
15
+ ```
@@ -0,0 +1,13 @@
1
+ - id: cb-authz-vuln
2
+ - language: php
3
+ - vulnerable: true
4
+ - detector: complex_bugs
5
+
6
+ ```php
7
+ <?php
8
+ add_action('wp_ajax_nopriv_delete_user', 'delete_user_handler');
9
+ function delete_user_handler() {
10
+ $id = $_POST['id'];
11
+ $wpdb->query("DELETE FROM users WHERE id=$id");
12
+ }
13
+ ```
package/dist/index.js CHANGED
@@ -15493,7 +15493,7 @@ async function checkMcpServers() {
15493
15493
  var init_mcp_status = () => {};
15494
15494
 
15495
15495
  // src/sync.ts
15496
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync20, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15496
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync21, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync4 } from "node:fs";
15497
15497
  import { join as join19 } from "node:path";
15498
15498
  import { homedir as homedir10 } from "node:os";
15499
15499
  import { fileURLToPath as fileURLToPath9 } from "node:url";
@@ -15589,7 +15589,7 @@ function manualCopy(src, dst) {
15589
15589
  if (e.isDirectory())
15590
15590
  manualCopy(s, d);
15591
15591
  else
15592
- writeFileSync8(d, readFileSync20(s));
15592
+ writeFileSync8(d, readFileSync21(s));
15593
15593
  }
15594
15594
  }
15595
15595
  var ROOT7, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
@@ -15640,7 +15640,7 @@ var init_update = __esm(() => {
15640
15640
  });
15641
15641
 
15642
15642
  // src/release.ts
15643
- import { readFileSync as readFileSync22, existsSync as existsSync12 } from "node:fs";
15643
+ import { readFileSync as readFileSync23, existsSync as existsSync12 } from "node:fs";
15644
15644
  import { join as join20 } from "node:path";
15645
15645
  import { fileURLToPath as fileURLToPath10 } from "node:url";
15646
15646
  import { execSync as execSync2 } from "node:child_process";
@@ -15648,7 +15648,7 @@ function validateRelease() {
15648
15648
  const checks = [];
15649
15649
  checks.push({ name: "package.json version", ok: true, detail: VERSION });
15650
15650
  try {
15651
- const changelog = readFileSync22(join20(ROOT8, "CHANGELOG.md"), "utf8");
15651
+ const changelog = readFileSync23(join20(ROOT8, "CHANGELOG.md"), "utf8");
15652
15652
  const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
15653
15653
  const changelogVersion = m?.[1] ?? null;
15654
15654
  checks.push({
@@ -15689,13 +15689,13 @@ var init_release = __esm(() => {
15689
15689
  });
15690
15690
 
15691
15691
  // src/dependency.ts
15692
- import { readFileSync as readFileSync23 } from "node:fs";
15692
+ import { readFileSync as readFileSync24 } from "node:fs";
15693
15693
  import { join as join21 } from "node:path";
15694
15694
  import { fileURLToPath as fileURLToPath11 } from "node:url";
15695
15695
  function checkDependencies() {
15696
15696
  let pkg = {};
15697
15697
  try {
15698
- pkg = JSON.parse(readFileSync23(join21(ROOT9, "package.json"), "utf8"));
15698
+ pkg = JSON.parse(readFileSync24(join21(ROOT9, "package.json"), "utf8"));
15699
15699
  } catch {
15700
15700
  return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
15701
15701
  }
@@ -15722,7 +15722,7 @@ var init_dependency = __esm(() => {
15722
15722
  });
15723
15723
 
15724
15724
  // src/cli.ts
15725
- import { readFileSync as readFileSync24, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
15725
+ import { readFileSync as readFileSync25, existsSync as existsSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10 } from "node:fs";
15726
15726
  import { join as join22, dirname as dirname2 } from "node:path";
15727
15727
  import { homedir as homedir11 } from "node:os";
15728
15728
  import { fileURLToPath as fileURLToPath12 } from "node:url";
@@ -15833,7 +15833,7 @@ function resolveCommand() {
15833
15833
  }
15834
15834
  function readJson(p) {
15835
15835
  try {
15836
- return JSON.parse(readFileSync24(p, "utf8"));
15836
+ return JSON.parse(readFileSync25(p, "utf8"));
15837
15837
  } catch {
15838
15838
  return null;
15839
15839
  }
@@ -15876,7 +15876,7 @@ function copyOpenCodeAgents() {
15876
15876
  const src = join22(srcDir, f);
15877
15877
  const dst = join22(dstDir, f);
15878
15878
  if (existsSync13(src)) {
15879
- writeFileSync9(dst, readFileSync24(src, "utf8"));
15879
+ writeFileSync9(dst, readFileSync25(src, "utf8"));
15880
15880
  }
15881
15881
  }
15882
15882
  }
@@ -15885,7 +15885,7 @@ function codexWrite(p) {
15885
15885
  const dir = dirname2(p);
15886
15886
  if (!existsSync13(dir))
15887
15887
  mkdirSync10(dir, { recursive: true });
15888
- let existing = existsSync13(p) ? readFileSync24(p, "utf8") : "";
15888
+ let existing = existsSync13(p) ? readFileSync25(p, "utf8") : "";
15889
15889
  if (!existing.trimEnd().endsWith(`
15890
15890
  `))
15891
15891
  existing += `
@@ -15904,7 +15904,7 @@ function hermesWrite(p) {
15904
15904
  const dir = dirname2(p);
15905
15905
  if (!existsSync13(dir))
15906
15906
  mkdirSync10(dir, { recursive: true });
15907
- let existing = existsSync13(p) ? readFileSync24(p, "utf8") : "";
15907
+ let existing = existsSync13(p) ? readFileSync25(p, "utf8") : "";
15908
15908
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
15909
15909
  if (!existing.trimEnd().endsWith(`
15910
15910
  `))
@@ -30754,7 +30754,7 @@ class StdioServerTransport {
30754
30754
 
30755
30755
  // src/server.ts
30756
30756
  init_scanner();
30757
- import { readFileSync as readFileSync21 } from "node:fs";
30757
+ import { readFileSync as readFileSync22 } from "node:fs";
30758
30758
 
30759
30759
  // src/recon.ts
30760
30760
  var TIMEOUT_MS = 25000;
@@ -56259,6 +56259,177 @@ function detectWrongSanitizer(code, file) {
56259
56259
  });
56260
56260
  }
56261
56261
 
56262
+ // src/framework-security.ts
56263
+ var WP_PRIMITIVES = {
56264
+ framework: "wordpress",
56265
+ auth_primitives: [
56266
+ "current_user_can",
56267
+ "current_user_can_edit_post",
56268
+ "current_user_can_for_blog",
56269
+ "current_user_can_edit_others_posts",
56270
+ "is_user_logged_in",
56271
+ "authorize"
56272
+ ],
56273
+ nonce_primitives: [
56274
+ "wp_verify_nonce",
56275
+ "check_ajax_referer",
56276
+ "check_admin_referer",
56277
+ "verify_nonce"
56278
+ ],
56279
+ entry_point_hooks: [
56280
+ "wp_ajax_",
56281
+ "wp_ajax_nopriv_",
56282
+ "admin_post_",
56283
+ "admin_post_nopriv_",
56284
+ "register_rest_route",
56285
+ "add_shortcode",
56286
+ "init",
56287
+ "rest_api_init"
56288
+ ],
56289
+ sanitizers: [
56290
+ "sanitize_text_field",
56291
+ "esc_sql",
56292
+ "esc_html",
56293
+ "esc_attr",
56294
+ "esc_url",
56295
+ "wp_kses",
56296
+ "wp_kses_post",
56297
+ "absint",
56298
+ "sanitize_email",
56299
+ "sanitize_file_name"
56300
+ ]
56301
+ };
56302
+ var FRAMEWORKS = {
56303
+ wordpress: WP_PRIMITIVES,
56304
+ wp: WP_PRIMITIVES
56305
+ };
56306
+ function frameworkKnowledge(framework = "wordpress") {
56307
+ return FRAMEWORKS[framework.toLowerCase()] ?? null;
56308
+ }
56309
+ function functionBody(code, name) {
56310
+ const idx = code.search(new RegExp(`function\\s+${name}\\s*\\(`));
56311
+ if (idx === -1)
56312
+ return null;
56313
+ const open = code.indexOf("{", idx);
56314
+ if (open === -1)
56315
+ return null;
56316
+ let depth = 0;
56317
+ for (let i = open;i < code.length; i++) {
56318
+ if (code[i] === "{")
56319
+ depth++;
56320
+ else if (code[i] === "}") {
56321
+ depth--;
56322
+ if (depth === 0)
56323
+ return code.slice(open, i + 1);
56324
+ }
56325
+ }
56326
+ return code.slice(open);
56327
+ }
56328
+ function lineNoOf(code, pos) {
56329
+ let n = 1;
56330
+ for (let i = 0;i < pos; i++)
56331
+ if (code.charCodeAt(i) === 10)
56332
+ n++;
56333
+ return n;
56334
+ }
56335
+ var INDIRECT_AUTH = /\b[a-zA-Z0-9_]*(auth|permission|cap|nonce|can_|check|verify|allow)[a-zA-Z0-9_]*\s*\(/;
56336
+ function detectMissingAuthz(code, file) {
56337
+ const out = [];
56338
+ const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
56339
+ let m;
56340
+ while ((m = ajaxRe.exec(code)) !== null) {
56341
+ const hook = m[1];
56342
+ const handler = m[2];
56343
+ const nopriv = hook.startsWith("wp_ajax_nopriv_");
56344
+ const body = functionBody(code, handler);
56345
+ if (!body)
56346
+ continue;
56347
+ const hasCap = /current_user_can|is_user_logged_in|authorize/.test(body) || INDIRECT_AUTH.test(body);
56348
+ const hasNonce = /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/.test(body);
56349
+ const line = lineNoOf(code, m.index);
56350
+ if (nopriv && !hasCap) {
56351
+ out.push({
56352
+ file,
56353
+ line,
56354
+ type: "missing_authz",
56355
+ category: "Missing capability check (unauthenticated AJAX handler)",
56356
+ severity: "high",
56357
+ evidence: `add_action('${hook}', '${handler}')`,
56358
+ detail: `Unauthenticated AJAX handler '${handler}()' performs no current_user_can()/authorization check — any unauthenticated visitor can trigger it (auth bypass / privilege escalation).`
56359
+ });
56360
+ }
56361
+ if (!hasNonce) {
56362
+ out.push({
56363
+ file,
56364
+ line,
56365
+ type: "missing_nonce",
56366
+ category: "Missing nonce verification",
56367
+ severity: "medium",
56368
+ evidence: `add_action('${hook}', '${handler}')`,
56369
+ detail: `AJAX handler '${handler}()' does not verify a nonce (wp_verify_nonce / check_ajax_referer) — CSRF: an attacker can force an authenticated admin to trigger it.`
56370
+ });
56371
+ }
56372
+ }
56373
+ const adminRe = /add_action\s*\(\s*['"](admin_post(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
56374
+ while ((m = adminRe.exec(code)) !== null) {
56375
+ const hook = m[1];
56376
+ const handler = m[2];
56377
+ const nopriv = hook.startsWith("admin_post_nopriv_");
56378
+ const body = functionBody(code, handler);
56379
+ if (!body)
56380
+ continue;
56381
+ const hasCap = /current_user_can|is_user_logged_in|authorize/.test(body) || INDIRECT_AUTH.test(body);
56382
+ const hasNonce = /wp_verify_nonce|check_admin_referer|verify_nonce/.test(body);
56383
+ const line = lineNoOf(code, m.index);
56384
+ if (nopriv && !hasCap) {
56385
+ out.push({
56386
+ file,
56387
+ line,
56388
+ type: "missing_authz",
56389
+ category: "Missing capability check (unauthenticated admin-post handler)",
56390
+ severity: "high",
56391
+ evidence: `add_action('${hook}', '${handler}')`,
56392
+ detail: `Unauthenticated admin-post handler '${handler}()' has no authorization check — auth bypass / privilege escalation.`
56393
+ });
56394
+ }
56395
+ if (!hasNonce) {
56396
+ out.push({
56397
+ file,
56398
+ line,
56399
+ type: "missing_nonce",
56400
+ category: "Missing nonce verification",
56401
+ severity: "medium",
56402
+ evidence: `add_action('${hook}', '${handler}')`,
56403
+ detail: `admin-post handler '${handler}()' does not verify a nonce — CSRF.`
56404
+ });
56405
+ }
56406
+ }
56407
+ const restRe = /register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
56408
+ while ((m = restRe.exec(code)) !== null) {
56409
+ const line = lineNoOf(code, m.index);
56410
+ const window2 = code.slice(m.index, m.index + 800);
56411
+ if (/permission_callback/.test(window2))
56412
+ continue;
56413
+ out.push({
56414
+ file,
56415
+ line,
56416
+ type: "missing_authz",
56417
+ category: "REST route missing permission_callback",
56418
+ severity: "high",
56419
+ evidence: `register_rest_route('${m[1]}', '${m[2]}')`,
56420
+ detail: `REST route '${m[1]}${m[2]}' has no permission_callback — the endpoint defaults to public (auth bypass / unauthenticated data access or mutation).`
56421
+ });
56422
+ }
56423
+ const seen = new Set;
56424
+ return out.filter((f) => {
56425
+ const key = `${f.type}:${f.line}`;
56426
+ if (seen.has(key))
56427
+ return false;
56428
+ seen.add(key);
56429
+ return true;
56430
+ });
56431
+ }
56432
+
56262
56433
  // src/complex-bugs.ts
56263
56434
  var 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)/;
56264
56435
  function lines2(code) {
@@ -56371,6 +56542,7 @@ function detectComplexBugs(code, file) {
56371
56542
  detectXxe(code, file, out);
56372
56543
  detectSsti(code, file, out);
56373
56544
  out.push(...detectWrongSanitizer(code, file));
56545
+ out.push(...detectMissingAuthz(code, file));
56374
56546
  const seen = new Set;
56375
56547
  return out.filter((f) => {
56376
56548
  const key = `${f.type}:${f.line}`;
@@ -56882,8 +57054,85 @@ function openCount() {
56882
57054
  return loadObligations().filter((o) => o.status === "open").length;
56883
57055
  }
56884
57056
 
57057
+ // src/variant-scan.ts
57058
+ init_scanner();
57059
+ import { readFileSync as readFileSync19 } from "node:fs";
57060
+ function deriveSignature(detector, type) {
57061
+ return `${detector}:${type}`;
57062
+ }
57063
+ function variantScan(root, opts = {}) {
57064
+ const files = iterSourceFiles(root, opts.maxFiles ?? 5000);
57065
+ const hits = [];
57066
+ for (const file of files) {
57067
+ let code;
57068
+ try {
57069
+ code = readFileSync19(file, "utf8");
57070
+ } catch {
57071
+ continue;
57072
+ }
57073
+ try {
57074
+ const taint = analyzeTaint(file);
57075
+ for (const f of taint.findings) {
57076
+ const type = f.category ?? f.sink ?? "taint";
57077
+ hits.push({
57078
+ file,
57079
+ detector: "taint",
57080
+ type,
57081
+ signature: deriveSignature("taint", type),
57082
+ line: f.sink_line ?? 0,
57083
+ severity: "high",
57084
+ evidence: f.sink ?? ""
57085
+ });
57086
+ }
57087
+ } catch {}
57088
+ for (const f of detectComplexBugs(code, file)) {
57089
+ hits.push({
57090
+ file,
57091
+ detector: "complex_bugs",
57092
+ type: f.type,
57093
+ signature: deriveSignature("complex_bugs", f.type),
57094
+ line: f.line,
57095
+ severity: f.severity,
57096
+ evidence: f.evidence
57097
+ });
57098
+ }
57099
+ for (const f of detectRouteConfusion(code, file)) {
57100
+ hits.push({
57101
+ file,
57102
+ detector: "route_confusion",
57103
+ type: f.type,
57104
+ signature: deriveSignature("route_confusion", f.type),
57105
+ line: f.line,
57106
+ severity: f.severity,
57107
+ evidence: f.evidence
57108
+ });
57109
+ }
57110
+ }
57111
+ const bySignature = {};
57112
+ for (const h of hits)
57113
+ bySignature[h.signature] = (bySignature[h.signature] ?? 0) + 1;
57114
+ const filtered = opts.signature ? hits.filter((h) => h.signature === opts.signature) : hits;
57115
+ const filesWithFindings = new Set(filtered.map((h) => h.file));
57116
+ return {
57117
+ root,
57118
+ files_scanned: files.length,
57119
+ files_with_findings: filesWithFindings.size,
57120
+ total_findings: filtered.length,
57121
+ signature: opts.signature ?? null,
57122
+ by_signature: bySignature,
57123
+ hits: filtered.map((h) => ({
57124
+ file: h.file,
57125
+ detector: h.detector,
57126
+ type: h.type,
57127
+ signature: h.signature,
57128
+ line: h.line,
57129
+ severity: h.severity
57130
+ }))
57131
+ };
57132
+ }
57133
+
56885
57134
  // src/enterprise-benchmark.ts
56886
- import { existsSync as existsSync11, readFileSync as readFileSync19, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
57135
+ import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
56887
57136
  import { join as join18, resolve } from "node:path";
56888
57137
  var ROOT6 = resolve(process.cwd(), "bench", "enterprise");
56889
57138
  var ENTERPRISE_FIXTURES = [
@@ -56925,7 +57174,7 @@ function detectFixture(dir) {
56925
57174
  for (const f of sourceFiles(dir)) {
56926
57175
  let code;
56927
57176
  try {
56928
- code = readFileSync19(f, "utf8");
57177
+ code = readFileSync20(f, "utf8");
56929
57178
  } catch {
56930
57179
  continue;
56931
57180
  }
@@ -57826,6 +58075,27 @@ ${statusLine}` : statusLine },
57826
58075
  }
57827
58076
  return { content: [{ type: "text", text: JSON.stringify(analyzeSecurityState({ sanitizers: arr, sinkType: sink_type })) }] };
57828
58077
  });
58078
+ server.registerTool("framework_knowledge", {
58079
+ title: "Framework security knowledge graph",
58080
+ description: "FRAMEWORK-KNOWLEDGE: return a framework's security primitives as data — authorization primitives (current_user_can, is_user_logged_in), nonce/CSRF primitives (wp_verify_nonce, check_ajax_referer), entry-point hooks (wp_ajax_*, admin_post_*, register_rest_route), and sanitizers. Powers the missing-authz/nonce detector. Extensible to other frameworks.",
58081
+ inputSchema: {
58082
+ framework: string2().optional().describe("Framework id (default 'wordpress')")
58083
+ }
58084
+ }, async ({ framework }) => {
58085
+ const k = frameworkKnowledge(framework ?? "wordpress");
58086
+ return { content: [{ type: "text", text: JSON.stringify(k ?? { error: "framework not in the knowledge graph", framework }) }] };
58087
+ });
58088
+ server.registerTool("variant_scan", {
58089
+ title: "Mass variant-mining scan of an install base",
58090
+ description: "VARIANT-MINING: mass-scan a source tree (plugin/theme/repo install base) with ALL detectors (taint, complex-bugs incl. wrong-context sanitization + missing authz/nonce, route-confusion) and report hits grouped by DERIVED signature (<detector>:<type>). Pass `signature` to mine for ONE bug family (e.g. complex_bugs:missing_authz) — the 'confirm once, weaponize everywhere' pattern that turns one CVE into hundreds of variants across an install base.",
58091
+ inputSchema: {
58092
+ root: string2().describe("Root directory to scan"),
58093
+ max_files: number2().optional().describe("Max files to scan (default 5000)"),
58094
+ signature: string2().optional().describe("Filter to one bug family (e.g. complex_bugs:missing_authz)")
58095
+ }
58096
+ }, async ({ root, max_files, signature }) => {
58097
+ return { content: [{ type: "text", text: JSON.stringify(variantScan(root, { maxFiles: max_files, signature })) }] };
58098
+ });
57829
58099
  server.registerTool("payload_lookup", {
57830
58100
  title: "Look up exploit payloads",
57831
58101
  description: "INTEL: find a payload collection (66 categories from PayloadsAllTheThings).",
@@ -58364,7 +58634,7 @@ ${statusLine}` : statusLine },
58364
58634
  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.",
58365
58635
  inputSchema: { path: string2().describe("Source file path (PHP)") }
58366
58636
  }, async ({ path }) => {
58367
- const code = readFileSync21(path, "utf8");
58637
+ const code = readFileSync22(path, "utf8");
58368
58638
  const findings = detectRouteConfusion(code, path);
58369
58639
  return {
58370
58640
  content: [{
@@ -58374,11 +58644,11 @@ ${statusLine}` : statusLine },
58374
58644
  };
58375
58645
  });
58376
58646
  server.registerTool("complex_scan", {
58377
- title: "Complex-bug scan (deserialization / type juggling / mass assignment / prototype pollution / CRLF / path confusion / SSRF / XXE / SSTI / wrong-context sanitization)",
58378
- 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), and wrong-context sanitization (a value sanitized for context X reaching a sink that needs Y — e.g. sanitize_text_field() into a SQL query).",
58647
+ title: "Complex-bug scan (deserialization / type juggling / mass assignment / prototype pollution / CRLF / path confusion / SSRF / XXE / SSTI / wrong-context sanitization / missing authz+nonce)",
58648
+ 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).",
58379
58649
  inputSchema: { path: string2().describe("Source file path") }
58380
58650
  }, async ({ path }) => {
58381
- const code = readFileSync21(path, "utf8");
58651
+ const code = readFileSync22(path, "utf8");
58382
58652
  const findings = detectComplexBugs(code, path);
58383
58653
  return {
58384
58654
  content: [{
@@ -58445,7 +58715,7 @@ ${statusLine}` : statusLine },
58445
58715
  let lang = language;
58446
58716
  if (path && !code) {
58447
58717
  try {
58448
- src = readFileSync21(path, "utf8");
58718
+ src = readFileSync22(path, "utf8");
58449
58719
  } catch {
58450
58720
  return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
58451
58721
  }
@@ -58686,7 +58956,7 @@ async function serve() {
58686
58956
 
58687
58957
  // src/index.ts
58688
58958
  init_cli();
58689
- import { readFileSync as readFileSync25, existsSync as existsSync14 } from "node:fs";
58959
+ import { readFileSync as readFileSync26, existsSync as existsSync14 } from "node:fs";
58690
58960
  import { join as join23 } from "node:path";
58691
58961
  import { fileURLToPath as fileURLToPath13 } from "node:url";
58692
58962
  var ROOT11 = join23(fileURLToPath13(new URL(".", import.meta.url)), "..");
@@ -58694,7 +58964,7 @@ function readVersion() {
58694
58964
  try {
58695
58965
  const p = join23(ROOT11, "package.json");
58696
58966
  if (existsSync14(p))
58697
- return JSON.parse(readFileSync25(p, "utf8")).version ?? "1.0.0";
58967
+ return JSON.parse(readFileSync26(p, "utf8")).version ?? "1.0.0";
58698
58968
  } catch {}
58699
58969
  return "1.0.0";
58700
58970
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.33",
3
+ "version": "2.4.35",
4
4
  "description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
5
5
  "type": "module",
6
6
  "bin": {