blitzstrike 1.0.51 → 1.0.53

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,36 @@ 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.53] — 2026-09-13
9
+
10
+ ### Added — Autonomous Security Engine (phase 13: model router)
11
+
12
+ - **Model Router** (`src/mission/router.ts`, §21):
13
+ - Pure routing layer — never calls a model. Deterministic by default.
14
+ - `routeTask()` — maps an agent type to a model tier (`large`/`medium`/`small`)
15
+ only when a provider is configured; otherwise routes to `none`.
16
+ - `AGENT_TIER` — §21 mapping (planning/source/review → large; attack execution
17
+ → medium; verification → small).
18
+ - `loadRouterConfig()` — reads `BLITZSTRIKE_MODEL_PROVIDER` / `_LARGE` /
19
+ `_MEDIUM` / `_SMALL` env vars.
20
+ - `routingPlan()` — full plan (all `none` when deterministic).
21
+ - Mission Control records the routing decision (tier) per task and the overall
22
+ routing plan in the mission `decision_log`.
23
+
24
+ ## [1.0.52] — 2026-09-13
25
+
26
+ ### Added — Autonomous Security Engine (phase 12: browser automation agent)
27
+
28
+ - **Browser Automation Agent** (`src/browser-agent.ts`, §16):
29
+ - Session-based (one persistent page) so multi-step flows work.
30
+ - Ops: `open`/`navigate`/`click`/`type`/`evaluate`/`screenshot`/`back`/`forward`/
31
+ `get_cookie`/`set_cookie`, plus `detectAuthForm()` and `browserDiagnostics()`.
32
+ - Graceful degradation — every op reports `unavailable` when `playwright-core`
33
+ (optional peer) is missing, never throws.
34
+ - New `@browser` mission agent (URL-scope-gated) — detects login forms and JS
35
+ runtime errors and emits auth-flow hypotheses.
36
+ - New MCP tool `browser_agent` (session ops + detect_auth_form + diagnostics + close).
37
+
8
38
  ## [1.0.51] — 2026-09-13
9
39
 
10
40
  ### Added — Autonomous Security Engine (phase 11: adversarial reviewer)
package/dist/index.js CHANGED
@@ -17722,6 +17722,219 @@ var init_universal_taint = __esm(() => {
17722
17722
  extensionMap = new Map;
17723
17723
  });
17724
17724
 
