blitzstrike 2.4.167 → 2.4.169

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,33 @@ 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.169] — 2026-10-11
9
+
10
+ ### Added — finding retest loop (post-confirmation lifecycle)
11
+
12
+ - The finding lifecycle no longer ends at `confirmed`: added `retesting`, `fixed`,
13
+ and `still_vulnerable` states with the transitions `confirmed -> retesting ->
14
+ fixed | still_vulnerable`, and `still_vulnerable -> retesting` (re-retest after
15
+ the next patch window; `fixed` is terminal).
16
+ - New `retest_finding` tool: pass a confirmed/still_vulnerable finding + a retest
17
+ verdict (`fixed` or `still_vulnerable`) + reason; it advances the lifecycle and
18
+ records the verdict + reason as evidence. Run `strike_verify` first to obtain the
19
+ live verdict, then record it here.
20
+
21
+ ## [2.4.168] — 2026-10-11
22
+
23
+ ### Fixed — blind_sqli_extract: space extraction + drift-proof time fallback
24
+
25
+ - Added SPACE (+ parens/comma) to the extraction linear-fallback charset, so
26
+ version/database strings with embedded spaces (e.g. "Microsoft SQL Server 2012")
27
+ extract fully instead of stopping at the first space.
28
+ - Auto-fall back to the TIME oracle when a marker-less byte-count boolean oracle
29
+ extracts nothing — the time oracle is deterministic and immune to page-size
30
+ drift on dynamic remote pages.
31
+ - Excluded WAF-sensitive chars (#, =, <, >, quotes, %, &, ;, |, *) from the
32
+ charset: a WAF that strips one of them turns the comparison into `= ''`
33
+ (always-true past the string end), which previously produced a false trailing run.
34
+
8
35
  ## [2.4.167] — 2026-10-11
9
36
 
10
37
  ### Fixed — blind_oracle detection false-negative + false-positive (real target)
package/dist/index.js CHANGED
@@ -33277,7 +33277,10 @@ var LIFECYCLE_TRANSITIONS = {
33277
33277
  triaged: ["hypothesis", "rejected", "out_of_scope"],
33278
33278
  hypothesis: ["validating", "false_positive", "rejected", "blocked"],
33279
33279
  validating: ["confirmed", "false_positive", "blocked", "out_of_scope"],
33280
- confirmed: [],
33280
+ confirmed: ["retesting"],
33281
+ retesting: ["fixed", "still_vulnerable", "false_positive"],
33282
+ fixed: [],
33283
+ still_vulnerable: ["retesting"],
33281
33284
  false_positive: [],
33282
33285
  rejected: [],
33283
33286
  blocked: [],
@@ -33440,6 +33443,17 @@ function rejectFinding(finding, status, reason, evidence) {
33440
33443
  timestamps: { ...updated.timestamps, updated: new Date().toISOString() }
33441
33444
  };
33442
33445
  }
33446
+ function retestFinding(finding, opts) {
33447
+ const retesting = transition(finding, "retesting");
33448
+ const ev = opts.evidence?.map((e) => makeEvidence(e)) ?? [];
33449
+ const result = transition(retesting, opts.verdict);
33450
+ return {
33451
+ ...result,
33452
+ remediation: { ...result.remediation, retest_note: opts.reason },
33453
+ evidence: [...result.evidence, ...ev],
33454
+ timestamps: { ...result.timestamps, updated: new Date().toISOString() }
33455
+ };
33456
+ }
33443
33457
 
33444
33458
  // src/sinks.ts
33445
33459
  init_secrets();
@@ -62729,7 +62743,7 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62729
62743
  }
62730
62744
  }
62731
62745
  }
62732
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_/.-:@";
62746
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_/.-:@(), ";
62733
62747
  const caseInsensitive = p.dbms === "mysql" || p.dbms === "mssql";
62734
62748
  const bsCharset = caseInsensitive ? "0123456789abcdefghijklmnopqrstuvwxyz" : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
62735
62749
  const baseTpl = (channel === "time" ? p.extraction.time : p.extraction.boolean).replace(/^'/, ctx);
@@ -62792,6 +62806,9 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62792
62806
  out += ch;
62793
62807
  }
62794
62808
  out = out.trimEnd();
