blitzstrike 1.0.48 → 1.0.51

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/index.js +217 -20
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,48 @@ 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
+ ## [1.0.51] — 2026-09-13
9
+
10
+ ### Added — Autonomous Security Engine (phase 11: adversarial reviewer)
11
+
12
+ - **Adversarial Reviewer** (`src/mission/reviewer.ts`, §15):
13
+ - `reviewFinding()` — challenges a hypothesis with the ten §15 questions
14
+ (reachability, auth required, authorization elsewhere, middleware, attacker
15
+ control, sink reachability, caching, reproducibility, negative control,
16
+ impact accuracy), each a deterministic check against evidence + Security Graph.
17
+ - Returns CONFIRMED / REJECTED / NEEDS_MORE_EVIDENCE.
18
+ - `reviewValidated()` — reviews every validated hypothesis in a mission.
19
+ - `@reviewer` now uses the full §15 review (rejects on any hard fail; demands
20
+ more evidence when supporting signals are too weak).
21
+
22
+ ## [1.0.50] — 2026-09-13
23
+
24
+ ### Added — Autonomous Security Engine (phase 10: cross-agent memory)
25
+
26
+ - **Cross-agent Memory** (`src/mission/memory.ts`, §20):
27
+ - `persistMissionMemory()` — persists confirmed findings (verified),
28
+ rejected findings, and failed approaches (with `future_policy`) into the
29
+ long-term memory store; pending hypotheses are NOT persisted (unverified).
30
+ - `recallForTarget()` — retrieves memory relevant to a target, to seed a
31
+ fresh mission.
32
+ - Mission Control's `finalize` now persists reusable knowledge and records the
33
+ saved/deduped count in `memory_refs`.
34
+
35
+ ## [1.0.49] — 2026-09-13
36
+
37
+ ### Added — Autonomous Security Engine (phase 9: failure recovery)
38
+
39
+ - **Failure Recovery** (`src/mission/recovery.ts`, §17):
40
+ - `diagnoseError()` — categorizes failures (network / permission /
41
+ missing_tool / invalid_args / scope / unknown).
42
+ - `recoveryDecision()` — network → retry (bounded); missing_tool → route to an
43
+ alternative agent; scope / permission / invalid_args → record (no retry).
44
+ - `AGENT_ALTERNATIVES` — fallback specialization per agent type.
45
+ - `recordFailure()` — a failed-approach record with a `future_policy` so the
46
+ autonomous system never blindly repeats the same mistake.
47
+ - Mission Control now routes task failures through the recovery engine and
48
+ persists `failed_approaches` into the mission state.
49
+
8
50
  ## [1.0.48] — 2026-09-13
9
51
 
10
52
  ### Added — Autonomous Security Engine (phase 8: coverage-driven scheduler)
package/dist/index.js CHANGED
@@ -17903,7 +17903,8 @@ function newMission(id, target, scope, mode, budget) {
17903
17903
  coverage: { dimensions: {} },
17904
17904
  memory_refs: [],
17905
17905
  termination_reason: null,
17906
- decision_log: []
17906
+ decision_log: [],
17907
+ failed_approaches: []
17907
17908
  };
17908
17909
  }
17909
17910
 
@@ -18257,6 +18258,92 @@ var init_planner = __esm(() => {
18257
18258
  RISK_PENALTY = { low: 0, medium: 0.1, high: 0.2 };
18258
18259
  });
18259
18260
 
