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 +31 -0
- package/dist/index.js +32 -15
- package/package.json +1 -1
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
|
-
|
|
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|
|
|
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 —
|
|
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}()'
|
|
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}()'
|
|
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}()'
|
|
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
|
-
|
|
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}')
|
|
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 &&
|
|
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:
|
|
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.
|
|
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": {
|