62809
+ if (out.length === 0 && channel === "boolean" && !marker) {
62810
+ return await blindSqliExtract(baseUrl, param, value, dbms, target, { ...opts, channel: "time" });
62811
+ }
62795
62812
  return {
62796
62813
  dbms,
62797
62814
  target,
@@ -66338,7 +66355,7 @@ ${statusLine}` : statusLine },
66338
66355
  });
66339
66356
  server.registerTool("finding_transition", {
66340
66357
  title: "Advance a finding lifecycle (STATEFUL)",
66341
- description: "FINDINGS: advance a canonical Finding through its lifecycle (detected->triaged->hypothesis->validating->confirmed) and RETURN the updated finding. STATEFUL — pass the FULL finding JSON (from finding_create / run_engagement), not a status string. Rejects illegal transitions with the legal next states. Confirmed is terminal. To confirm FROM a live verdict use strike_resolve instead.",
66358
+ description: "FINDINGS: advance a canonical Finding through its lifecycle (detected->triaged->hypothesis->validating->confirmed, then the retest loop confirmed->retesting->fixed|still_vulnerable) and RETURN the updated finding. STATEFUL — pass the FULL finding JSON (from finding_create / run_engagement), not a status string. Rejects illegal transitions with the legal next states. To confirm FROM a live verdict use strike_resolve; to re-verify a confirmed finding use retest_finding.",
66342
66359
  inputSchema: {
66343
66360
  finding: string2().describe("JSON of the canonical Finding object (from finding_create / run_engagement)"),
66344
66361
  to: string2().describe("Target status (triaged/hypothesis/validating/confirmed/false_positive/rejected/blocked/out_of_scope)")
@@ -66367,6 +66384,46 @@ ${statusLine}` : statusLine },
66367
66384
  }]
66368
66385
  };
66369
66386
  });
66387
+ server.registerTool("retest_finding", {
66388
+ title: "Re-verify a confirmed finding (retest loop)",
66389
+ description: "FINDINGS: re-verify a CONFIRMED (or still_vulnerable) finding to check whether the vendor patched it. Pass the full finding JSON + the retest verdict: 'fixed' (the exploit no longer reproduces — patch applied) or 'still_vulnerable' (still exploitable). Advances confirmed -> retesting -> fixed|still_vulnerable and records the verdict + reason as evidence. still_vulnerable findings can be re-retested later. Run strike_verify FIRST to get the live verdict, then record it here.",
66390
+ inputSchema: {
66391
+ finding: string2().describe("JSON of the canonical Finding object (status confirmed or still_vulnerable)"),
66392
+ verdict: _enum(["fixed", "still_vulnerable"]).describe("Retest outcome: fixed (patch applied) or still_vulnerable (still exploitable)"),
66393
+ reason: string2().describe("Short note: what changed / what still reproduces"),
66394
+ evidence: string2().optional().describe("Optional JSON array of evidence entries [{type, description, content}]")
66395
+ }
66396
+ }, async ({ finding, verdict, reason, evidence }) => {
66397
+ let f;
66398
+ try {
66399
+ f = JSON.parse(finding);
66400
+ } catch {
66401
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON — pass the full Finding object" }) }] };
66402
+ }
66403
+ const from = f.status ?? "detected";
66404
+ if (!(from === "confirmed" || from === "still_vulnerable")) {
66405
+ return { content: [{ type: "text", text: JSON.stringify({ error: `retest only valid from 'confirmed' or 'still_vulnerable', not '${from}'`, legal_next: LIFECYCLE_TRANSITIONS[from] ?? [] }) }] };
66406
+ }
66407
+ let ev = [];
66408
+ if (evidence) {
66409
+ try {
66410
+ ev = JSON.parse(evidence);
66411
+ } catch {
66412
+ ev = [];
66413
+ }
66414
+ }
66415
+ const updated = retestFinding(f, { verdict, reason, evidence: ev });
66416
+ return {
66417
+ content: [{
66418
+ type: "text",
66419
+ text: JSON.stringify({
66420
+ ...updated,
66421
+ transition: `${from} -> retesting -> ${updated.status}`,
66422
+ ...updated.status === "fixed" ? { hint: "FIXED — the vendor patched it. The retest closes this finding's loop." } : { hint: "STILL_VULNERABLE — re-retest later via retest_finding after the next patch window." }
66423
+ })
66424
+ }]
66425
+ };
66426
+ });
66370
66427
  server.registerTool("capture_false_positive", {
66371
66428
  title: "Capture a verified false positive (self-hardening)",
66372
66429
  description: "SELF-HARDENING: record a VERIFIED false positive (a code pattern the detector flagged but live verification ruled out) into the writable benchmark corpus, so the next run_benchmark measures whether the detector STILL flags it. Pass the triggering CODE + language + detector + a short note. Every capture makes the detector more precise — close the loop, never just discard a refuted lead.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.167",
3
+ "version": "2.4.169",
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": {