blitzstrike 2.4.41 → 2.4.43

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,32 @@ 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.43] — 2026-09-14
9
+
10
+ ### Added — Race Condition / TOCTOU Tester (concurrency oracle)
11
+
12
+ - New `src/race-test.ts`: detect TOCTOU races + idempotency violations — fire
13
+ a CONCURRENT BURST of N identical requests and count how many succeed. If
14
+ more than `expected_max` (default 1) succeed, the operation is not atomic
15
+ under concurrency (double-spend, duplicate orders, discount abuse).
16
+ - New `race_test` tool: success criterion is a caller-supplied regex (the
17
+ "marker" of a committed side effect). Pure `classifyRace` + `requestSignature`
18
+ for deterministic, testable logic.
19
+
20
+ ## [2.4.42] — 2026-09-14
21
+
22
+ ### Added — Duplicate Parameter Precedence Fuzzer (WAF-bypass oracle)
23
+
24
+ - New `src/param-precedence.ts`: determine how the app resolves duplicate
25
+ parameters + alternate delimiters (`;` `,` `%26` `%3b` `|` `[]` `%00` `+`),
26
+ and classify first-wins / last-wins / mixed precedence.
27
+ - New `param_precedence` tool: baseline A/B + differential per variant, with a
28
+ DERIVED waf_bypass_hint (inject via the WAF/app disagreement).
29
+ - System prompt: added a "LIVE PENTEST + TARGETING" directive wiring the new
30
+ black-box tools (cache_gap / blind_oracle / param_precedence) and source
31
+ targeting tools (action_surface / security_state / framework_knowledge /
32
+ variant_scan / patch_analyze) into a coherent, evidence-first workflow.
33
+
8
34
  ## [2.4.41] — 2026-09-14
9
35
 
10
36
  ### Added — Blind Differential Oracle (calibration-based sink detection)
package/dist/index.js CHANGED
@@ -57623,6 +57623,133 @@ async function blindOracle(baseUrl, param, value, opts = {}) {
57623
57623
  };
57624
57624
  }
57625
57625
 
