blitzstrike 2.4.47 → 2.4.49

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,37 @@ 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.49] — 2026-09-14
9
+
10
+ ### Changed — REST permission_callback precision (traced to WP_REST_Server)
11
+
12
+ - Traced WP core `class-wp-rest-server.php` dispatch: `permission_callback` is
13
+ OPTIONAL and defaults to PUBLIC when absent. Now distinguishes three cases:
14
+ missing (HIGH — forgot, defaults public), `__return_true` (MEDIUM — explicitly
15
+ public, the classic "commented out check_admin_permission" pattern), and a
16
+ real callback (skip — function/method/current_user_can).
17
+ - Sanitizer table widened to the full `formatting.php` surface: `esc_textarea`,
18
+ `esc_js`, `sanitize_url`, `sanitize_title`, `sanitize_title_with_dashes`,
19
+ `sanitize_user`, `wp_validate_boolean`.
20
+
21
+ ## [2.4.48] — 2026-09-14
22
+
23
+ ### Changed — Precision + consistency (traced to WP core dispatch)
24
+
25
+ - Grounded the authz/nonce rules in the WordPress core dispatch: `wp_ajax_*` and
26
+ `admin_post_*` fire ONLY when `is_user_logged_in()`, while `*_nopriv_*` fire
27
+ unauthenticated. `missing_authz` is now scoped to nopriv handlers that perform
28
+ a state-changing/privileged operation.
29
+ - Added a DANGEROUS_OP_RE gate: `missing_nonce`/`missing_authz` only fire when
30
+ the handler actually performs a WRITE (option/meta/user/post/comment writes,
31
+ file upload/write, `$wpdb->query/insert/update/delete`, `wp_mail`, `wp_redirect`).
32
+ Read-only handlers (list/search/get) are no longer flagged — removes the CSRF/
33
+ authz false-positive storm (forminator 90→16, elementor 11→4, wp-fastest-cache
34
+ 15→1).
35
+ - Removed `is_admin()` / `wp_doing_ajax()` from the auth surface — they are
36
+ request-context checks, not authorization (treating them as auth caused false
37
+ negatives).
38
+
8
39
  ## [2.4.47] — 2026-09-14
9
40
 
10
41
  ### Changed — WordPress integration coverage is now COMPLETE (class-method + direct handlers)
package/dist/index.js CHANGED
@@ -56202,6 +56202,8 @@ var SANITIZERS2 = {
56202
56202
  esc_attr: { context: "html_attr", level: "sanitized" },
56203
56203
  esc_url: { context: "url", level: "sanitized" },
56204
56204
  esc_url_raw: { context: "url", level: "sanitized" },
56205
+ esc_textarea: { context: "html", level: "sanitized" },
56206
+ esc_js: { context: "js", level: "sanitized" },
56205
56207
  urlencode: { context: "url", level: "sanitized" },
56206
56208
  rawurlencode: { context: "url", level: "sanitized" },
56207
56209
  wp_json_encode: { context: "json", level: "sanitized" },
@@ -56211,7 +56213,12 @@ var SANITIZERS2 = {
56211
56213
  sanitize_key: { context: "key", level: "validated" },
56212
56214
  sanitize_email: { context: "email", level: "validated" },
56213
56215
  sanitize_file_name: { context: "filename", level: "validated" },
56214
- filter_var: { context: "validated", level: "validated" }
56216
+ sanitize_url: { context: "url", level: "validated" },
56217
+ sanitize_title: { context: "slug", level: "validated" },
56218
+ sanitize_title_with_dashes: { context: "slug", level: "validated" },
56219
+ sanitize_user: { context: "username", level: "validated" },
56220
+ filter_var: { context: "validated", level: "validated" },
56221
+ wp_validate_boolean: { context: "validated", level: "validated" }
56215
56222
  };
56216
56223
  var PSEUDO_SANITIZERS = new Set([
56217
56224
  "trim",
@@ -56478,8 +56485,9 @@ function handlerName(expr) {
56478
56485
  return null;
56479
56486
  }
56480
56487
  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_admin\b|is_super_admin|current_user_can_edit_post|authorize|wp_doing_ajax/;
56488
+ var AUTH_RE = /current_user_can|is_user_logged_in|is_super_admin|current_user_can_edit_post|authorize/;
56482
56489
  var NONCE_RE = /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/;
56490
+ 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*\(/;
56483
56491
  function detectWpAuthz(code, file, out) {
56484
56492
  const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*(.+?)\s*\)/g;
56485
56493
  let m;
@@ -56494,12 +56502,13 @@ function detectWpAuthz(code, file, out) {
56494
56502
  continue;
56495
56503
  const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56496
56504
  const hasNonce = NONCE_RE.test(body);
56505
+ const dangerous = DANGEROUS_OP_RE.test(body);
56497
56506
  const line = lineNoOf(code, m.index);
56498
- if (nopriv && !hasCap) {
56499
- 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 no current_user_can()/authorization check — any unauthenticated visitor can trigger it (auth bypass / privilege escalation).` });
56507
+ if (nopriv && !hasCap && dangerous) {
56508
+ 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.` });
56500
56509
  }
56501
- if (!hasNonce) {
56502
- out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', ...)`, detail: `AJAX handler '${handler}()' does not verify a nonce (wp_verify_nonce / check_ajax_referer) — CSRF.` });
56510
+ if (!hasNonce && dangerous) {
56511
+ 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.` });
56503
56512
  }
56504
56513
  }
56505
56514
  const adminRe = /add_action\s*\(\s*['"](admin_post(?:_nopriv)?_\w+)['"]\s*,\s*(.+?)\s*\)/g;
@@ -56514,20 +56523,27 @@ function detectWpAuthz(code, file, out) {
56514
56523
  continue;
56515
56524
  const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56516
56525
  const hasNonce = NONCE_RE.test(body);
56526
+ const dangerous = DANGEROUS_OP_RE.test(body);
56517
56527
  const line = lineNoOf(code, m.index);
56518
- if (nopriv && !hasCap) {
56519
- 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}()' has no authorization check — auth bypass / privilege escalation.` });
56528
+ if (nopriv && !hasCap && dangerous) {
56529
+ 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.` });
56520
56530
  }
56521
- if (!hasNonce) {
56522
- out.push({ file, line, type: "missing_nonce", category: "Missing nonce verification", severity: "medium", evidence: `add_action('${hook}', ...)`, detail: `admin-post handler '${handler}()' does not verify a nonce — CSRF.` });
56531
+ if (!hasNonce && dangerous) {
56532
+ 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.` });
56523
56533
  }
56524
56534
  }