17725
+ // src/browser-agent.ts
17726
+ async function ensureChromium2() {
17727
+ if (chromiumCache2)
17728
+ return chromiumCache2;
17729
+ try {
17730
+ chromiumCache2 = await import("playwright-core");
17731
+ return chromiumCache2;
17732
+ } catch {
17733
+ return null;
17734
+ }
17735
+ }
17736
+ async function ensureSession() {
17737
+ if (page)
17738
+ return { available: true };
17739
+ const chr = await ensureChromium2();
17740
+ if (!chr)
17741
+ return { available: false, reason: "playwright-core not installed (optional peer dependency)" };
17742
+ try {
17743
+ browser = await chr.chromium.launch({ headless: true });
17744
+ page = await browser.newPage();
17745
+ page.on("console", (m) => {
17746
+ const msg = m;
17747
+ if (msg.type() === "error")
17748
+ consoleErrors.push(msg.text());
17749
+ });
17750
+ page.on("pageerror", (e) => pageErrors.push(String(e)));
17751
+ return { available: true };
17752
+ } catch (e) {
17753
+ return { available: false, reason: e instanceof Error ? e.message : String(e) };
17754
+ }
17755
+ }
17756
+ function unavailable2(op, reason) {
17757
+ return { op, ok: false, unavailable: true, detail: reason };
17758
+ }
17759
+ function fail(op, e) {
17760
+ return { op, ok: false, unavailable: false, detail: e instanceof Error ? e.message : String(e) };
17761
+ }
17762
+ async function browserOpen(url) {
17763
+ const s = await ensureSession();
17764
+ if (!s.available || !page)
17765
+ return unavailable2("open", s.reason ?? "unavailable");
17766
+ try {
17767
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 20000 });
17768
+ return { op: "open", ok: true, unavailable: false, detail: `opened ${redactSecrets(page.url())}` };
17769
+ } catch (e) {
17770
+ return fail("open", e);
17771
+ }
17772
+ }
17773
+ async function browserNavigate(url) {
17774
+ return browserOpen(url);
17775
+ }
17776
+ async function browserClick(selector) {
17777
+ const s = await ensureSession();
17778
+ if (!s.available || !page)
17779
+ return unavailable2("click", s.reason ?? "unavailable");
17780
+ try {
17781
+ await page.click(selector);
17782
+ return { op: "click", ok: true, unavailable: false, detail: `clicked ${selector}` };
17783
+ } catch (e) {
17784
+ return fail("click", e);
17785
+ }
17786
+ }
17787
+ async function browserType(selector, text) {
17788
+ const s = await ensureSession();
17789
+ if (!s.available || !page)
17790
+ return unavailable2("type", s.reason ?? "unavailable");
17791
+ try {
17792
+ await page.fill(selector, text);
17793
+ return { op: "type", ok: true, unavailable: false, detail: `typed into ${selector}` };
17794
+ } catch (e) {
17795
+ return fail("type", e);
17796
+ }
17797
+ }
17798
+ async function browserEvaluate(js) {
17799
+ const s = await ensureSession();
17800
+ if (!s.available || !page)
17801
+ return unavailable2("evaluate", s.reason ?? "unavailable");
17802
+ try {
17803
+ const value = await page.evaluate((code) => {
17804
+ const fn = new Function(`"use strict"; return (${code})`)();
17805
+ return typeof fn === "function" ? fn() : fn;
17806
+ }, js);
17807
+ return { op: "evaluate", ok: true, unavailable: false, detail: "evaluated", value };
17808
+ } catch (e) {
17809
+ return fail("evaluate", e);
17810
+ }
17811
+ }
17812
+ async function browserScreenshot(path) {
17813
+ const s = await ensureSession();
17814
+ if (!s.available || !page)
17815
+ return unavailable2("screenshot", s.reason ?? "unavailable");
17816
+ try {
17817
+ const buf = await page.screenshot(path ? { path } : {});
17818
+ return { op: "screenshot", ok: true, unavailable: false, detail: path ? `saved ${path}` : "captured (in-memory)", value: path ? undefined : buf.byteLength };
17819
+ } catch (e) {
17820
+ return fail("screenshot", e);
17821
+ }
17822
+ }
17823
+ async function browserBack() {
17824
+ const s = await ensureSession();
17825
+ if (!s.available || !page)
17826
+ return unavailable2("back", s.reason ?? "unavailable");
17827
+ try {
17828
+ await page.goBack();
17829
+ return { op: "back", ok: true, unavailable: false, detail: `back to ${redactSecrets(page.url())}` };
17830
+ } catch (e) {
17831
+ return fail("back", e);
17832
+ }
17833
+ }
17834
+ async function browserForward() {
17835
+ const s = await ensureSession();
17836
+ if (!s.available || !page)
17837
+ return unavailable2("forward", s.reason ?? "unavailable");
17838
+ try {
17839
+ await page.goForward();
17840
+ return { op: "forward", ok: true, unavailable: false, detail: `forward to ${redactSecrets(page.url())}` };
17841
+ } catch (e) {
17842
+ return fail("forward", e);
17843
+ }
17844
+ }
17845
+ async function browserGetCookie(name) {
17846
+ const s = await ensureSession();
17847
+ if (!s.available || !page)
17848
+ return unavailable2("get_cookie", s.reason ?? "unavailable");
17849
+ try {
17850
+ const cookies = await page.context().cookies();
17851
+ const hit = cookies.filter((c) => c.name === name);
17852
+ return { op: "get_cookie", ok: true, unavailable: false, detail: hit.length ? "found" : "not found", value: hit };
17853
+ } catch (e) {
17854
+ return fail("get_cookie", e);
17855
+ }
17856
+ }
17857
+ async function browserSetCookie(name, value) {
17858
+ const s = await ensureSession();
17859
+ if (!s.available || !page)
17860
+ return unavailable2("set_cookie", s.reason ?? "unavailable");
17861
+ try {
17862
+ const url = page.url();
17863
+ await page.context().addCookies([{ name, value, url }]);
17864
+ return { op: "set_cookie", ok: true, unavailable: false, detail: `set ${name}` };
17865
+ } catch (e) {
17866
+ return fail("set_cookie", e);
17867
+ }
17868
+ }
17869
+ async function browserClose() {
17870
+ if (browser) {
17871
+ await browser.close().catch(() => {
17872
+ return;
17873
+ });
17874
+ browser = null;
17875
+ page = null;
17876
+ }
17877
+ }
17878
+ async function detectAuthForm() {
17879
+ const s = await ensureSession();
17880
+ if (!s.available || !page)
17881
+ return unavailable2("evaluate", s.reason ?? "unavailable");
17882
+ try {
17883
+ const value = await page.evaluate(() => {
17884
+ const forms = Array.from(document.querySelectorAll("form"));
17885
+ const loginForm = forms.find((f) => /login|signin|auth/i.test(f.action || "") || f.querySelector('input[type="password"]'));
17886
+ if (!loginForm)
17887
+ return { found: false };
17888
+ const userField = loginForm.querySelector('input[type="email"], input[type="text"], input[name*="user"], input[name*="email"], input[name*="login"]');
17889
+ const passField = loginForm.querySelector('input[type="password"]');
17890
+ return {
17891
+ found: true,
17892
+ action: loginForm.action || "(self)",
17893
+ has_user: Boolean(userField),
17894
+ has_pass: Boolean(passField),
17895
+ user_selector: userField ? `input[name="${userField.name || ""}"], input[type="${userField.type}"]` : null,
17896
+ pass_selector: passField ? 'input[type="password"]' : null
17897
+ };
17898
+ });
17899
+ return { op: "evaluate", ok: true, unavailable: false, detail: value.found ? "login form detected" : "no login form", value };
17900
+ } catch (e) {
17901
+ return fail("evaluate", e);
17902
+ }
17903
+ }
17904
+ async function browserAgent(op, args = []) {
17905
+ switch (op) {
17906
+ case "open":
17907
+ return browserOpen(args[0] ?? "");
17908
+ case "navigate":
17909
+ return browserNavigate(args[0] ?? "");
17910
+ case "click":
17911
+ return browserClick(args[0] ?? "");
17912
+ case "type":
17913
+ return browserType(args[0] ?? "", args[1] ?? "");
17914
+ case "evaluate":
17915
+ return browserEvaluate(args[0] ?? "");
17916
+ case "screenshot":
17917
+ return browserScreenshot(args[0] || undefined);
17918
+ case "back":
17919
+ return browserBack();
17920
+ case "forward":
17921
+ return browserForward();
17922
+ case "get_cookie":
17923
+ return browserGetCookie(args[0] ?? "");
17924
+ case "set_cookie":
17925
+ return browserSetCookie(args[0] ?? "", args[1] ?? "");
17926
+ }
17927
+ }
17928
+ function browserDiagnostics() {
17929
+ return { console_errors: [...consoleErrors], page_errors: [...pageErrors] };
17930
+ }
17931
+ var chromiumCache2 = null, browser = null, page = null, consoleErrors, pageErrors;
17932
+ var init_browser_agent = __esm(() => {
17933
+ init_evidence();
17934
+ consoleErrors = [];
17935
+ pageErrors = [];
17936
+ });
17937
+
17725
17938
  // src/sync.ts
