blitzstrike 1.0.47 → 1.0.49

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,39 @@ 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.49] — 2026-09-13
9
+
10
+ ### Added — Autonomous Security Engine (phase 9: failure recovery)
11
+
12
+ - **Failure Recovery** (`src/mission/recovery.ts`, §17):
13
+ - `diagnoseError()` — categorizes failures (network / permission /
14
+ missing_tool / invalid_args / scope / unknown).
15
+ - `recoveryDecision()` — network → retry (bounded); missing_tool → route to an
16
+ alternative agent; scope / permission / invalid_args → record (no retry).
17
+ - `AGENT_ALTERNATIVES` — fallback specialization per agent type.
18
+ - `recordFailure()` — a failed-approach record with a `future_policy` so the
19
+ autonomous system never blindly repeats the same mistake.
20
+ - Mission Control now routes task failures through the recovery engine and
21
+ persists `failed_approaches` into the mission state.
22
+
23
+ ## [1.0.48] — 2026-09-13
24
+
25
+ ### Added — Autonomous Security Engine (phase 8: coverage-driven scheduler)
26
+
27
+ - **Coverage-driven Scheduler** (`src/mission/coverage.ts`, §13):
28
+ - `computeCoverage()` — 10 dimensions (attack surface, endpoints, technologies,
29
+ source files, sink classes, auth/authorization boundaries, vulnerability
30
+ classes, attack vectors, escalation chains).
31
+ - `coverageGaps()` — dimensions below a threshold.
32
+ - `scheduleCoverageTasks()` — auto-generates tasks for under-covered
33
+ dimensions, skipping already-attempted agents and mode-inappropriate ones
34
+ (e.g. no `@web` for source audits; endpoints/technologies resolve locally).
35
+ - `gapAgent()` — mode-aware dimension→agent mapping.
36
+ - Mission Control's `replan` now runs the coverage scheduler and persists
37
+ per-dimension coverage into `mission.coverage.dimensions`.
38
+ - `@recon` now links discovered endpoints into the Security Graph (so coverage
39
+ reflects real endpoint exploration).
40
+
8
41
  ## [1.0.47] — 2026-09-13
9
42
 
10
43
  ### Added — Autonomous Security Engine (phase 7: adaptive planner)
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
 
@@ -18301,11 +18302,18 @@ var init_agents = __esm(() => {
18301
18302
  };
18302
18303
  }
18303
18304
  const r = enrichScan(ctx.mission.target, 500);
18305
+ let graph = { nodes: [], edges: [] };
18306
+ const tid = `target:${ctx.mission.target}`;
18307
+ for (const ep of r.hits.filter((h) => h.type === "endpoint").slice(0, 50)) {
18308
+ const epId = `endpoint:${ep.file}:${ep.line}`;
18309
+ graph = upsertNode(graph, epId, "endpoint", { file: ep.file, line: ep.line, hook: ep.detail });
18310
+ graph = addEdge2(graph, tid, epId, "exposes");
18311
+ }
18304
18312
  return {
18305
18313
  ok: true,
18306
18314
  output: { files_scanned: r.files_scanned, hits: r.hits.length, sink_signals: r.sink_signals, endpoints: r.hits.filter((h) => h.type === "endpoint").length },
18307
18315
  hypotheses: [],
18308
- graph: emptyGraph()
18316
+ graph
18309
18317
  };
18310
18318
  }
18311
18319
  };
