blitzstrike 2.4.33 → 2.4.34

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,18 @@ 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.34] — 2026-09-14
9
+
10
+ ### Added — Framework Security Knowledge Graph (WordPress authz/nonce)
11
+
12
+ - New `src/framework-security.ts`: WordPress security primitives encoded as DATA
13
+ (auth, nonce, entry-point hooks, sanitizers), exposed via `framework_knowledge`.
14
+ - `detectMissingAuthz` (wired into `complex_scan`) finds missing capability /
15
+ nonce checks on WP entry points — `wp_ajax_*` / `wp_ajax_nopriv_*` /
16
+ `admin_post_*` / `register_rest_route` without `current_user_can`,
17
+ `wp_verify_nonce`, or `permission_callback` (auth bypass + CSRF + privesc,
18
+ the class that dominates real WordPress CVE reports).
19
+
8
20
  ## [2.4.33] — 2026-09-14
9
21
 
10
22
  ### 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
@@ -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}`;
@@ -57826,6 +57998,16 @@ ${statusLine}` : statusLine },
57826
57998
  }
57827
57999
  return { content: [{ type: "text", text: JSON.stringify(analyzeSecurityState({ sanitizers: arr, sinkType: sink_type })) }] };
57828
58000
  });
58001
+ server.registerTool("framework_knowledge", {
58002
+ title: "Framework security knowledge graph",
58003
+ 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.",
58004
+ inputSchema: {
58005
+ framework: string2().optional().describe("Framework id (default 'wordpress')")
58006
+ }
58007
+ }, async ({ framework }) => {
58008
+ const k = frameworkKnowledge(framework ?? "wordpress");
58009
+ return { content: [{ type: "text", text: JSON.stringify(k ?? { error: "framework not in the knowledge graph", framework }) }] };
58010
+ });
57829
58011
  server.registerTool("payload_lookup", {
57830
58012
  title: "Look up exploit payloads",
57831
58013
  description: "INTEL: find a payload collection (66 categories from PayloadsAllTheThings).",
@@ -58374,8 +58556,8 @@ ${statusLine}` : statusLine },
58374
58556
  };
58375
58557
  });
58376
58558
  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).",
58559
+ title: "Complex-bug scan (deserialization / type juggling / mass assignment / prototype pollution / CRLF / path confusion / SSRF / XXE / SSTI / wrong-context sanitization / missing authz+nonce)",
58560
+ 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
58561
  inputSchema: { path: string2().describe("Source file path") }
58380
58562
  }, async ({ path }) => {
58381
58563
  const code = readFileSync21(path, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.33",
3
+ "version": "2.4.34",
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": {