blitzstrike 2.4.46 → 2.4.48

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,39 @@ 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.48] — 2026-09-14
9
+
10
+ ### Changed — Precision + consistency (traced to WP core dispatch)
11
+
12
+ - Grounded the authz/nonce rules in the WordPress core dispatch: `wp_ajax_*` and
13
+ `admin_post_*` fire ONLY when `is_user_logged_in()`, while `*_nopriv_*` fire
14
+ unauthenticated. `missing_authz` is now scoped to nopriv handlers that perform
15
+ a state-changing/privileged operation.
16
+ - Added a DANGEROUS_OP_RE gate: `missing_nonce`/`missing_authz` only fire when
17
+ the handler actually performs a WRITE (option/meta/user/post/comment writes,
18
+ file upload/write, `$wpdb->query/insert/update/delete`, `wp_mail`, `wp_redirect`).
19
+ Read-only handlers (list/search/get) are no longer flagged — removes the CSRF/
20
+ authz false-positive storm (forminator 90→16, elementor 11→4, wp-fastest-cache
21
+ 15→1).
22
+ - Removed `is_admin()` / `wp_doing_ajax()` from the auth surface — they are
23
+ request-context checks, not authorization (treating them as auth caused false
24
+ negatives).
25
+
26
+ ## [2.4.47] — 2026-09-14
27
+
28
+ ### Changed — WordPress integration coverage is now COMPLETE (class-method + direct handlers)
29
+
30
+ - The WP authz/nonce detector previously only matched STRING handlers
31
+ (`add_action('wp_ajax_x','fn')`), missing the `array($this,'method')` /
32
+ `array('Class','method')` form that dominates modern class-based plugins.
33
+ Now resolves any handler form via `handlerName()` + `functionBody()`.
34
+ - Added raw request-handler detection: `add_action('init'|'admin_init'|
35
+ 'wp_loaded'|'template_redirect', ...)` that reads request input directly —
36
+ flagged for missing nonce (CSRF) and missing capability (privileged ops).
37
+ - Auth surface widened (`is_admin`, `is_super_admin`, `current_user_can_edit_post`,
38
+ `wp_doing_ajax`). Benchmark: elementor 0→11, forminator ~3→90, learnpress 1→11
39
+ authz/nonce findings (class-method handlers now visible).
40
+
8
41
  ## [2.4.46] — 2026-09-14
9
42
 
10
43
  ### Added — SQL injection (raw string-interpolated query) detector
package/dist/index.js CHANGED
@@ -56471,43 +56471,58 @@ function functionBody(code, name) {
56471
56471
  }
56472
56472
  return code.slice(open);
56473
56473
  }