@@ -18582,6 +18590,132 @@ ${m.hypotheses.map((h) => `- [${h.status}] ${h.type} @ ${h.target} (confidence $
18582
18590
  };
18583
18591
  });
18584
18592
 
18593
+ // src/mission/coverage.ts
18594
+ function clamp012(n) {
18595
+ return Math.max(0, Math.min(1, n));
18596
+ }
18597
+ function computeCoverage(mission) {
18598
+ const byType = graphStats(mission.graph).by_type;
18599
+ const hyps = mission.hypotheses;
18600
+ const sinkCategories = new Set(mission.graph.nodes.filter((n) => n.type === "sink").map((n) => String(n.properties.category ?? n.properties.sink ?? "unknown")));
18601
+ const hypTypes = new Set(hyps.map((h) => h.type));
18602
+ return {
18603
+ attack_surface: clamp012(mission.graph.nodes.length / 25),
18604
+ endpoints: clamp012((byType.endpoint ?? 0) / 10),
18605
+ technologies: clamp012(mission.technologies.length / 6),
18606
+ source_files: clamp012((byType.sink ?? 0) / 15),
18607
+ sink_classes: clamp012(sinkCategories.size / 10),
18608
+ auth_boundaries: hyps.length ? clamp012(hyps.filter((h) => /auth/i.test(h.evidence)).length / hyps.length) : 0,
18609
+ authorization_boundaries: hyps.length ? clamp012(hyps.filter((h) => /missing_authorization/i.test(h.type)).length / hyps.length) : 0,
18610
+ vulnerability_classes: clamp012(hypTypes.size / 10),
18611
+ attack_vectors: clamp012(planNextActions(hyps, mission).length / 20),
18612
+ escalation_chains: clamp012((byType.attack_chain ?? 0) / 57)
18613
+ };
18614
+ }
18615
+ function coverageGaps(coverage, threshold = 0.5) {
18616
+ return Object.entries(coverage).filter(([, v]) => v < threshold).map(([k]) => k);
18617
+ }
18618
+ function gapAgent(dimension, isUrl) {
18619
+ if (dimension === "endpoints" && !isUrl)
18620
+ return "recon";
18621
+ if (dimension === "technologies" && !isUrl)
18622
+ return "source";
18623
+ return GAP_AGENT[dimension] ?? "analysis";
18624
+ }
18625
+ function scheduleCoverageTasks(mission, threshold = 0.5) {
18626
+ const coverage = computeCoverage(mission);
18627
+ const gaps = coverageGaps(coverage, threshold);
18628
+ const attempted = new Set(mission.tasks.map((t) => t.agent_type));
18629
+ const isUrl = /^https?:\/\//i.test(mission.target);
18630
+ const tasks = [];
18631
+ for (const gap of gaps) {
18632
+ const agent = gapAgent(gap, isUrl);
18633
+ if (attempted.has(agent))
18634
+ continue;
18635
+ if (!isUrl && agent === "web")
18636
+ continue;
18637
+ tasks.push(createTask({ objective: `close coverage gap: ${gap} (${coverage[gap].toFixed(2)})`, agent_type: agent, priority: 8 }));
18638
+ }
18639
+ return tasks;
18640
+ }
18641
+ var GAP_AGENT;
18642
+ var init_coverage = __esm(() => {
18643
+ init_planner();
18644
+ GAP_AGENT = {
18645
+ attack_surface: "recon",
18646
+ endpoints: "api",
18647
+ technologies: "web",
18648
+ source_files: "source",
18649
+ sink_classes: "source",
18650
+ auth_boundaries: "auth",
18651
+ authorization_boundaries: "auth",
18652
+ vulnerability_classes: "analysis",
18653
+ attack_vectors: "analysis",
18654
+ escalation_chains: "commander"
18655
+ };
18656
+ });
18657
+
18658
+ // src/mission/recovery.ts
18659
+ function diagnoseError(error) {
18660
+ const e = error.toLowerCase();
18661
+ if (/network|connect|timeout|econn|enotfound|unreachable|dns|resolve|refused|abort/i.test(e))
18662
+ return "network";
18663
+ if (/permission|eacces|denied|forbidden|401|403/i.test(e))
18664
+ return "permission";
18665
+ if (/not (installed|found)|no such|command not found|missing tool/i.test(e))
18666
+ return "missing_tool";
18667
+ if (/invalid|argument|usage|required|schema|type/i.test(e))
18668
+ return "invalid_args";
18669
+ if (/scope|authorized|out of scope|unauthorized/i.test(e))
18670
+ return "scope";
18671
+ return "unknown";
18672
+ }
18673
+ function recoveryDecision(task, error) {
18674
+ const category = diagnoseError(error);
18675
+ switch (category) {
18676
+ case "network":
18677
+ return task.attempts < 3 ? { action: "retry", reason: "transient network failure — retry", alternative_agent: null } : { action: "record", reason: "persistent network failure", alternative_agent: null };
18678
+ case "missing_tool":
18679
+ return { action: "alternative", reason: "tool unavailable — try alternative", alternative_agent: AGENT_ALTERNATIVES[task.agent_type]?.[0] ?? null };
18680
+ case "invalid_args":
18681
+ return { action: "record", reason: "invalid arguments — cannot auto-recover", alternative_agent: null };
18682
+ case "scope":
18683
+ return { action: "record", reason: "out of scope — do not retry", alternative_agent: null };
18684
+ case "permission":
18685
+ return { action: "record", reason: "permission denied — cannot auto-recover", alternative_agent: null };
18686
+ default:
18687
+ return task.attempts < 2 ? { action: "retry", reason: "unknown failure — retry once", alternative_agent: null } : { action: "record", reason: "unknown failure — give up", alternative_agent: null };
18688
+ }
18689
+ }
18690
+ function recordFailure(task, error) {
18691
+ const category = diagnoseError(error);
18692
+ return {
18693
+ technique: `${task.agent_type}:${task.objective}`,
18694
+ target: task.objective,
18695
+ category,
18696
+ reason: error,
18697
+ attempts: task.attempts,
18698
+ 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"
18699
+ };
18700
+ }
18701
+ var AGENT_ALTERNATIVES;
18702
+ var init_recovery = __esm(() => {
18703
+ AGENT_ALTERNATIVES = {
18704
+ commander: [],
18705
+ recon: ["api", "web"],
18706
+ web: ["api", "recon"],
18707
+ api: ["recon", "web"],
18708
+ source: ["analysis"],
18709
+ auth: ["analysis", "source"],
18710
+ mobile: ["source"],
18711
+ cloud: ["source"],
18712
+ analysis: ["source"],
18713
+ validator: ["reviewer"],
18714
+ reviewer: ["validator"],
18715
+ reporter: []
18716
+ };
18717
+ });
18718
+
18585
18719
  // src/mission/control.ts
18586
18720
  import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
18587
18721
  function scopeAllowed(mission) {
@@ -18634,7 +18768,12 @@ function replan(mission) {
18634
18768
  tasks = [...tasks, createTask({ objective: "generate the final report", agent_type: "reporter", risk: "low" })];
18635
18769
  }
18636
18770
  }
18637
- return { ...mission, tasks };
18771
+ const coverage = computeCoverage(mission);
18772
+ const coverageTasks = scheduleCoverageTasks(mission);
18773
+ if (coverageTasks.length > 0) {
18774
+ tasks = [...tasks, ...coverageTasks];
18775
+ }
18776
+ return { ...mission, tasks, coverage: { dimensions: { ...mission.coverage.dimensions, ...coverage } } };
18638
18777
  }
18639
18778
  async function executeTask(task, mission, scope) {
18640
18779
  const agent = agentFor(task.agent_type);
@@ -18697,8 +18836,29 @@ async function runMission(mission) {
18697
18836
  m = { ...m, report: r.output };
18698
18837
  }
18699
18838
  } else {
18700
- const failed = setTask(m.tasks, r.task.task_id, { status: r.task.attempts >= 2 ? "failed" : "retry", error: r.error ?? null });
18701
- m = { ...m, tasks: failed, decision_log: [...m.decision_log, { at: new Date().toISOString(), agent: r.task.agent_type, decision }] };
18839
+ const rec = recoveryDecision(r.task, r.error ?? "unknown error");
18840
+ const stamp = { at: new Date().toISOString(), agent: r.task.agent_type, decision };
18841
+ if (rec.action === "retry") {
18842
+ const retried = setTask(m.tasks, r.task.task_id, { status: "retry", error: r.error ?? null });
18843
+ m = { ...m, tasks: retried, decision_log: [...m.decision_log, stamp] };
18844
+ } else if (rec.action === "alternative" && rec.alternative_agent) {
18845
+ const failed = setTask(m.tasks, r.task.task_id, { status: "failed", error: r.error ?? null });
18846
+ const alt = createTask({ objective: `[alternative] ${r.task.objective}`, agent_type: rec.alternative_agent, risk: r.task.risk, priority: r.task.priority - 1 });
18847
+ m = {
18848
+ ...m,
18849
+ tasks: [...failed, alt],
18850
+ decision_log: [...m.decision_log, stamp],
18851
+ failed_approaches: [...m.failed_approaches, recordFailure(r.task, r.error ?? "")]
18852
+ };
18853
+ } else {
18854
+ const failed = setTask(m.tasks, r.task.task_id, { status: "failed", error: r.error ?? null });
18855
+ m = {
18856
+ ...m,
18857
+ tasks: failed,
18858
+ decision_log: [...m.decision_log, stamp],
18859
+ failed_approaches: [...m.failed_approaches, recordFailure(r.task, r.error ?? "")]
18860
+ };
18861
+ }
18702
18862
  }
18703
18863
  }
18704
18864
  m.budget.used_tasks += batch.length;
@@ -18738,6 +18898,8 @@ var init_control = __esm(() => {
18738
18898
  init_state();
18739
18899
  init_agents();
18740
18900
  init_planner();
18901
+ init_coverage();
18902
+ init_recovery();
18741
18903
  MODE_PROFILES = {
18742
18904
  web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
18743
18905
  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.47",
3
+ "version": "1.0.49",
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": {