57626
+ // src/param-precedence.ts
57627
+ function generatePrecedenceVariants(param, a, b) {
57628
+ const enc = encodeURIComponent;
57629
+ const variants = [
57630
+ { name: "duplicate_amp", qs: `${enc(param)}=${enc(a)}&${enc(param)}=${enc(b)}` },
57631
+ { name: "semicolon", qs: `${enc(param)}=${enc(a)};${enc(param)}=${enc(b)}` },
57632
+ { name: "comma", qs: `${enc(param)}=${enc(a)},${enc(param)}=${enc(b)}` },
57633
+ { name: "encoded_amp", qs: `${enc(param)}=${enc(a)}%26${enc(param)}=${enc(b)}` },
57634
+ { name: "encoded_semicolon", qs: `${enc(param)}=${enc(a)}%3b${enc(param)}=${enc(b)}` },
57635
+ { name: "pipe", qs: `${enc(param)}=${enc(a)}|${enc(param)}=${enc(b)}` },
57636
+ { name: "array", qs: `${enc(param)}[]=${enc(a)}&${enc(param)}[]=${enc(b)}` },
57637
+ { name: "null_byte", qs: `${enc(param)}=${enc(a)}%00${enc(b)}` },
57638
+ { name: "space", qs: `${enc(param)}=${enc(a)}+${enc(b)}` }
57639
+ ];
57640
+ return variants;
57641
+ }
57642
+ function classifyPrecedence(input) {
57643
+ const h = input.bodyHash;
57644
+ if (h === input.hashA) {
57645
+ return { winner: "A", split: true, detail: "value A won (the first/leftmost value) — first-wins precedence for this delimiter." };
57646
+ }
57647
+ if (h === input.hashB) {
57648
+ return { winner: "B", split: true, detail: "value B won (the last/rightmost value) — last-wins precedence for this delimiter." };
57649
+ }
57650
+ return { winner: "neither", split: false, detail: "neither A nor B matched — the delimiter may be literal (not a parameter separator) or the values were joined." };
57651
+ }
57652
+ async function paramPrecedenceScan(baseUrl, param, valueA, valueB, opts = {}) {
57653
+ const base = baseUrl.replace(/\/+$/, "");
57654
+ const headers = { "User-Agent": "Mozilla/5.0 (blitzstrike precedence probe)" };
57655
+ const timeoutMs = opts.timeoutMs ?? 8000;
57656
+ const enc = encodeURIComponent;
57657
+ const fetchHash = async (qs) => {
57658
+ try {
57659
+ const r = await fetch(`${base}?${qs}`, { headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
57660
+ const body = await r.text();
57661
+ return { status: r.status, hash: sha256(body) };
57662
+ } catch (e) {
57663
+ return { status: 0, hash: "", error: String(e) };
57664
+ }
57665
+ };
57666
+ const baseA = await fetchHash(`${enc(param)}=${enc(valueA)}`);
57667
+ const baseB = await fetchHash(`${enc(param)}=${enc(valueB)}`);
57668
+ if (baseA.error || baseB.error) {
57669
+ return { error: "baseline fetch failed", base_url: base, param, detail: baseA.error ?? baseB.error };
57670
+ }
57671
+ if (baseA.hash === baseB.hash) {
57672
+ return { error: "baseline A and B produce identical responses — pick two values with a distinguishable effect", base_url: base, param };
57673
+ }
57674
+ const variants = generatePrecedenceVariants(param, valueA, valueB);
57675
+ const results = [];
57676
+ let firstWins = 0;
57677
+ let lastWins = 0;
57678
+ for (const v of variants) {
57679
+ const r = await fetchHash(v.qs);
57680
+ if (r.error) {
57681
+ results.push({ name: v.name, qs: v.qs, status: 0, body_hash: "", winner: "neither", split: false, detail: "fetch error/timeout" });
57682
+ continue;
57683
+ }
57684
+ const cls = classifyPrecedence({ hashA: baseA.hash, hashB: baseB.hash, bodyHash: r.hash, name: v.name });
57685
+ if (cls.winner === "A")
57686
+ firstWins++;
57687
+ else if (cls.winner === "B")
57688
+ lastWins++;
57689
+ results.push({ name: v.name, qs: v.qs, status: r.status, body_hash: r.hash.slice(0, 12), winner: cls.winner, split: cls.split, detail: cls.detail });
57690
+ }
57691
+ let precedence = "ambiguous";
57692
+ if (firstWins > 0 && lastWins === 0)
57693
+ precedence = "first_wins";
57694
+ else if (lastWins > 0 && firstWins === 0)
57695
+ precedence = "last_wins";
57696
+ else if (firstWins > 0 && lastWins > 0)
57697
+ precedence = "mixed_by_delimiter";
57698
+ return {
57699
+ base_url: base,
57700
+ param,
57701
+ value_a: valueA,
57702
+ value_b: valueB,
57703
+ precedence,
57704
+ first_wins_count: firstWins,
57705
+ last_wins_count: lastWins,
57706
+ variants_tested: results.length,
57707
+ results,
57708
+ waf_bypass_hint: precedence === "first_wins" ? "the app uses the FIRST duplicate value — a WAF that inspects the LAST value is bypassed with ?param=benign&param=payload." : precedence === "last_wins" ? "the app uses the LAST duplicate value — a WAF that inspects the FIRST value is bypassed with ?param=payload&param=benign." : "precedence is mixed by delimiter — map which delimiter the WAF validates vs which the app binds, then inject via the disagreement."
57709
+ };
57710
+ }
57711
+
57712
+ // src/race-test.ts
57713
+ function classifyRace(input) {
57714
+ const race = input.successes > input.expected_max;
57715
+ return {
57716
+ race,
57717
+ verdict: race ? `race condition / idempotency violation — ${input.successes} of ${input.total} concurrent requests succeeded (expected ≤ ${input.expected_max}).` : `no race — ${input.successes} of ${input.total} succeeded (within expected ${input.expected_max}).`
57718
+ };
57719
+ }
57720
+ async function raceTest(input) {
57721
+ const method = input.method ?? "POST";
57722
+ const N = input.concurrent ?? 10;
57723
+ const expected = input.expected_max ?? 1;
57724
+ const successRe = new RegExp(input.success_pattern, "i");
57725
+ const timeoutMs = input.timeoutMs ?? 1e4;
57726
+ const headers = { "User-Agent": "Mozilla/5.0 (blitzstrike race probe)", ...input.headers ?? {} };
57727
+ const doFetch = async () => {
57728
+ try {
57729
+ const r = await fetch(input.url, { method, body: input.body, headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
57730
+ return { status: r.status, body: await r.text() };
57731
+ } catch (e) {
57732
+ return { status: 0, body: "", error: String(e) };
57733
+ }
57734
+ };
57735
+ const results = await Promise.all(Array.from({ length: N }, () => doFetch()));
57736
+ const successes = results.filter((r) => r.status > 0 && successRe.test(r.body)).length;
57737
+ const errors = results.filter((r) => r.error).length;
57738
+ const verdict = classifyRace({ successes, total: N, expected_max: expected });
57739
+ return {
57740
+ url: input.url,
57741
+ method,
57742
+ concurrent_requests: N,
57743
+ expected_max: expected,
57744
+ concurrent_successes: successes,
57745
+ fetch_errors: errors,
57746
+ success_rate: Number((successes / N).toFixed(3)),
57747
+ race_detected: verdict.race,
57748
+ verdict: verdict.verdict,
57749
+ detail: verdict.race ? "multiple concurrent requests each passed the check before any committed the side effect (TOCTOU)." : "the operation appears atomic/idempotent under this concurrency (a tighter single-packet burst via HTTP/2 may still win; this is a parallel-request approximation)."
57750
+ };
57751
+ }
57752
+
57626
57753
  // src/enterprise-benchmark.ts
57627
57754
  import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
57628
57755
  import { join as join18, resolve } from "node:path";
@@ -57862,8 +57989,7 @@ function createServer() {
57862
57989
  ` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
57863
57990
 
57864
57991
  ` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + `DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.
57865
- ` + `KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability).
57866
- ` + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
57992
+ ` + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
57867
57993
  ` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
57868
57994
  ` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
57869
57995
  ` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
@@ -58640,6 +58766,32 @@ ${statusLine}` : statusLine },
58640
58766
  }
58641
58767
  return { content: [{ type: "text", text: JSON.stringify(await blindOracle(base_url, param, value, { sinks: s })) }] };
58642
58768
  });
58769
+ server.registerTool("param_precedence", {
58770
+ title: "Duplicate parameter precedence fuzzer (WAF-bypass oracle)",
58771
+ description: "LIVE (black-box): determine how the app resolves duplicate parameters + alternate delimiters (; , %26 %3b | [] %00 + #). Reveals the WAF-bypass vector — if the WAF validates one value and the app binds another, inject via the disagreement. Deterministic: baseline A/B + differential per variant.",
58772
+ inputSchema: {
58773
+ base_url: string2().describe("Target base URL"),
58774
+ param: string2().describe("Parameter name (e.g. id)"),
58775
+ value_a: string2().describe("First value (e.g. 1)"),
58776
+ value_b: string2().describe("Second value (e.g. 2)")
58777
+ }
58778
+ }, async ({ base_url, param, value_a, value_b }) => {
58779
+ return { content: [{ type: "text", text: JSON.stringify(await paramPrecedenceScan(base_url, param, value_a, value_b)) }] };
58780
+ });
58781
+ server.registerTool("race_test", {
58782
+ title: "Race condition / TOCTOU tester (concurrency oracle)",
58783
+ description: "LIVE (black-box): detect TOCTOU race conditions + idempotency violations — fire a sanity request, then a CONCURRENT BURST of N identical requests, and count how many succeed. If more than expected_max succeed, the operation is not atomic under concurrency (double-spend, duplicate orders, discount abuse, mass action). Deterministic: success is a caller-supplied regex.",
58784
+ inputSchema: {
58785
+ url: string2().describe("Full request URL"),
58786
+ method: string2().optional().describe("HTTP method (default POST)"),
58787
+ body: string2().optional().describe("Request body"),
58788
+ success_pattern: string2().describe("Regex that matches a 'success' response (the committed side effect)"),
58789
+ concurrent: number2().optional().describe("Number of concurrent requests (default 10)"),
58790
+ expected_max: number2().optional().describe("Max expected successes (default 1)")
58791
+ }
58792
+ }, async ({ url, method, body, success_pattern, concurrent, expected_max }) => {
58793
+ return { content: [{ type: "text", text: JSON.stringify(await raceTest({ url, method, body, success_pattern, concurrent, expected_max })) }] };
58794
+ });
58643
58795
  server.registerTool("payload_lookup", {
58644
58796
  title: "Look up exploit payloads",
58645
58797
  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.41",
3
+ "version": "2.4.43",
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": {