17726
17939
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync16, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
17727
17940
  import { join as join12 } from "node:path";
@@ -18359,7 +18572,7 @@ function sourceFiles(root, max = 500) {
18359
18572
  function agentFor(type) {
18360
18573
  return AGENTS[type];
18361
18574
  }
18362
- var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
18575
+ var reconAgent, webAgent, sourceAgent, analysisAgent, authAgent, validatorAgent, reviewerAgent, browserAgent2, reporterAgent, mobileAgent, cloudAgent, commanderAgent, apiAgent, AGENTS;
18363
18576
  var init_agents = __esm(() => {
18364
18577
  init_planner();
18365
18578
  init_reviewer();
@@ -18556,6 +18769,52 @@ var init_agents = __esm(() => {
18556
18769
  return { ok: true, output: { outcomes: results.map((r) => ({ id: r.hypothesis_id, outcome: r.outcome })) }, hypotheses, graph: emptyGraph() };
18557
18770
  }
18558
18771
  };
18772
+ browserAgent2 = {
18773
+ type: "browser",
18774
+ responsibility: "Drive a real browser to detect DOM issues and auth flows.",
18775
+ tools: ["browser_open", "browser_click", "browser_type", "browser_evaluate", "browser_screenshot", "browser_navigate", "browser_back", "browser_forward", "browser_get_cookie", "browser_set_cookie"],
18776
+ async execute(_task, ctx) {
18777
+ if (!/^https?:\/\//i.test(ctx.mission.target) || !ctx.scopeAllowed) {
18778
+ return { ok: false, output: {}, hypotheses: [], graph: emptyGraph(), error: "browser agent requires an authorized URL target" };
18779
+ }
18780
+ await Promise.resolve().then(() => init_browser_agent());
18781
+ const opened = await browserOpen(ctx.mission.target);
18782
+ if (opened.unavailable) {
18783
+ return { ok: false, output: {}, hypotheses: [], graph: emptyGraph(), error: opened.detail };
18784
+ }
18785
+ const auth = await detectAuthForm();
18786
+ const diag = browserDiagnostics();
18787
+ await browserClose();
18788
+ const hypotheses = [];
18789
+ const authInfo = auth.value ?? {};
18790
+ if (authInfo.found) {
18791
+ hypotheses.push(makeHypothesis({
18792
+ type: "auth_flow_detected",
18793
+ target: ctx.mission.target,
18794
+ evidence: `login form detected (user_field=${authInfo.has_user}, pass_field=${authInfo.has_pass})`,
18795
+ confidence: 0.6,
18796
+ required_validation: "login flow",
18797
+ potential_impact: "auth boundary"
18798
+ }));
18799
+ }
18800
+ if (diag.console_errors.length > 0 || diag.page_errors.length > 0) {
18801
+ hypotheses.push(makeHypothesis({
18802
+ type: "js_runtime_error",
18803
+ target: ctx.mission.target,
18804
+ evidence: `console_errors=${diag.console_errors.length} page_errors=${diag.page_errors.length}`,
18805
+ confidence: 0.4,
18806
+ required_validation: "review",
18807
+ potential_impact: "unspecified"
18808
+ }));
18809
+ }
18810
+ return {
18811
+ ok: true,
18812
+ output: { opened: opened.ok, auth_form: authInfo.found ?? false, console_errors: diag.console_errors.length, page_errors: diag.page_errors.length },
18813
+ hypotheses,
18814
+ graph: emptyGraph()
18815
+ };
18816
+ }
18817
+ };
18559
18818
  reporterAgent = {
18560
18819
  type: "reporter",
18561
18820
  responsibility: "Produce the final structured mission report.",
@@ -18666,7 +18925,8 @@ ${m.hypotheses.map((h) => `- [${h.status}] ${h.type} @ ${h.target} (confidence $
18666
18925
  analysis: analysisAgent,
18667
18926
  validator: validatorAgent,
18668
18927
  reviewer: reviewerAgent,
18669
- reporter: reporterAgent
18928
+ reporter: reporterAgent,
18929
+ browser: browserAgent2
18670
18930
  };
18671
18931
  });
18672
18932
 
@@ -18792,7 +19052,8 @@ var init_recovery = __esm(() => {
18792
19052
  analysis: ["source"],
18793
19053
  validator: ["reviewer"],
18794
19054
  reviewer: ["validator"],
18795
- reporter: []
19055
+ reporter: [],
19056
+ browser: ["web"]
18796
19057
  };
18797
19058
  });
18798
19059
 
@@ -18824,6 +19085,55 @@ var init_memory2 = __esm(() => {
18824
19085
  init_memory();
18825
19086
  });
18826
19087
 
19088
+ // src/mission/router.ts
19089
+ function isModelConfigured(config) {
19090
+ return Boolean(config?.provider);
19091
+ }
19092
+ function routeTask(agent, config) {
19093
+ if (!isModelConfigured(config)) {
19094
+ return { agent, tier: "none", model: null, deterministic: true };
19095
+ }
19096
+ const tier = AGENT_TIER[agent] ?? "medium";
19097
+ const key = `model_${tier}`;
19098
+ const model = config?.[key] ?? null;
19099
+ return { agent, tier, model, deterministic: model === null };
19100
+ }
19101
+ function loadRouterConfig(env = process.env) {
19102
+ return {
19103
+ provider: env.BLITZSTRIKE_MODEL_PROVIDER || undefined,
19104
+ model_large: env.BLITZSTRIKE_MODEL_LARGE || undefined,
19105
+ model_medium: env.BLITZSTRIKE_MODEL_MEDIUM || undefined,
19106
+ model_small: env.BLITZSTRIKE_MODEL_SMALL || undefined
19107
+ };
19108
+ }
19109
+ function routingPlan(config) {
19110
+ if (!isModelConfigured(config)) {
19111
+ const out = {};
19112
+ for (const k of Object.keys(AGENT_TIER))
19113
+ out[k] = "none";
19114
+ return out;
19115
+ }
19116
+ return { ...AGENT_TIER };
19117
+ }
19118
+ var AGENT_TIER;
19119
+ var init_router = __esm(() => {
19120
+ AGENT_TIER = {
19121
+ commander: "large",
19122
+ source: "large",
19123
+ analysis: "large",
19124
+ reviewer: "large",
19125
+ recon: "medium",
19126
+ web: "medium",
19127
+ api: "medium",
19128
+ auth: "medium",
19129
+ mobile: "medium",
19130
+ cloud: "medium",
19131
+ reporter: "medium",
19132
+ validator: "small",
19133
+ browser: "small"
19134
+ };
19135
+ });
19136
+
18827
19137
  // src/mission/control.ts
18828
19138
  import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
18829
19139
  function scopeAllowed(mission) {
@@ -18903,6 +19213,11 @@ async function runMission(mission) {
18903
19213
  let m = { ...mission, status: "running" };
18904
19214
  m.tasks = buildInitialTasks(m);
18905
19215
  m.graph = loadEscalationGraph(m.graph);
19216
+ const routerCfg = loadRouterConfig();
19217
+ m.decision_log = [
19218
+ ...m.decision_log,
19219
+ { at: new Date().toISOString(), agent: "router", decision: isModelConfigured(routerCfg) ? `model routing active: ${JSON.stringify(routingPlan(routerCfg))}` : "deterministic (no model provider configured)" }
19220
+ ];
18906
19221
  m = saveMission(m);
18907
19222
  const scope = m.scope.join(",");
18908
19223
  let iterations = 0;
@@ -18932,20 +19247,22 @@ async function runMission(mission) {
18932
19247
  const results = await Promise.all(batch.map((t) => executeTask(t, m, scope)));
18933
19248
  for (const r of results) {
18934
19249
  const decision = r.ok ? `${r.task.agent_type} completed (${Object.keys(r.output).length} output fields, ${r.hypotheses.length} hypotheses)` : `${r.task.agent_type} failed: ${r.error ?? "unknown"}`;
19250
+ const route = routeTask(r.task.agent_type, routerCfg);
19251
+ const logEntry = (entryDecision) => ({ at: new Date().toISOString(), agent: r.task.agent_type, decision: entryDecision, tier: route.tier });
18935
19252
  if (r.ok) {
18936
19253
  m = {
18937
19254
  ...m,
18938
19255
  tasks: setTask(m.tasks, r.task.task_id, { status: "success", result: r.output }),
18939
19256
  hypotheses: mergeHypotheses(m.hypotheses, r.hypotheses),
18940
19257
  graph: mergeGraphs(m.graph, r.graph),
18941
- decision_log: [...m.decision_log, { at: new Date().toISOString(), agent: r.task.agent_type, decision }]
19258
+ decision_log: [...m.decision_log, logEntry(decision)]
18942
19259
  };
18943
19260
  if (r.task.agent_type === "reporter") {
18944
19261
  m = { ...m, report: r.output };
18945
19262
  }
18946
19263
  } else {
18947
19264
  const rec = recoveryDecision(r.task, r.error ?? "unknown error");
18948
- const stamp = { at: new Date().toISOString(), agent: r.task.agent_type, decision };
19265
+ const stamp = logEntry(decision);
18949
19266
  if (rec.action === "retry") {
18950
19267
  const retried = setTask(m.tasks, r.task.task_id, { status: "retry", error: r.error ?? null });
18951
19268
  m = { ...m, tasks: retried, decision_log: [...m.decision_log, stamp] };
@@ -19013,6 +19330,7 @@ var init_control = __esm(() => {
19013
19330
  init_coverage();
19014
19331
  init_recovery();
19015
19332
  init_memory2();
19333
+ init_router();
19016
19334
  MODE_PROFILES = {
19017
19335
  web: { agents: ["recon", "web", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
19018
19336
  api: { agents: ["recon", "api", "auth", "validator", "reviewer", "reporter"], verify: true, risk: "medium" },
@@ -53121,6 +53439,7 @@ async function browserValidate(check, target, payload) {
53121
53439
  }
53122
53440
 
53123
53441
  // src/server.ts
53442
+ init_browser_agent();
53124
53443
  init_universal_taint();
53125
53444
 
53126
53445
  // src/guidance.ts
@@ -54684,6 +55003,28 @@ function createServer() {
54684
55003
  const result = await browserValidate(check, target, payload);
54685
55004
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
54686
55005
  });
55006
+ server.registerTool("browser_agent", {
55007
+ title: "Browser automation agent (session)",
55008
+ description: "PHASE 12: drive a real headless-Chromium session. Ops: open, navigate, click, type, evaluate, screenshot, back, forward, get_cookie, set_cookie, plus detect_auth_form, diagnostics, close. Enables multi-step flows (login/MFA) and DOM issue detection. Requires playwright-core (optional peer dependency).",
55009
+ inputSchema: {
55010
+ op: _enum(["open", "navigate", "click", "type", "evaluate", "screenshot", "back", "forward", "get_cookie", "set_cookie", "detect_auth_form", "diagnostics", "close"]),
55011
+ args: array(string2()).optional().describe("Positional args (url, selector, text, js, cookie name/value, screenshot path)")
55012
+ }
55013
+ }, async ({ op, args }) => {
55014
+ if (op === "detect_auth_form") {
55015
+ const r = await detectAuthForm();
55016
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
55017
+ }
55018
+ if (op === "diagnostics") {
55019
+ return { content: [{ type: "text", text: JSON.stringify(browserDiagnostics()) }] };
55020
+ }
55021
+ if (op === "close") {
55022
+ await browserClose();
55023
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, detail: "session closed" }) }] };
55024
+ }
55025
+ const r = await browserAgent(op, args ?? []);
55026
+ return { content: [{ type: "text", text: JSON.stringify(r) }] };
55027
+ });
54687
55028
  server.registerTool("taint_tree", {
54688
55029
  title: "Whole-tree taint scan",
54689
55030
  description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
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": {