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 +33 -0
- package/dist/index.js +53 -16
- package/package.json +1 -1
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*
|
|
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 =
|
|
56486
|
-
const hasNonce =
|
|
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}',
|
|
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}',
|
|
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*
|
|
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 =
|
|
56504
|
-
const hasNonce =
|
|
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}',
|
|
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}',
|
|
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.
|
|
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": {
|