blitzstrike 2.4.36 → 2.4.38
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 +21 -0
- package/dist/index.js +119 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.38] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — Action-Name Heuristic Engine (what to look for WHERE)
|
|
11
|
+
|
|
12
|
+
- New `src/action-heuristics.ts`: maps WordPress AJAX action-name patterns to
|
|
13
|
+
their likely vulnerability class (upload/import→AFU/RCE, delete/write→
|
|
14
|
+
file-write, load/download→LFI, login/reset→auth-bypass, exec/run→command
|
|
15
|
+
injection, settings/update→unauth options change, …) — as DATA, with real
|
|
16
|
+
examples (ai1wm_import, revslider_upload, duplicator_download, …).
|
|
17
|
+
- New `action_surface` tool: extract every `wp_ajax_*` action, cross-reference
|
|
18
|
+
the hook (nopriv?) + authz gap (nonce/capability), and return a DERIVED
|
|
19
|
+
priority so the scanner targets the most dangerous action first.
|
|
20
|
+
|
|
21
|
+
## [2.4.37] — 2026-09-14
|
|
22
|
+
|
|
23
|
+
### Fixed — benchmark top-level recall/f1 (self-audit)
|
|
24
|
+
|
|
25
|
+
- `run_benchmark` now returns top-level `recall` and `f1` (previously only
|
|
26
|
+
`detection_rate` held the recall value, and no top-level f1). Matches the
|
|
27
|
+
per-detector shape for consistency. No functional change.
|
|
28
|
+
|
|
8
29
|
## [2.4.36] — 2026-09-14
|
|
9
30
|
|
|
10
31
|
### Changed — Framework Knowledge Graph is now MULTI-framework (not WordPress-only)
|
package/dist/index.js
CHANGED
|
@@ -56936,6 +56936,8 @@ function runBenchmark() {
|
|
|
56936
56936
|
const detectionRate = tp + fn > 0 ? tp / (tp + fn) : 0;
|
|
56937
56937
|
const falsePositiveRate = fp + tn > 0 ? fp / (fp + tn) : 0;
|
|
56938
56938
|
const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
|
|
56939
|
+
const recall = detectionRate;
|
|
56940
|
+
const f1 = precision + recall > 0 ? 2 * precision * recall / (precision + recall) : 0;
|
|
56939
56941
|
const byDetector = {};
|
|
56940
56942
|
for (const r of results) {
|
|
56941
56943
|
const d = r.entry.detector ?? "taint";
|
|
@@ -56960,6 +56962,8 @@ function runBenchmark() {
|
|
|
56960
56962
|
detection_rate: Number(detectionRate.toFixed(3)),
|
|
56961
56963
|
false_positive_rate: Number(falsePositiveRate.toFixed(3)),
|
|
56962
56964
|
precision: Number(precision.toFixed(3)),
|
|
56965
|
+
recall: Number(recall.toFixed(3)),
|
|
56966
|
+
f1: Number(f1.toFixed(3)),
|
|
56963
56967
|
by_detector: detectorMetrics,
|
|
56964
56968
|
captured_false_positives: {
|
|
56965
56969
|
total: captured.length,
|
|
@@ -57164,6 +57168,101 @@ function variantScan(root, opts = {}) {
|
|
|
57164
57168
|
};
|
|
57165
57169
|
}
|
|
57166
57170
|
|
|
57171
|
+
// src/action-heuristics.ts
|
|
57172
|
+
var ACTION_HEURISTICS = [
|
|
57173
|
+
{ pattern: /upload|import|install|activate|plugin|theme/, vuln_class: "Arbitrary File Upload / Plugin Install → RCE", detector: "file_upload + missing_authz", weight: 0.9, examples: ["ai1wm_import", "ocdi_upload_manual_import_files", "alone_import_pack_install_plugin", "revslider_upload"] },
|
|
57174
|
+
{ pattern: /write|save|delete|remove|move|rename/, vuln_class: "Arbitrary File Write / Delete / Move", detector: "file_operations", weight: 0.8, examples: ["ai1wm_backup_delete"] },
|
|
57175
|
+
{ pattern: /load|include|template|file|download|read/, vuln_class: "LFI / Path Traversal / RFI", detector: "path_traversal + file_inclusion", weight: 0.8, examples: ["duplicator_download", "get_file", "preview"] },
|
|
57176
|
+
{ pattern: /exec|run|cmd|shell|eval|code/, vuln_class: "OS Command / Code Injection / eval", detector: "command_execution + code_execution", weight: 0.85, examples: [] },
|
|
57177
|
+
{ pattern: /import|unserialize|restore|data/, vuln_class: "Insecure Deserialization / Object Injection", detector: "deserialization", weight: 0.8, examples: [] },
|
|
57178
|
+
{ pattern: /query|search|filter|export|list/, vuln_class: "SQLi → RCE / Create Admin", detector: "sql_execution", weight: 0.7, examples: [] },
|
|
57179
|
+
{ pattern: /render|shortcode|block|preview/, vuln_class: "SSTI / Shortcode / Block Injection", detector: "ssti", weight: 0.6, examples: [] },
|
|
57180
|
+
{ pattern: /settings|option|update/, vuln_class: "Unauth Options Update / Settings Change / Missing Cap+Nonce", detector: "missing_authz + missing_nonce", weight: 0.85, examples: ["woocommerce_*", "elementor_*"] },
|
|
57181
|
+
{ pattern: /login|auth|token|reset|verify/, vuln_class: "Auth Bypass / Auto Login / Account Takeover", detector: "missing_authz + type_juggling", weight: 0.9, examples: ["auto-login", "magic link", "reset password"] },
|
|
57182
|
+
{ pattern: /role|capability|promote|change_role/, vuln_class: "Privilege Escalation", detector: "missing_authz", weight: 0.9, examples: [] },
|
|
57183
|
+
{ pattern: /register|signup|create_user/, vuln_class: "Register → RCE / AFU / PrivEsc", detector: "file_upload + missing_authz", weight: 0.8, examples: [] }
|
|
57184
|
+
];
|
|
57185
|
+
function mapActionToVuln(action) {
|
|
57186
|
+
const a = action.toLowerCase();
|
|
57187
|
+
for (const h of ACTION_HEURISTICS) {
|
|
57188
|
+
if (h.pattern.test(a)) {
|
|
57189
|
+
return { vuln_class: h.vuln_class, detector: h.detector, weight: h.weight, examples: h.examples };
|
|
57190
|
+
}
|
|
57191
|
+
}
|
|
57192
|
+
return null;
|
|
57193
|
+
}
|
|
57194
|
+
function actionSurface(code) {
|
|
57195
|
+
const entries = [];
|
|
57196
|
+
const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
|
|
57197
|
+
let m;
|
|
57198
|
+
while ((m = ajaxRe.exec(code)) !== null) {
|
|
57199
|
+
const hook = m[1];
|
|
57200
|
+
const handler = m[2];
|
|
57201
|
+
const nopriv = hook.startsWith("wp_ajax_nopriv_");
|
|
57202
|
+
const action = hook.replace(/^wp_ajax(?:_nopriv)?_/, "");
|
|
57203
|
+
const mapped = mapActionToVuln(action);
|
|
57204
|
+
if (!mapped)
|
|
57205
|
+
continue;
|
|
57206
|
+
const body = functionBody2(code, handler);
|
|
57207
|
+
const hasCapability = body ? /current_user_can|is_user_logged_in|authorize/.test(body) : false;
|
|
57208
|
+
const hasNonce = body ? /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/.test(body) : false;
|
|
57209
|
+
let priority = mapped.weight;
|
|
57210
|
+
if (nopriv)
|
|
57211
|
+
priority += 0.05;
|
|
57212
|
+
if (!hasCapability)
|
|
57213
|
+
priority += 0.05;
|
|
57214
|
+
if (!hasNonce)
|
|
57215
|
+
priority += 0.05;
|
|
57216
|
+
priority = Math.min(1, Number(priority.toFixed(2)));
|
|
57217
|
+
entries.push({
|
|
57218
|
+
hook,
|
|
57219
|
+
action,
|
|
57220
|
+
nopriv,
|
|
57221
|
+
vuln_class: mapped.vuln_class,
|
|
57222
|
+
detector: mapped.detector,
|
|
57223
|
+
examples: mapped.examples,
|
|
57224
|
+
has_capability: hasCapability,
|
|
57225
|
+
has_nonce: hasNonce,
|
|
57226
|
+
priority
|
|
57227
|
+
});
|
|
57228
|
+
}
|
|
57229
|
+
entries.sort((a, b) => b.priority - a.priority);
|
|
57230
|
+
return {
|
|
57231
|
+
actions: entries.length,
|
|
57232
|
+
prioritized: entries.map((e) => ({
|
|
57233
|
+
action: e.action,
|
|
57234
|
+
hook: e.hook,
|
|
57235
|
+
vuln_class: e.vuln_class,
|
|
57236
|
+
detector: e.detector,
|
|
57237
|
+
priority: e.priority,
|
|
57238
|
+
nopriv: e.nopriv,
|
|
57239
|
+
has_capability: e.has_capability,
|
|
57240
|
+
has_nonce: e.has_nonce,
|
|
57241
|
+
examples: e.examples
|
|
57242
|
+
})),
|
|
57243
|
+
top_priority: entries[0] ?? null
|
|
57244
|
+
};
|
|
57245
|
+
}
|
|
57246
|
+
function functionBody2(code, name) {
|
|
57247
|
+
const idx = code.search(new RegExp(`function\\s+${name}\\s*\\(`));
|
|
57248
|
+
if (idx === -1)
|
|
57249
|
+
return null;
|
|
57250
|
+
const open = code.indexOf("{", idx);
|
|
57251
|
+
if (open === -1)
|
|
57252
|
+
return null;
|
|
57253
|
+
let depth = 0;
|
|
57254
|
+
for (let i = open;i < code.length; i++) {
|
|
57255
|
+
if (code[i] === "{")
|
|
57256
|
+
depth++;
|
|
57257
|
+
else if (code[i] === "}") {
|
|
57258
|
+
depth--;
|
|
57259
|
+
if (depth === 0)
|
|
57260
|
+
return code.slice(open, i + 1);
|
|
57261
|
+
}
|
|
57262
|
+
}
|
|
57263
|
+
return code.slice(open);
|
|
57264
|
+
}
|
|
57265
|
+
|
|
57167
57266
|
// src/enterprise-benchmark.ts
|
|
57168
57267
|
import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
57169
57268
|
import { join as join18, resolve } from "node:path";
|
|
@@ -58132,6 +58231,26 @@ ${statusLine}` : statusLine },
|
|
|
58132
58231
|
}, async ({ root, max_files, signature }) => {
|
|
58133
58232
|
return { content: [{ type: "text", text: JSON.stringify(variantScan(root, { maxFiles: max_files, signature })) }] };
|
|
58134
58233
|
});
|
|
58234
|
+
server.registerTool("action_surface", {
|
|
58235
|
+
title: "Action-name attack surface (what to look for WHERE)",
|
|
58236
|
+
description: "TARGETING: extract every wp_ajax_* action name, map it to its likely vuln class (upload/import→AFU/RCE, delete/write→file-write, load/download→LFI, login/reset→auth-bypass, exec/run→command injection, settings/update→unauth options change, etc.), cross-reference the hook (nopriv?) + authz gap (nonce/capability present?), and return a DERIVED priority — so the scanner targets the most dangerous action first, instead of scanning blindly.",
|
|
58237
|
+
inputSchema: {
|
|
58238
|
+
code: string2().optional().describe("Source code (PHP) to analyze"),
|
|
58239
|
+
path: string2().optional().describe("OR a source file path")
|
|
58240
|
+
}
|
|
58241
|
+
}, async ({ code, path }) => {
|
|
58242
|
+
let src = code;
|
|
58243
|
+
if (!src && path) {
|
|
58244
|
+
try {
|
|
58245
|
+
src = readFileSync22(path, "utf8");
|
|
58246
|
+
} catch {
|
|
58247
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
|
|
58248
|
+
}
|
|
58249
|
+
}
|
|
58250
|
+
if (!src)
|
|
58251
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide code or path" }) }] };
|
|
58252
|
+
return { content: [{ type: "text", text: JSON.stringify(actionSurface(src)) }] };
|
|
58253
|
+
});
|
|
58135
58254
|
server.registerTool("payload_lookup", {
|
|
58136
58255
|
title: "Look up exploit payloads",
|
|
58137
58256
|
description: "INTEL: find a payload collection (66 categories from PayloadsAllTheThings).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.38",
|
|
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": {
|