blitzstrike 2.4.37 → 2.4.39
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 +25 -0
- package/dist/index.js +238 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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.39] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — Patch Reversal (1-day weaponization engine)
|
|
11
|
+
|
|
12
|
+
- `analyzePatch` (in differential.ts): read a security patch (old vs new code)
|
|
13
|
+
and REVERSE it — detect what the patch ADDED (sanitizer, auth/nonce gate,
|
|
14
|
+
parameterized query, input validation) or REMOVED (a dangerous sink fed by
|
|
15
|
+
attacker input), and reconstruct the vulnerability in the OLD version.
|
|
16
|
+
- New `patch_analyze` tool: each reversal carries a DERIVED `mine_signature` to
|
|
17
|
+
sweep unpatched installs via `variant_scan(root, { signature })`.
|
|
18
|
+
- A cosmetic-only change (no security delta) correctly yields no reversal.
|
|
19
|
+
|
|
20
|
+
## [2.4.38] — 2026-09-14
|
|
21
|
+
|
|
22
|
+
### Added — Action-Name Heuristic Engine (what to look for WHERE)
|
|
23
|
+
|
|
24
|
+
- New `src/action-heuristics.ts`: maps WordPress AJAX action-name patterns to
|
|
25
|
+
their likely vulnerability class (upload/import→AFU/RCE, delete/write→
|
|
26
|
+
file-write, load/download→LFI, login/reset→auth-bypass, exec/run→command
|
|
27
|
+
injection, settings/update→unauth options change, …) — as DATA, with real
|
|
28
|
+
examples (ai1wm_import, revslider_upload, duplicator_download, …).
|
|
29
|
+
- New `action_surface` tool: extract every `wp_ajax_*` action, cross-reference
|
|
30
|
+
the hook (nopriv?) + authz gap (nonce/capability), and return a DERIVED
|
|
31
|
+
priority so the scanner targets the most dangerous action first.
|
|
32
|
+
|
|
8
33
|
## [2.4.37] — 2026-09-14
|
|
9
34
|
|
|
10
35
|
### Fixed — benchmark top-level recall/f1 (self-audit)
|
package/dist/index.js
CHANGED
|
@@ -55613,6 +55613,110 @@ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
55613
55613
|
const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
|
|
55614
55614
|
return { repo, base, head, files, total_changes, security_sensitive };
|
|
55615
55615
|
}
|
|
55616
|
+
var PATCH_RANK = { high: 0, medium: 1 };
|
|
55617
|
+
function analyzePatch(oldCode, newCode, language = "php") {
|
|
55618
|
+
const { added, removed } = diffLines(oldCode, newCode);
|
|
55619
|
+
const findings = [];
|
|
55620
|
+
for (const l of added) {
|
|
55621
|
+
const sans = findSanitizers(l.text);
|
|
55622
|
+
if (sans.length > 0) {
|
|
55623
|
+
findings.push({
|
|
55624
|
+
kind: "sanitizer_added",
|
|
55625
|
+
old_vuln: "raw attacker input reached a sink without escaping",
|
|
55626
|
+
new_fix: l.text.trim(),
|
|
55627
|
+
severity: "high",
|
|
55628
|
+
line: l.line,
|
|
55629
|
+
detail: `sanitizer ADDED: ${sans.map((s) => s.label).join(", ")} — the OLD version passed raw input to a sink (sanitization gap).`,
|
|
55630
|
+
signature: "patch:sanitizer_added",
|
|
55631
|
+
mine_signature: "complex_bugs:wrong_sanitizer"
|
|
55632
|
+
});
|
|
55633
|
+
}
|
|
55634
|
+
const gates = AUTH_GATES.filter((g) => l.text.includes(g));
|
|
55635
|
+
if (gates.length > 0) {
|
|
55636
|
+
findings.push({
|
|
55637
|
+
kind: "authorization_added",
|
|
55638
|
+
old_vuln: "missing authorization / nonce check",
|
|
55639
|
+
new_fix: l.text.trim(),
|
|
55640
|
+
severity: "high",
|
|
55641
|
+
line: l.line,
|
|
55642
|
+
detail: `authorization gate ADDED: ${gates.join(", ")} — the OLD version performed no auth/nonce check (auth bypass / CSRF).`,
|
|
55643
|
+
signature: "patch:authorization_added",
|
|
55644
|
+
mine_signature: "complex_bugs:missing_authz"
|
|
55645
|
+
});
|
|
55646
|
+
}
|
|
55647
|
+
if (/->prepare\s*\(|\bprepare\s*\(\s*["']/.test(l.text)) {
|
|
55648
|
+
findings.push({
|
|
55649
|
+
kind: "prepared_statement_added",
|
|
55650
|
+
old_vuln: "raw SQL string interpolation",
|
|
55651
|
+
new_fix: l.text.trim(),
|
|
55652
|
+
severity: "high",
|
|
55653
|
+
line: l.line,
|
|
55654
|
+
detail: "parameterized query ADDED — the OLD version concatenated user input into SQL (SQLi).",
|
|
55655
|
+
signature: "patch:prepared_statement_added",
|
|
55656
|
+
mine_signature: "complex_bugs:wrong_sanitizer"
|
|
55657
|
+
});
|
|
55658
|
+
}
|
|
55659
|
+
if (/preg_match\s*\(\s*["']\/\^|in_array\s*\(\s*\$|ctype_[a-z]+|is_numeric\s*\(|filter_var\s*\(/.test(l.text)) {
|
|
55660
|
+
findings.push({
|
|
55661
|
+
kind: "validation_added",
|
|
55662
|
+
old_vuln: "unvalidated / arbitrary input accepted",
|
|
55663
|
+
new_fix: l.text.trim(),
|
|
55664
|
+
severity: "medium",
|
|
55665
|
+
line: l.line,
|
|
55666
|
+
detail: "input validation ADDED — the OLD version accepted arbitrary input (type/format gap).",
|
|
55667
|
+
signature: "patch:validation_added",
|
|
55668
|
+
mine_signature: "complex_bugs:wrong_sanitizer"
|
|
55669
|
+
});
|
|
55670
|
+
}
|
|
55671
|
+
}
|
|
55672
|
+
for (const l of removed) {
|
|
55673
|
+
const sink = classifySink2(l.text);
|
|
55674
|
+
const source = classifySource(l.text);
|
|
55675
|
+
if (sink && source && source.attacker_controlled) {
|
|
55676
|
+
findings.push({
|
|
55677
|
+
kind: "sink_removed",
|
|
55678
|
+
old_vuln: `dangerous sink fed by attacker input: ${sink.category}`,
|
|
55679
|
+
new_fix: "(removed)",
|
|
55680
|
+
cwe: sink.cwe,
|
|
55681
|
+
severity: "high",
|
|
55682
|
+
line: l.line,
|
|
55683
|
+
detail: `sink REMOVED: ${l.text.trim()} (${sink.category}) — the OLD version executed this dangerous operation on attacker input.`,
|
|
55684
|
+
signature: `patch:sink_removed:${sink.category}`,
|
|
55685
|
+
mine_signature: `taint:${sink.category}`
|
|
55686
|
+
});
|
|
55687
|
+
}
|
|
55688
|
+
}
|
|
55689
|
+
findings.sort((a, b) => PATCH_RANK[a.severity] - PATCH_RANK[b.severity] || a.line - b.line);
|
|
55690
|
+
const count = (k) => findings.filter((f) => f.kind === k).length;
|
|
55691
|
+
return {
|
|
55692
|
+
language,
|
|
55693
|
+
reversal_findings: findings.map((f) => ({
|
|
55694
|
+
kind: f.kind,
|
|
55695
|
+
old_vuln: f.old_vuln,
|
|
55696
|
+
new_fix: f.new_fix,
|
|
55697
|
+
cwe: f.cwe,
|
|
55698
|
+
line: f.line,
|
|
55699
|
+
severity: f.severity,
|
|
55700
|
+
detail: f.detail,
|
|
55701
|
+
signature: f.signature,
|
|
55702
|
+
mine_signature: f.mine_signature
|
|
55703
|
+
})),
|
|
55704
|
+
summary: {
|
|
55705
|
+
sanitizer_added: count("sanitizer_added"),
|
|
55706
|
+
authorization_added: count("authorization_added"),
|
|
55707
|
+
prepared_statement_added: count("prepared_statement_added"),
|
|
55708
|
+
validation_added: count("validation_added"),
|
|
55709
|
+
sink_removed: count("sink_removed")
|
|
55710
|
+
},
|
|
55711
|
+
interpretation: "Each reversal is the vulnerability REVERSED from the patch: the OLD version had this bug, the NEW version fixed it. Sweep unpatched installs with variant_scan(root, { signature: mine_signature })."
|
|
55712
|
+
};
|
|
55713
|
+
}
|
|
55714
|
+
function analyzePatchFiles(oldPath, newPath, language) {
|
|
55715
|
+
const oldCode = readFileSync15(oldPath, "utf8");
|
|
55716
|
+
const newCode = readFileSync15(newPath, "utf8");
|
|
55717
|
+
const lang = language ?? languageOf(newPath);
|
|
55718
|
+
return analyzePatch(oldCode, newCode, lang);
|
|
55719
|
+
}
|
|
55616
55720
|
|
|
55617
55721
|
// src/frameworks.ts
|
|
55618
55722
|
import { readFileSync as readFileSync16, existsSync as existsSync8 } from "node:fs";
|
|
@@ -57168,6 +57272,101 @@ function variantScan(root, opts = {}) {
|
|
|
57168
57272
|
};
|
|
57169
57273
|
}
|
|
57170
57274
|
|
|
57275
|
+
// src/action-heuristics.ts
|
|
57276
|
+
var ACTION_HEURISTICS = [
|
|
57277
|
+
{ 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"] },
|
|
57278
|
+
{ pattern: /write|save|delete|remove|move|rename/, vuln_class: "Arbitrary File Write / Delete / Move", detector: "file_operations", weight: 0.8, examples: ["ai1wm_backup_delete"] },
|
|
57279
|
+
{ 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"] },
|
|
57280
|
+
{ pattern: /exec|run|cmd|shell|eval|code/, vuln_class: "OS Command / Code Injection / eval", detector: "command_execution + code_execution", weight: 0.85, examples: [] },
|
|
57281
|
+
{ pattern: /import|unserialize|restore|data/, vuln_class: "Insecure Deserialization / Object Injection", detector: "deserialization", weight: 0.8, examples: [] },
|
|
57282
|
+
{ pattern: /query|search|filter|export|list/, vuln_class: "SQLi → RCE / Create Admin", detector: "sql_execution", weight: 0.7, examples: [] },
|
|
57283
|
+
{ pattern: /render|shortcode|block|preview/, vuln_class: "SSTI / Shortcode / Block Injection", detector: "ssti", weight: 0.6, examples: [] },
|
|
57284
|
+
{ 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_*"] },
|
|
57285
|
+
{ 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"] },
|
|
57286
|
+
{ pattern: /role|capability|promote|change_role/, vuln_class: "Privilege Escalation", detector: "missing_authz", weight: 0.9, examples: [] },
|
|
57287
|
+
{ pattern: /register|signup|create_user/, vuln_class: "Register → RCE / AFU / PrivEsc", detector: "file_upload + missing_authz", weight: 0.8, examples: [] }
|
|
57288
|
+
];
|
|
57289
|
+
function mapActionToVuln(action) {
|
|
57290
|
+
const a = action.toLowerCase();
|
|
57291
|
+
for (const h of ACTION_HEURISTICS) {
|
|
57292
|
+
if (h.pattern.test(a)) {
|
|
57293
|
+
return { vuln_class: h.vuln_class, detector: h.detector, weight: h.weight, examples: h.examples };
|
|
57294
|
+
}
|
|
57295
|
+
}
|
|
57296
|
+
return null;
|
|
57297
|
+
}
|
|
57298
|
+
function actionSurface(code) {
|
|
57299
|
+
const entries = [];
|
|
57300
|
+
const ajaxRe = /add_action\s*\(\s*['"](wp_ajax(?:_nopriv)?_\w+)['"]\s*,\s*['"](\w+)['"]/g;
|
|
57301
|
+
let m;
|
|
57302
|
+
while ((m = ajaxRe.exec(code)) !== null) {
|
|
57303
|
+
const hook = m[1];
|
|
57304
|
+
const handler = m[2];
|
|
57305
|
+
const nopriv = hook.startsWith("wp_ajax_nopriv_");
|
|
57306
|
+
const action = hook.replace(/^wp_ajax(?:_nopriv)?_/, "");
|
|
57307
|
+
const mapped = mapActionToVuln(action);
|
|
57308
|
+
if (!mapped)
|
|
57309
|
+
continue;
|
|
57310
|
+
const body = functionBody2(code, handler);
|
|
57311
|
+
const hasCapability = body ? /current_user_can|is_user_logged_in|authorize/.test(body) : false;
|
|
57312
|
+
const hasNonce = body ? /wp_verify_nonce|check_ajax_referer|check_admin_referer|verify_nonce/.test(body) : false;
|
|
57313
|
+
let priority = mapped.weight;
|
|
57314
|
+
if (nopriv)
|
|
57315
|
+
priority += 0.05;
|
|
57316
|
+
if (!hasCapability)
|
|
57317
|
+
priority += 0.05;
|
|
57318
|
+
if (!hasNonce)
|
|
57319
|
+
priority += 0.05;
|
|
57320
|
+
priority = Math.min(1, Number(priority.toFixed(2)));
|
|
57321
|
+
entries.push({
|
|
57322
|
+
hook,
|
|
57323
|
+
action,
|
|
57324
|
+
nopriv,
|
|
57325
|
+
vuln_class: mapped.vuln_class,
|
|
57326
|
+
detector: mapped.detector,
|
|
57327
|
+
examples: mapped.examples,
|
|
57328
|
+
has_capability: hasCapability,
|
|
57329
|
+
has_nonce: hasNonce,
|
|
57330
|
+
priority
|
|
57331
|
+
});
|
|
57332
|
+
}
|
|
57333
|
+
entries.sort((a, b) => b.priority - a.priority);
|
|
57334
|
+
return {
|
|
57335
|
+
actions: entries.length,
|
|
57336
|
+
prioritized: entries.map((e) => ({
|
|
57337
|
+
action: e.action,
|
|
57338
|
+
hook: e.hook,
|
|
57339
|
+
vuln_class: e.vuln_class,
|
|
57340
|
+
detector: e.detector,
|
|
57341
|
+
priority: e.priority,
|
|
57342
|
+
nopriv: e.nopriv,
|
|
57343
|
+
has_capability: e.has_capability,
|
|
57344
|
+
has_nonce: e.has_nonce,
|
|
57345
|
+
examples: e.examples
|
|
57346
|
+
})),
|
|
57347
|
+
top_priority: entries[0] ?? null
|
|
57348
|
+
};
|
|
57349
|
+
}
|
|
57350
|
+
function functionBody2(code, name) {
|
|
57351
|
+
const idx = code.search(new RegExp(`function\\s+${name}\\s*\\(`));
|
|
57352
|
+
if (idx === -1)
|
|
57353
|
+
return null;
|
|
57354
|
+
const open = code.indexOf("{", idx);
|
|
57355
|
+
if (open === -1)
|
|
57356
|
+
return null;
|
|
57357
|
+
let depth = 0;
|
|
57358
|
+
for (let i = open;i < code.length; i++) {
|
|
57359
|
+
if (code[i] === "{")
|
|
57360
|
+
depth++;
|
|
57361
|
+
else if (code[i] === "}") {
|
|
57362
|
+
depth--;
|
|
57363
|
+
if (depth === 0)
|
|
57364
|
+
return code.slice(open, i + 1);
|
|
57365
|
+
}
|
|
57366
|
+
}
|
|
57367
|
+
return code.slice(open);
|
|
57368
|
+
}
|
|
57369
|
+
|
|
57171
57370
|
// src/enterprise-benchmark.ts
|
|
57172
57371
|
import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
57173
57372
|
import { join as join18, resolve } from "node:path";
|
|
@@ -58136,6 +58335,26 @@ ${statusLine}` : statusLine },
|
|
|
58136
58335
|
}, async ({ root, max_files, signature }) => {
|
|
58137
58336
|
return { content: [{ type: "text", text: JSON.stringify(variantScan(root, { maxFiles: max_files, signature })) }] };
|
|
58138
58337
|
});
|
|
58338
|
+
server.registerTool("action_surface", {
|
|
58339
|
+
title: "Action-name attack surface (what to look for WHERE)",
|
|
58340
|
+
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.",
|
|
58341
|
+
inputSchema: {
|
|
58342
|
+
code: string2().optional().describe("Source code (PHP) to analyze"),
|
|
58343
|
+
path: string2().optional().describe("OR a source file path")
|
|
58344
|
+
}
|
|
58345
|
+
}, async ({ code, path }) => {
|
|
58346
|
+
let src = code;
|
|
58347
|
+
if (!src && path) {
|
|
58348
|
+
try {
|
|
58349
|
+
src = readFileSync22(path, "utf8");
|
|
58350
|
+
} catch {
|
|
58351
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "unreadable path" }) }] };
|
|
58352
|
+
}
|
|
58353
|
+
}
|
|
58354
|
+
if (!src)
|
|
58355
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide code or path" }) }] };
|
|
58356
|
+
return { content: [{ type: "text", text: JSON.stringify(actionSurface(src)) }] };
|
|
58357
|
+
});
|
|
58139
58358
|
server.registerTool("payload_lookup", {
|
|
58140
58359
|
title: "Look up exploit payloads",
|
|
58141
58360
|
description: "INTEL: find a payload collection (66 categories from PayloadsAllTheThings).",
|
|
@@ -58725,6 +58944,25 @@ ${statusLine}` : statusLine },
|
|
|
58725
58944
|
}
|
|
58726
58945
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
58727
58946
|
});
|
|
58947
|
+
server.registerTool("patch_analyze", {
|
|
58948
|
+
title: "Patch reversal (1-day weaponization)",
|
|
58949
|
+
description: "1-DAY: read a security patch (old vs new code) and REVERSE it — find what the patch ADDED to make the code safer (sanitizer, auth/nonce gate, parameterized query, validation) or REMOVED (a dangerous sink), and reconstruct the vulnerability in the OLD version. Each reversal carries a DERIVED mine_signature to sweep unpatched installs via variant_scan(root, { signature: mine_signature }).",
|
|
58950
|
+
inputSchema: {
|
|
58951
|
+
old_path: string2().optional().describe("Old file path"),
|
|
58952
|
+
new_path: string2().optional().describe("New file path"),
|
|
58953
|
+
old_code: string2().optional().describe("Old source code (when not using paths)"),
|
|
58954
|
+
new_code: string2().optional().describe("New source code (when not using paths)"),
|
|
58955
|
+
language: string2().optional().describe("Language (default php)")
|
|
58956
|
+
}
|
|
58957
|
+
}, async ({ old_path, new_path, old_code, new_code, language }) => {
|
|
58958
|
+
if (old_path && new_path) {
|
|
58959
|
+
return { content: [{ type: "text", text: JSON.stringify(analyzePatchFiles(old_path, new_path, language)) }] };
|
|
58960
|
+
}
|
|
58961
|
+
if (old_code !== undefined && new_code !== undefined) {
|
|
58962
|
+
return { content: [{ type: "text", text: JSON.stringify(analyzePatch(old_code, new_code, language ?? "php")) }] };
|
|
58963
|
+
}
|
|
58964
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path) or (old_code+new_code)" }) }] };
|
|
58965
|
+
});
|
|
58728
58966
|
server.registerTool("incremental_scan", {
|
|
58729
58967
|
title: "Incremental scan (CI)",
|
|
58730
58968
|
description: "EAGLE-EYE: analyze only the files changed between two git refs (default HEAD~1..HEAD) — the fast CI pass. Runs the taint + data-flow engine on just the changed source files and skips unchanged ones; the full-scan path (run_engagement / enrich_scan) remains authoritative.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.39",
|
|
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": {
|