18261
+ // src/mission/reviewer.ts
18262
+ function checkAll(h, mission) {
18263
+ const ev = h.evidence.toLowerCase();
18264
+ const sinkNode = mission.graph.nodes.find((n) => n.properties && n.properties.hypothesis === h.id);
18265
+ const reachable = sinkNode ? incoming(mission.graph, sinkNode.id).length > 0 : false;
18266
+ return [
18267
+ {
18268
+ id: "reachable_code",
18269
+ question: "Is the vulnerable code actually reachable?",
18270
+ verdict: reachable ? "pass" : "unknown",
18271
+ note: reachable ? "sink has an incoming flow edge" : "no flow edge in the graph"
18272
+ },
18273
+ {
18274
+ id: "auth_required",
18275
+ question: "Is authentication required?",
18276
+ verdict: /auth-gated|auth_required|login required/i.test(ev) ? "pass" : "unknown",
18277
+ note: /auth-gated|auth_required|login required/i.test(ev) ? "auth gate detected" : "no auth gate signal"
18278
+ },
18279
+ {
18280
+ id: "authz_elsewhere",
18281
+ question: "Is authorization enforced elsewhere?",
18282
+ verdict: /missing_authorization/i.test(h.type) ? "fail" : "unknown",
18283
+ note: /missing_authorization/i.test(h.type) ? "no authorization boundary detected" : "not an authorization finding"
18284
+ },
18285
+ {
18286
+ id: "middleware",
18287
+ question: "Is there middleware protection?",
18288
+ verdict: /sanitized|middleware|waf|firewall/i.test(ev) ? "pass" : "unknown",
18289
+ note: /sanitized|middleware|waf|firewall/i.test(ev) ? "sanitizer/middleware present" : "no protection signal"
18290
+ },
18291
+ {
18292
+ id: "attacker_controlled",
18293
+ question: "Is the input actually attacker-controlled?",
18294
+ verdict: /attacker-controlled|user input|\$_(get|post|request|cookie)/i.test(ev) ? "pass" : "unknown",
18295
+ note: /attacker-controlled|user input|\$_(get|post|request|cookie)/i.test(ev) ? "input is attacker-controlled" : "input provenance unclear"
18296
+ },
18297
+ {
18298
+ id: "sink_reachable",
18299
+ question: "Is the sink reachable?",
18300
+ verdict: sinkNode ? "pass" : "unknown",
18301
+ note: sinkNode ? "sink node present in graph" : "sink not mapped in graph"
18302
+ },
18303
+ {
18304
+ id: "caching",
18305
+ question: "Is the observed behavior caused by caching?",
18306
+ verdict: /cache|etag|304/i.test(ev) ? "fail" : "unknown",
18307
+ note: /cache|etag|304/i.test(ev) ? "possible cache artifact" : "no cache signal"
18308
+ },
18309
+ {
18310
+ id: "reproducible",
18311
+ question: "Is the exploit reproducible?",
18312
+ verdict: /verdict=confirmed|reproducible|marker/i.test(ev) ? "pass" : "unknown",
18313
+ note: /verdict=confirmed|reproducible|marker/i.test(ev) ? "live validation performed" : "no live validation"
18314
+ },
18315
+ {
18316
+ id: "negative_control",
18317
+ question: "Does the negative control remain inert?",
18318
+ verdict: /control (not|did not|inert)|control=false|verdict=confirmed/i.test(ev) ? "pass" : /control.*reflected|benign echo|false_positive/i.test(ev) ? "fail" : "unknown",
18319
+ note: /benign echo|false_positive/i.test(ev) ? "control also reflected" : /verdict=confirmed/i.test(ev) ? "control inert" : "no negative control"
18320
+ },
18321
+ {
18322
+ id: "impact_accurate",
18323
+ question: "Is the impact accurately classified?",
18324
+ verdict: /cwe-\d+/i.test(h.potential_impact) || h.potential_impact !== "unspecified" ? "pass" : "unknown",
18325
+ note: h.potential_impact !== "unspecified" ? `impact: ${h.potential_impact}` : "impact unspecified"
18326
+ }
18327
+ ];
18328
+ }
18329
+ function reviewFinding(h, mission) {
18330
+ const questions = checkAll(h, mission);
18331
+ const fails = questions.filter((q) => q.verdict === "fail").length;
18332
+ const passes = questions.filter((q) => q.verdict === "pass").length;
18333
+ let outcome;
18334
+ if (fails > 0)
18335
+ outcome = "REJECTED";
18336
+ else if (passes < 3)
18337
+ outcome = "NEEDS_MORE_EVIDENCE";
18338
+ else
18339
+ outcome = "CONFIRMED";
18340
+ return { hypothesis_id: h.id, outcome, questions };
18341
+ }
18342
+ function reviewValidated(mission) {
18343
+ return mission.hypotheses.filter((h) => h.status === "validated").map((h) => reviewFinding(h, mission));
18344
+ }
18345
+ var init_reviewer = () => {};
18346
+
18260
18347
  // src/agents.ts