56474
+ function handlerName(expr) {
56475
+ const quoted = expr.match(/['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g);
56476
+ if (quoted && quoted.length > 0)
56477
+ return quoted[quoted.length - 1].replace(/['"]/g, "");
56478
+ return null;
56479
+ }
56474
56480
  var INDIRECT_AUTH = /\b[a-zA-Z0-9_]*(auth|permission|cap|nonce|can_|check|verify|allow)[a-zA-Z0-9_]*\s*\(/;
56481
+ var AUTH_RE = /current_user_can|is_user_logged_in|is_super_admin|current_user_can_edit_post|authorize/;
56482
+ var NONCE_RE = /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/;
56483
+ var DANGEROUS_OP_RE = /\b(update_option|add_option|delete_option|update_site_option|delete_site_option|update_post_meta|add_post_meta|delete_post_meta|update_user_meta|add_user_meta|delete_user_meta|update_metadata|add_metadata|delete_metadata|set_transient|delete_transient|wp_insert_post|wp_update_post|wp_delete_post|wp_trash_post|wp_untrash_post|wp_insert_comment|wp_update_comment|wp_delete_comment|wp_insert_user|wp_update_user|wp_delete_user|wp_create_user|wp_set_current_user|set_user_role|wp_set_auth_cookie|wp_mail|wp_new_user_notification|wp_handle_upload|move_uploaded_file|wp_upload_bits|wp_set_object_terms|wp_set_post_terms|wp_schedule_event|wp_clear_scheduled_hook|wp_redirect|file_put_contents|unlink|rename|wpdb->query|wpdb->insert|wpdb->update|wpdb->delete|wpdb->replace)\s*\(/;
56475
56484
  function detectWpAuthz(code, file, out) {
56476
- const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
56485
+ const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*(.+?)\s*\)/g;
56477
56486
  let m;
56478
56487
  while ((m = ajaxRe.exec(code)) !== null) {
56479
56488
  const hook = m[1];
56480
- const handler = m[2];
56489
+ const handler = handlerName(m[2]);
56490
+ if (!handler)
56491
+ continue;
56481
56492
  const nopriv = hook.startsWith("wp_ajax_nopriv_");
56482
56493
  const body = functionBody(code, handler);
56483
56494
  if (!body)
56484
56495
  continue;
56485
- const hasCap = /current_user_can|is_user_logged_in|authorize/.test(body) || INDIRECT_AUTH.test(body);
56486
- const hasNonce = /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/.test(body);
56496
+ const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56497
+ const hasNonce = NONCE_RE.test(body);
56498
+ const dangerous = DANGEROUS_OP_RE.test(body);
56487
56499
  const line = lineNoOf(code, m.index);
56488
- if (nopriv && !hasCap) {
56489
- out.push({ file, line, type: "missing_authz", category: "Missing capability check (unauthenticated AJAX handler)", severity: "high", evidence: `add_action('${hook}', '${handler}')`, detail: `Unauthenticated AJAX handler '${handler}()' performs no current_user_can()/authorization check — any unauthenticated visitor can trigger it (auth bypass / privilege escalation).` });
56500
+ if (nopriv && !hasCap && dangerous) {
56501
+ out.push({ file, line, type: "missing_authz", category: "Missing capability check (unauthenticated AJAX handler)", severity: "high", evidence: `add_action('${hook}', ...)`, detail: `Unauthenticated AJAX handler '${handler}()' performs a privileged/state-changing operation with no current_user_can()/authorization check — auth bypass / privilege escalation.` });
56490
56502
  }
56491
- if (!hasNonce) {
56492
- out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', '${handler}')`, detail: `AJAX handler '${handler}()' does not verify a nonce (wp_verify_nonce / check_ajax_referer) — CSRF.` });
56503
+ if (!hasNonce && dangerous) {
56504
+ out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', ...)`, detail: `AJAX handler '${handler}()' performs a state-changing operation without nonce verification (wp_verify_nonce / check_ajax_referer) — CSRF.` });
56493
56505
  }
56494
56506
  }
56495
- const adminRe = /add_action\s*\(\s*['"](admin_post(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
56507
+ const adminRe = /add_action\s*\(\s*['"](admin_post(?:_nopriv)?_\w+)['"]\s*,\s*(.+?)\s*\)/g;
56496
56508
  while ((m = adminRe.exec(code)) !== null) {
56497
56509
  const hook = m[1];
56498
- const handler = m[2];
56510
+ const handler = handlerName(m[2]);
56511
+ if (!handler)
56512
+ continue;
56499
56513
  const nopriv = hook.startsWith("admin_post_nopriv_");
56500
56514
  const body = functionBody(code, handler);
56501
56515
  if (!body)
56502
56516
  continue;
56503
- const hasCap = /current_user_can|is_user_logged_in|authorize/.test(body) || INDIRECT_AUTH.test(body);
56504
- const hasNonce = /wp_verify_nonce|check_admin_referer|verify_nonce/.test(body);
56517
+ const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56518
+ const hasNonce = NONCE_RE.test(body);
56519
+ const dangerous = DANGEROUS_OP_RE.test(body);
56505
56520
  const line = lineNoOf(code, m.index);
56506
- if (nopriv && !hasCap) {
56507
- out.push({ file, line, type: "missing_authz", category: "Missing capability check (unauthenticated admin-post handler)", severity: "high", evidence: `add_action('${hook}', '${handler}')`, detail: `Unauthenticated admin-post handler '${handler}()' has no authorization check — auth bypass / privilege escalation.` });
56521
+ if (nopriv && !hasCap && dangerous) {
56522
+ out.push({ file, line, type: "missing_authz", category: "Missing capability check (unauthenticated admin-post handler)", severity: "high", evidence: `add_action('${hook}', ...)`, detail: `Unauthenticated admin-post handler '${handler}()' performs a privileged/state-changing operation with no authorization check — auth bypass / privilege escalation.` });
56508
56523
  }
56509
- if (!hasNonce) {
56510
- out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', '${handler}')`, detail: `admin-post handler '${handler}()' does not verify a nonce — CSRF.` });
56524
+ if (!hasNonce && dangerous) {
56525
+ out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', ...)`, detail: `admin-post handler '${handler}()' performs a state-changing operation without nonce verification — CSRF.` });
56511
56526
  }
56512
56527
  }
56513
56528
  const restRe = /register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
@@ -56518,6 +56533,28 @@ function detectWpAuthz(code, file, out) {
56518
56533
  continue;
56519
56534
  out.push({ file, line, type: "missing_authz", category: "REST route missing permission_callback", severity: "high", evidence: `register_rest_route('${m[1]}', '${m[2]}')`, detail: `REST route '${m[1]}${m[2]}' has no permission_callback — the endpoint defaults to public (auth bypass).` });
56520
56535
  }
56536
+ const formRe = /add_action\s*\(\s*['"](init|admin_init|wp_loaded|template_redirect|parse_request|wp)['"]\s*,\s*(.+?)\s*\)/g;
56537
+ while ((m = formRe.exec(code)) !== null) {
56538
+ const hook = m[1];
56539
+ const handler = handlerName(m[2]);
56540
+ if (!handler)
56541
+ continue;
56542
+ const body = functionBody(code, handler);
56543
+ if (!body)
56544
+ continue;
56545
+ if (!/\$_(GET|POST|REQUEST|COOKIE|SERVER)\b|php:\/\/input|filter_input|file_get_contents\(['"]php:\/\/input/.test(body))
56546
+ continue;
56547
+ const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56548
+ const hasNonce = NONCE_RE.test(body);
56549
+ const dangerous = DANGEROUS_OP_RE.test(body);
56550
+ const line = lineNoOf(code, m.index);
56551
+ if (!hasNonce && dangerous) {
56552
+ out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification (direct request handler)", severity: "medium", evidence: `add_action('${hook}', ...)`, detail: `Request handler '${handler}()' (on '${hook}') performs a state-changing operation without nonce verification — CSRF.` });
56553
+ }
56554
+ if (!hasCap && dangerous) {
56555
+ out.push({ file, line, type: "missing_authz", category: "Missing capability check (direct request handler)", severity: "high", evidence: `add_action('${hook}', ...)`, detail: `Request handler '${handler}()' (on '${hook}') performs a privileged/state-changing operation with no capability check — auth bypass / privilege escalation.` });
56556
+ }
56557
+ }
56521
56558
  }
56522
56559
  var ROUTE_DETECTORS = [
56523
56560
  { framework: "laravel", entry: /Route::(?:get|post|put|patch|delete|any|match|resource)\s*\(\s*['"][^'"]+['"]/g, auth: /->middleware\s*\(\s*['"]auth|middleware\s*\(\s*\[[^\]]*['"]auth|->can\s*\(|\$this->authorize|Gate::|authorize\s*\(|@can\b/, window: 2 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.46",
3
+ "version": "2.4.48",
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": {