blitzstrike 2.4.169 → 2.4.171

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,20 @@ 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.170] — 2026-10-11
9
+
10
+ ### Added — 3-way retest verdict + planner discipline
11
+
12
+ - `retest_finding` now supports a third verdict `inconclusive` (could not determine —
13
+ WAF blocked / target down / missing login / evidence insufficient), so one failed
14
+ request is never recorded as "fixed". `inconclusive -> retesting` re-opens the loop.
15
+ - Added 5 doctrine rules: `never_stall` (always emit the next action — a 0-action
16
+ stall is not restraint), `breadth_then_focus` (2-3 mechanically-different routes in
17
+ parallel, then focus), `quantitative_acceptance` (verify measured coverage/findings
18
+ before "done"), `negative_recheck_once` (a negative observation is not a verdict —
19
+ re-check once), `observed_vs_inferred` (never upgrade a version/CVE/inferred hit
20
+ to confirmed).
21
+
8
22
  ## [2.4.169] — 2026-10-11
9
23
 
10
24
  ### Added — finding retest loop (post-confirmation lifecycle)
package/dist/index.js CHANGED
@@ -33278,9 +33278,10 @@ var LIFECYCLE_TRANSITIONS = {
33278
33278
  hypothesis: ["validating", "false_positive", "rejected", "blocked"],
33279
33279
  validating: ["confirmed", "false_positive", "blocked", "out_of_scope"],
33280
33280
  confirmed: ["retesting"],
33281
- retesting: ["fixed", "still_vulnerable", "false_positive"],
33281
+ retesting: ["fixed", "still_vulnerable", "inconclusive", "false_positive"],
33282
33282
  fixed: [],
33283
33283
  still_vulnerable: ["retesting"],
33284
+ inconclusive: ["retesting"],
33284
33285
  false_positive: [],
33285
33286
  rejected: [],
33286
33287
  blocked: [],
@@ -63735,6 +63736,31 @@ var HUNTING_DOCTRINE = [
63735
63736
  id: "per_domain_exhaustion",
63736
63737
  title: "Depth-First Per Domain — Drive Each to Exhaustion (Dead-End)",
63737
63738
  rule: "When scope spans many domains/hosts, treat EACH as a SEPARATE engagement and go DEPTH-FIRST: finish one domain to total exhaustion before touching the next — never spray a shallow recon across the whole scope. A domain is EXHAUSTED (a true dead-end) only when EVERY item on the exhaustion checklist passes: (a) every subdomain/host enumerated (asset_discovery), (b) every endpoint/param discovered (param_surface / action_surface / JS refs), (c) every class × eligible shape carries a terminal verdict (confirmed/dead/partial/skipped), (d) every hypothesis verified (strike_verify / OOB) or refuted (dead verdict), (e) every foothold escalated to REAL impact (records/admin/PII) or genuinely blocked, and (f) zero obligations remain pending (next_obligation returns none). Only then move to the next domain — a shallow 'found one bug' pass is NEVER a finished domain, and skipping any checklist item means the domain is NOT done."
63739
+ },
63740
+ {
63741
+ id: "never_stall",
63742
+ title: "Never Stall — Always Emit the Next Action",
63743
+ rule: "HARD BOTTOM LINE: whenever the goal is NOT met and there is no in-flight or queued work (zero pending obligations, zero un-verified hypotheses), you MUST produce at least ONE concrete next action toward the goal — emitting zero actions at that moment is a stall, not restraint. A '0 next steps' answer is only legitimate when (a) every identified direction is already covered by in-flight work, or (b) the next step truly depends on an upstream result that has not landed yet. Never let 'nothing left to do' be the lazy default while a goal is unmet and scope remains untested."
63744
+ },
63745
+ {
63746
+ id: "breadth_then_focus",
63747
+ title: "Breadth-First Route Diversity, Then Focus",
63748
+ rule: "Do NOT tunnel-vision the first promising lead. Open by laying out 2–3 MECHANICALLY-DIFFERENT routes in parallel (e.g. an upload chain vs an auth-bypass chain) and let them progress side-by-side; only concentrate effort on a route once it delivers evidence that actually approaches the goal. Early tunnel-vision onto one elegant route is the single-agent failure mode that misses the real bug. Diversity is bounded by scope/constraints: a direction excluded by scope is never opened no matter how different."
63749
+ },
63750
+ {
63751
+ id: "quantitative_acceptance",
63752
+ title: "Quantitative Acceptance Gate — No Premature 'Done'",
63753
+ rule: "When a goal carries a QUANTITATIVE condition (coverage ≥ X%, N flags, a specific privilege, a named asset), you MUST verify the MEASURED value (coverage %, findings count, obtained privilege) against that condition BEFORE marking it met — never declare 'done' on 'roughly achieved / core is in hand'. If the measured value falls short, keep hunting to close the gap instead of stamping the goal complete early."
63754
+ },
63755
+ {
63756
+ id: "negative_recheck_once",
63757
+ title: "A Negative Observation Is Not a Conclusion — Re-Check Once",
63758
+ rule: "A recorded negative ('not injectable', 'port closed', 'not vulnerable', 'filter blocked it') is an OBSERVATION, not a verdict. Before trusting it as a dead-end, inspect its evidence: if it is solid (controlled disproof, path exhausted via multiple encodings/methods/params) then respect it; if it is weak ('looked like' / 'tried once' / inferred) and the direction is in-scope with no other coverage, dispatch ONE re-check via a DIFFERENT path. Re-check the same negative direction AT MOST ONCE — after a second solid negative, record it as dead and move on; never spin on 'one more try'."
63759
+ },
63760
+ {
63761
+ id: "observed_vs_inferred",
63762
+ title: "Observed vs Inferred — Never Upgrade Inferred to Confirmed",
63763
+ rule: "Tag every conclusion with its confidence honestly: OBSERVED (you directly triggered it and hold the request/response or command output) vs INFERRED (you deduced it from a fingerprint/version/diff/phenomenon without actually triggering it). A version/CVE match, a 'looks injectable' param, or a patch-diff inference is INFERRED and must never be reported as a confirmed finding — record it as a hypothesis and verify it live (strike_verify / OOB / a clean oracle) before it can claim confirmed. Disproof is worth as much as proof: an honest dead verdict is a deliverable, an overstated inferred-as-confirmed is a retraction waiting to happen."
63738
63764
  }
63739
63765
  ];