18261
18348
  import { readFileSync as readFileSync18 } from "node:fs";
18262
18349
  function emptyGraph() {
@@ -18275,6 +18362,7 @@ function agentFor(type) {
18275
18362
  var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
18276
18363
  var init_agents = __esm(() => {
18277
18364
  init_planner();
18365
+ init_reviewer();
18278
18366
  init_orchestrator();
18279
18367
  init_scanner();
18280
18368
  init_eagle2();
@@ -18454,25 +18542,18 @@ var init_agents = __esm(() => {
18454
18542
  responsibility: "Challenge findings before confirmation (reachability, auth, controls).",
18455
18543
  tools: ["read_skill", "eagle_eye2"],
18456
18544
  async execute(_task, ctx) {
18457
- const outcomes = [];
18545
+ const results = reviewValidated(ctx.mission);
18458
18546
  const hypotheses = [];
18459
- for (const h of ctx.mission.hypotheses) {
18460
- if (h.status !== "validated")
18547
+ for (const r of results) {
18548
+ if (r.outcome !== "REJECTED")
18461
18549
  continue;
18462
- const sinkNode = ctx.mission.graph.nodes.find((n) => n.properties && n.properties.hypothesis === h.id);
18463
- const reachable = sinkNode ? incoming(ctx.mission.graph, sinkNode.id).length > 0 : false;
18464
- let outcome = "CONFIRMED";
18465
- if (!reachable)
18466
- outcome = "NEEDS_MORE_EVIDENCE";
18467
- else if (h.confidence < 0.3)
18468
- outcome = "REJECTED";
18469
- else if (!/reachability|verification|proof|negative control/i.test(h.required_validation))
18470
- outcome = "NEEDS_MORE_EVIDENCE";
18471
- outcomes.push({ hypothesis: h.id, outcome });
18472
- if (outcome === "REJECTED")
18473
- hypotheses.push(rejectHypothesis(h, "adversarial review: confidence below threshold"));
18474
- }
18475
- return { ok: true, output: { outcomes }, hypotheses, graph: emptyGraph() };
18550
+ const h = ctx.mission.hypotheses.find((x) => x.id === r.hypothesis_id);
18551
+ if (h) {
18552
+ const failIds = r.questions.filter((q) => q.verdict === "fail").map((q) => q.id).join(", ");
18553
+ hypotheses.push(rejectHypothesis(h, `adversarial review rejected: ${failIds || "unexplainable"}`));
18554
+ }
18555
+ }
18556
+ return { ok: true, output: { outcomes: results.map((r) => ({ id: r.hypothesis_id, outcome: r.outcome })) }, hypotheses, graph: emptyGraph() };
18476
18557
  }
18477
18558
  };
18478
18559
  reporterAgent = {
@@ -18654,6 +18735,95 @@ var init_coverage = __esm(() => {
18654
18735
  };
18655
18736
  });
18656
18737
 
18738
+ // src/mission/recovery.ts
18739
+ function diagnoseError(error) {
18740
+ const e = error.toLowerCase();
18741
+ if (/network|connect|timeout|econn|enotfound|unreachable|dns|resolve|refused|abort/i.test(e))
18742
+ return "network";
18743
+ if (/permission|eacces|denied|forbidden|401|403/i.test(e))
18744
+ return "permission";
18745
+ if (/not (installed|found)|no such|command not found|missing tool/i.test(e))
18746
+ return "missing_tool";
18747
+ if (/invalid|argument|usage|required|schema|type/i.test(e))
18748
+ return "invalid_args";
18749
+ if (/scope|authorized|out of scope|unauthorized/i.test(e))
18750
+ return "scope";
18751
+ return "unknown";
18752
+ }
18753
+ function recoveryDecision(task, error) {
18754
+ const category = diagnoseError(error);
18755
+ switch (category) {
18756
+ case "network":
18757
+ return task.attempts < 3 ? { action: "retry", reason: "transient network failure — retry", alternative_agent: null } : { action: "record", reason: "persistent network failure", alternative_agent: null };
18758
+ case "missing_tool":
18759
+ return { action: "alternative", reason: "tool unavailable — try alternative", alternative_agent: AGENT_ALTERNATIVES[task.agent_type]?.[0] ?? null };
18760
+ case "invalid_args":
18761
+ return { action: "record", reason: "invalid arguments — cannot auto-recover", alternative_agent: null };
18762
+ case "scope":
18763
+ return { action: "record", reason: "out of scope — do not retry", alternative_agent: null };
18764
+ case "permission":
18765
+ return { action: "record", reason: "permission denied — cannot auto-recover", alternative_agent: null };
18766
+ default:
18767
+ return task.attempts < 2 ? { action: "retry", reason: "unknown failure — retry once", alternative_agent: null } : { action: "record", reason: "unknown failure — give up", alternative_agent: null };
18768
+ }
18769
+ }
18770
+ function recordFailure(task, error) {
18771
+ const category = diagnoseError(error);
18772
+ return {
18773
+ technique: `${task.agent_type}:${task.objective}`,
18774
+ target: task.objective,
18775
+ category,
18776
+ reason: error,
18777
+ attempts: task.attempts,
18778
+ future_policy: category === "scope" ? "do not retry — out of scope" : category === "missing_tool" ? "route to an alternative agent next time" : category === "network" ? "retry with backoff" : "lower priority for this technique"
18779
+ };
18780
+ }
18781
+ var AGENT_ALTERNATIVES;
18782
+ var init_recovery = __esm(() => {
18783
+ AGENT_ALTERNATIVES = {
18784
+ commander: [],
18785
+ recon: ["api", "web"],
18786
+ web: ["api", "recon"],
18787
+ api: ["recon", "web"],
18788
+ source: ["analysis"],
18789
+ auth: ["analysis", "source"],
18790
+ mobile: ["source"],
18791
+ cloud: ["source"],
18792
+ analysis: ["source"],
18793
+ validator: ["reviewer"],
18794
+ reviewer: ["validator"],
18795
+ reporter: []
18796
+ };
18797
+ });
18798
+
18799
+ // src/mission/memory.ts
18800
+ function recordHypothesisOutcome(type, target, status, evidence) {
18801
+ return remember(`finding:${type}`, `[${status}] ${type} @ ${target}: ${evidence}`, status === "validated" ? "signature" : "lesson", [type.toLowerCase(), status], "mission", status === "validated").saved === true;
18802
+ }
18803
+ function recordFailedApproach(fa) {
18804
+ return remember(`failed:${fa.technique}`, `${fa.technique} failed (${fa.category}): ${fa.reason} — ${fa.future_policy}`, "lesson", ["failed", fa.category], "mission", false).saved === true;
18805
+ }
18806
+ function persistMissionMemory(mission) {
18807
+ let attempted = 0;
18808
+ let saved = 0;
18809
+ for (const h of mission.hypotheses) {
18810
+ if (h.status !== "validated" && h.status !== "rejected")
18811
+ continue;
18812
+ attempted += 1;
18813
+ if (recordHypothesisOutcome(h.type, h.target, h.status, h.evidence))
18814
+ saved += 1;
18815
+ }
18816
+ for (const fa of mission.failed_approaches) {
18817
+ attempted += 1;
18818
+ if (recordFailedApproach(fa))
18819
+ saved += 1;
18820
+ }
18821
+ return { saved, deduped: attempted - saved };
18822
+ }
18823
+ var init_memory2 = __esm(() => {
18824
+ init_memory();
18825
+ });
18826
+
18657
18827
  // src/mission/control.ts
18658
18828
  import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
18659
18829
  function scopeAllowed(mission) {
@@ -18774,8 +18944,29 @@ async function runMission(mission) {
18774
18944
  m = { ...m, report: r.output };
18775
18945
  }
18776
18946
  } else {
18777
- const failed = setTask(m.tasks, r.task.task_id, { status: r.task.attempts >= 2 ? "failed" : "retry", error: r.error ?? null });
18778
- m = { ...m, tasks: failed, decision_log: [...m.decision_log, { at: new Date().toISOString(), agent: r.task.agent_type, decision }] };
18947
+ const rec = recoveryDecision(r.task, r.error ?? "unknown error");
18948
+ const stamp = { at: new Date().toISOString(), agent: r.task.agent_type, decision };
18949
+ if (rec.action === "retry") {
18950
+ const retried = setTask(m.tasks, r.task.task_id, { status: "retry", error: r.error ?? null });
18951
+ m = { ...m, tasks: retried, decision_log: [...m.decision_log, stamp] };
18952
+ } else if (rec.action === "alternative" && rec.alternative_agent) {
18953
+ const failed = setTask(m.tasks, r.task.task_id, { status: "failed", error: r.error ?? null });
18954
+ const alt = createTask({ objective: `[alternative] ${r.task.objective}`, agent_type: rec.alternative_agent, risk: r.task.risk, priority: r.task.priority - 1 });
18955
+ m = {
18956
+ ...m,
18957
+ tasks: [...failed, alt],
18958
+ decision_log: [...m.decision_log, stamp],
18959
+ failed_approaches: [...m.failed_approaches, recordFailure(r.task, r.error ?? "")]
18960
+ };
18961
+ } else {
18962
+ const failed = setTask(m.tasks, r.task.task_id, { status: "failed", error: r.error ?? null });
18963
+ m = {
18964
+ ...m,
18965
+ tasks: failed,
18966
+ decision_log: [...m.decision_log, stamp],
18967
+ failed_approaches: [...m.failed_approaches, recordFailure(r.task, r.error ?? "")]
18968
+ };
18969
+ }
18779
18970
  }
18780
18971
  }
18781
18972
  m.budget.used_tasks += batch.length;
@@ -18795,6 +18986,10 @@ function finalize2(mission) {
18795
18986
  tasks: allResolved(m.tasks) ? 1 : 0,
18796
18987
  hypotheses_validated: m.hypotheses.length > 0 ? m.hypotheses.filter((h) => h.status === "validated").length / m.hypotheses.length : 0
18797
18988
  };
18989
+ const mem = persistMissionMemory(m);
18990
+ if (mem.saved > 0) {
18991
+ m = { ...m, memory_refs: [...m.memory_refs, `persisted:${mem.saved} (${mem.deduped} deduped)`] };
18992
+ }
18798
18993
  return saveMission(m);
18799
18994
  }
18800
18995
  async function startMission(target, mode, opts = {}) {
@@ -18816,6 +19011,8 @@ var init_control = __esm(() => {
18816
19011
  init_agents();
18817
19012
  init_planner();
18818
19013
  init_coverage();
19014
+ init_recovery();
19015
+ init_memory2();
18819
19016
  MODE_PROFILES = {
18820
19017
  web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
18821
19018
  api: { agents: ["recon", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.48",
3
+ "version": "1.0.51",
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": {