56525
56535
  const restRe = /register_rest_route\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]/g;
56526
56536
  while ((m = restRe.exec(code)) !== null) {
56527
56537
  const line = lineNoOf(code, m.index);
56528
56538
  const window2 = code.slice(m.index, m.index + 800);
56529
- if (/permission_callback/.test(window2))
56539
+ const pc = window2.match(/permission_callback['"]?\s*=>\s*(.+?)(?:,|\s*\n|\])/);
56540
+ if (pc) {
56541
+ const expr = pc[1].trim();
56542
+ if (/__return_true/.test(expr)) {
56543
+ out.push({ file, line, type: "missing_authz", category: "REST route explicitly public (__return_true)", severity: "medium", evidence: `register_rest_route('${m[1]}', '${m[2]}')`, detail: `REST route '${m[1]}${m[2]}' sets permission_callback to __return_true (always allow) — verify the endpoint does not expose sensitive data or perform privileged actions.` });
56544
+ }
56530
56545
  continue;
56546
+ }
56531
56547
  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).` });
56532
56548
  }
56533
56549
  const formRe = /add_action\s*\(\s*['"](init|admin_init|wp_loaded|template_redirect|parse_request|wp)['"]\s*,\s*(.+?)\s*\)/g;
@@ -56543,11 +56559,12 @@ function detectWpAuthz(code, file, out) {
56543
56559
  continue;
56544
56560
  const hasCap = AUTH_RE.test(body) || INDIRECT_AUTH.test(body);
56545
56561
  const hasNonce = NONCE_RE.test(body);
56562
+ const dangerous = DANGEROUS_OP_RE.test(body);
56546
56563
  const line = lineNoOf(code, m.index);
56547
- if (!hasNonce) {
56548
- 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}') reads request input without nonce verification — CSRF.` });
56564
+ if (!hasNonce && dangerous) {
56565
+ 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.` });
56549
56566
  }
56550
- if (!hasCap && /(update_option|add_option|delete_option|wp_insert_post|wp_update_post|wp_delete_post|update_user_meta|delete_user_meta|wp_set_current_user|wp_mail|wp_handle_upload|move_uploaded_file|wp_delete_user|add_user|wp_create_user|set_user_role|wp_redirect\s*\(\s*['"]admin)/.test(body)) {
56567
+ if (!hasCap && dangerous) {
56551
56568
  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.` });
56552
56569
  }
56553
56570
  }
@@ -57949,7 +57966,7 @@ var ENTERPRISE_FIXTURES = [
57949
57966
  { id: "js-express", dir: join18(ROOT6, "js-express"), expected_vulns: 2 },
57950
57967
  { id: "py-django", dir: join18(ROOT6, "py-django"), expected_vulns: 2 },
57951
57968
  { id: "java-spring", dir: join18(ROOT6, "java-spring"), expected_vulns: 2 },
57952
- { id: "wp-batch-route", dir: join18(ROOT6, "wp-batch-route"), expected_vulns: 2 },
57969
+ { id: "wp-batch-route", dir: join18(ROOT6, "wp-batch-route"), expected_vulns: 3 },
57953
57970
  { id: "php-complex", dir: join18(ROOT6, "php-complex"), expected_vulns: 4 }
57954
57971
  ];
57955
57972
  function sourceFiles(root) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.47",
3
+ "version": "2.4.49",
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": {