blitzstrike 2.4.168 → 2.4.170

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.170] — 2026-10-11
9
+
10
+ ### Added — 3-way retest verdict + planner doctrine (from ARTEX reference)
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 distilled from ARTEX's planner/pentest/worker discipline:
16
+ `never_stall` (always emit the next action — a 0-action stall is not restraint),
17
+ `breadth_then_focus` (2-3 mechanically-different routes in parallel, then focus),
18
+ `quantitative_acceptance` (verify measured coverage/findings before "done"),
19
+ `negative_recheck_once` (a negative observation is not a verdict — re-check once),
20
+ `observed_vs_inferred` (never upgrade a version/CVE/inferred hit to confirmed).
21
+
22
+ ## [2.4.169] — 2026-10-11
23
+
24
+ ### Added — finding retest loop (post-confirmation lifecycle)
25
+
26
+ - The finding lifecycle no longer ends at `confirmed`: added `retesting`, `fixed`,
27
+ and `still_vulnerable` states with the transitions `confirmed -> retesting ->
28
+ fixed | still_vulnerable`, and `still_vulnerable -> retesting` (re-retest after
29
+ the next patch window; `fixed` is terminal).
30
+ - New `retest_finding` tool: pass a confirmed/still_vulnerable finding + a retest
31
+ verdict (`fixed` or `still_vulnerable`) + reason; it advances the lifecycle and
32
+ records the verdict + reason as evidence. Run `strike_verify` first to obtain the
33
+ live verdict, then record it here.
34
+
8
35
  ## [2.4.168] — 2026-10-11
9
36
 
10
37
  ### Fixed — blind_sqli_extract: space extraction + drift-proof time fallback
package/dist/index.js CHANGED
@@ -33277,7 +33277,11 @@ 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", "inconclusive", "false_positive"],
33282
+ fixed: [],
33283
+ still_vulnerable: ["retesting"],
33284
+ inconclusive: ["retesting"],
33281
33285
  false_positive: [],
33282
33286
  rejected: [],
33283
33287
  blocked: [],
@@ -33440,6 +33444,17 @@ function rejectFinding(finding, status, reason, evidence) {
33440
33444
  timestamps: { ...updated.timestamps, updated: new Date().toISOString() }
33441
33445
  };
33442
33446
  }
33447
+ function retestFinding(finding, opts) {
33448
+ const retesting = transition(finding, "retesting");
33449
+ const ev = opts.evidence?.map((e) => makeEvidence(e)) ?? [];
33450
+ const result = transition(retesting, opts.verdict);
33451
+ return {
33452
+ ...result,
33453
+ remediation: { ...result.remediation, retest_note: opts.reason },
33454
+ evidence: [...result.evidence, ...ev],
33455
+ timestamps: { ...result.timestamps, updated: new Date().toISOString() }
33456
+ };
33457
+ }
33443
33458
 
33444
33459
  // src/sinks.ts
33445
33460
  init_secrets();
@@ -63721,6 +63736,31 @@ var HUNTING_DOCTRINE = [
63721
63736
  id: "per_domain_exhaustion",
63722
63737
  title: "Depth-First Per Domain — Drive Each to Exhaustion (Dead-End)",
63723
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."
63724
63764
  }
63725
63765
  ];
63726
63766
  var DOMAIN_EXHAUSTION_CHECKLIST = [
@@ -66341,7 +66381,7 @@ ${statusLine}` : statusLine },
66341
66381
  });
66342
66382
  server.registerTool("finding_transition", {
66343
66383
  title: "Advance a finding lifecycle (STATEFUL)",
66344
- 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.",
66384
+ 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.",
66345
66385
  inputSchema: {
66346
66386
  finding: string2().describe("JSON of the canonical Finding object (from finding_create / run_engagement)"),
66347
66387
  to: string2().describe("Target status (triaged/hypothesis/validating/confirmed/false_positive/rejected/blocked/out_of_scope)")
@@ -66370,6 +66410,43 @@ ${statusLine}` : statusLine },
66370
66410
  }]
66371
66411
  };
66372
66412
  });
66413
+ server.registerTool("retest_finding", {
66414
+ title: "Re-verify a confirmed finding (retest loop)",
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.",
66416
+ inputSchema: {
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"),
66420
+ evidence: string2().optional().describe("Optional JSON array of evidence entries [{type, description, content}]")
66421
+ }
66422
+ }, async ({ finding, verdict, reason, evidence }) => {
66423
+ let f;
66424
+ try {
66425
+ f = JSON.parse(finding);
66426
+ } catch {
66427
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid finding JSON — pass the full Finding object" }) }] };
66428
+ }
66429
+ const from = f.status ?? "detected";
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] ?? [] }) }] };
66432
+ }
66433
+ let ev = [];
66434
+ if (evidence) {
66435
+ try {
66436
+ ev = JSON.parse(evidence);
66437
+ } catch {
66438
+ ev = [];
66439
+ }
66440
+ }
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.";
66443
+ return {
66444
+ content: [{
66445
+ type: "text",
66446
+ text: JSON.stringify({ ...updated, transition: `${from} -> retesting -> ${updated.status}`, hint })
66447
+ }]
66448
+ };
66449
+ });
66373
66450
  server.registerTool("capture_false_positive", {
66374
66451
  title: "Capture a verified false positive (self-hardening)",
66375
66452
  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.168",
3
+ "version": "2.4.170",
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": {