63740
63766
  var DOMAIN_EXHAUSTION_CHECKLIST = [
@@ -66386,11 +66412,11 @@ ${statusLine}` : statusLine },
66386
66412
  });
66387
66413
  server.registerTool("retest_finding", {
66388
66414
  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.",
66415
+ description: "FINDINGS: re-verify a CONFIRMED (or still_vulnerable/inconclusive) finding to check whether the vendor patched it. Pass the full finding JSON + the retest verdict: 'fixed' (exploit no longer reproduces — patch applied), 'still_vulnerable' (still exploitable), or 'inconclusive' (could NOT determine — WAF blocked / target down / missing login / evidence insufficient; one failed request proves nothing). Advances confirmed -> retesting -> fixed|still_vulnerable|inconclusive and records the verdict + reason as evidence. Run strike_verify FIRST to get the live verdict, then record it here.",
66390
66416
  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"),
66417
+ finding: string2().describe("JSON of the canonical Finding object (status confirmed, still_vulnerable, or inconclusive)"),
66418
+ verdict: _enum(["fixed", "still_vulnerable", "inconclusive"]).describe("Retest outcome: fixed (patch applied), still_vulnerable (still exploitable), or inconclusive (could not determine)"),
66419
+ reason: string2().describe("Short note: what changed / what still reproduces / what blocked the check"),
66394
66420
  evidence: string2().optional().describe("Optional JSON array of evidence entries [{type, description, content}]")
66395
66421
  }
66396
66422
  }, async ({ finding, verdict, reason, evidence }) => {
@@ -66401,8 +66427,8 @@ ${statusLine}` : statusLine },
66401
66427
  return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON — pass the full Finding object" }) }] };
66402
66428
  }
66403
66429
  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] ?? [] }) }] };
66430
+ if (!(from === "confirmed" || from === "still_vulnerable" || from === "inconclusive")) {
66431
+ return { content: [{ type: "text", text: JSON.stringify({ error: `retest only valid from 'confirmed'/'still_vulnerable'/'inconclusive', not '${from}'`, legal_next: LIFECYCLE_TRANSITIONS[from] ?? [] }) }] };
66406
66432
  }
66407
66433
  let ev = [];
66408
66434
  if (evidence) {
@@ -66413,14 +66439,11 @@ ${statusLine}` : statusLine },
66413
66439
  }
66414
66440
  }
66415
66441
  const updated = retestFinding(f, { verdict, reason, evidence: ev });
66442
+ const hint = updated.status === "fixed" ? "FIXED — the vendor patched it. The retest closes this finding's loop." : updated.status === "inconclusive" ? "INCONCLUSIVE — could not determine (blocker). One failed request proves nothing; re-retest via retest_finding once the blocker clears." : "STILL_VULNERABLE — re-retest later via retest_finding after the next patch window.";
66416
66443
  return {
66417
66444
  content: [{
66418
66445
  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
- })
66446
+ text: JSON.stringify({ ...updated, transition: `${from} -> retesting -> ${updated.status}`, hint })
66424
66447
  }]
66425
66448
  };
66426
66449
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.169",
3
+ "version": "2.4.171",
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": {