blitzstrike 1.0.39 → 1.0.43

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 (34) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/bench/corpus/java-cmd-processbuilder.md +8 -0
  3. package/bench/corpus/java-deser-readunshared.md +8 -0
  4. package/bench/corpus/java-sqli-execute.md +8 -0
  5. package/bench/corpus/java-ssrf-httpurl.md +8 -0
  6. package/bench/corpus/java-xss-escape.md +8 -0
  7. package/bench/corpus/java-xss-println.md +8 -0
  8. package/bench/corpus/js-cmd-execsync.md +8 -0
  9. package/bench/corpus/js-filewrite.md +8 -0
  10. package/bench/corpus/js-pathtraversal-readsync.md +8 -0
  11. package/bench/corpus/js-redirect-encodeuri.md +8 -0
  12. package/bench/corpus/js-ssrf-axios.md +8 -0
  13. package/bench/corpus/js-ssrf-fetch.md +8 -0
  14. package/bench/corpus/js-xss-documentwrite.md +8 -0
  15. package/bench/corpus/js-xss-innerhtml.md +8 -0
  16. package/bench/corpus/php-cmd-absint.md +9 -0
  17. package/bench/corpus/php-cmd-popen.md +9 -0
  18. package/bench/corpus/php-deser-json.md +9 -0
  19. package/bench/corpus/php-include-once.md +9 -0
  20. package/bench/corpus/php-include-sanitized.md +9 -0
  21. package/bench/corpus/php-sqli-filterinput.md +9 -0
  22. package/bench/corpus/php-sqli-getresults.md +9 -0
  23. package/bench/corpus/php-ssrf-wppost.md +9 -0
  24. package/bench/corpus/php-xss-escurl.md +9 -0
  25. package/bench/corpus/php-xss-print.md +9 -0
  26. package/bench/corpus/php-xss-sanitizetext.md +9 -0
  27. package/bench/corpus/py-cmd-popen.md +9 -0
  28. package/bench/corpus/py-cmd-shlex.md +9 -0
  29. package/bench/corpus/py-deser-yaml.md +9 -0
  30. package/bench/corpus/py-sqli-executescript.md +9 -0
  31. package/bench/corpus/py-ssrf-urllib.md +9 -0
  32. package/bench/corpus/py-xss-escape.md +8 -0
  33. package/dist/index.js +469 -59
  34. package/package.json +12 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,73 @@ 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.43] — 2026-09-13
9
+
10
+ ### Added
11
+
12
+ - **Phase 8 — Production Hardening**:
13
+ - **Caching** (`src/cache.ts`, §46) — versioned result cache (`sha256(source) + engine_version`);
14
+ wired into `analyzeDataFlow2` so unchanged files skip re-parsing.
15
+ - **SARIF export** (`src/sarif.ts`, §32) — `toSarif()` emits a SARIF 2.1.0 document
16
+ (GitHub Code Scanning / GitLab / Azure DevOps).
17
+ - **Release validation** (`src/release.ts`, §49) — `validateRelease()` checks
18
+ package.json vs CHANGELOG (hard) + git tag / npm registry / lockfile (soft).
19
+ - **Dependency security** (`src/dependency.ts`, §50) — `checkDependencies()`
20
+ inspects install-time scripts and git/unusual dependency sources.
21
+ - **Incremental analysis** (`src/incremental.ts`, §47) — `analyzeChangedFiles()`
22
+ re-analyzes only the files a commit touched (git diff → changed files → engine).
23
+ - **CI/CD** (`.github/workflows/ci.yml`, §48) — typecheck, tests, build, npm pack,
24
+ release-consistency, and dependency-surface gates.
25
+ - `blitzstrike doctor` now reports release-metadata and dependency-surface health.
26
+
27
+ ## [1.0.42] — 2026-09-13
28
+
29
+ ### Added
30
+
31
+ - **Phase 7 — MCP Orchestration** (`src/orchestration.ts`, wired into `run_engagement`):
32
+ - A data-driven **state machine** (10 states: scope_check → plan → recon →
33
+ analyze → hypothesis → select_chain → validate → evidence → finding → report).
34
+ - A deterministic **planner** (`buildPlan`) that emits an ordered plan with
35
+ per-step tool, risk level, evidence requirements, and done/pending/blocked status.
36
+ - A **risk policy** (`actionPermitted`) that gates high-risk actions
37
+ (strike_verify, confirm_finding) behind scope.
38
+ - **§34 agent guidance** (`computeGuidance`) — every result now exposes
39
+ current_state / confidence / blocking_reason / required_evidence /
40
+ recommended_next_action / recommended_tool.
41
+ - `run_engagement` output now includes `state`, `plan`, `guidance`, and
42
+ `risk_policy` (no new MCP tools added — the surface was deepened, not widened).
43
+
44
+ ## [1.0.41] — 2026-09-13
45
+
46
+ ### Added
47
+
48
+ - **Phase 6 — Browser Validation** (`src/browser.ts` + MCP tool `browser_validate`):
49
+ - Playwright-based validation of static-analysis hypotheses (not a scanner —
50
+ gated, "use only when necessary").
51
+ - Checks: `dom_xss` (marker-payload execution), `open_redirect` (unexpected
52
+ redirect), `auth_bypass` (missing auth enforcement), `csrf` (tokenless
53
+ state-changing form).
54
+ - Each check returns structured evidence (verdict + final_url + dom/console/
55
+ page-error signals) suitable for attaching to a finding.
56
+ - `playwright-core` is an **optional peer dependency**; `browser_validate`
57
+ degrades to a clear `unavailable` result when it is not installed.
58
+ - Hermetic browser test suite (`bun run test:browser`, 8 checks) against a
59
+ local vulnerable server.
60
+
61
+ ## [1.0.40] — 2026-09-13
62
+
63
+ ### Changed (review + spec alignment)
64
+
65
+ - **Coverage Matrix 2.0 (§57)**: `coverageMatrix()` now also tracks the framework
66
+ dimension — per-framework sinks/sources/sanitizers/auth counts + CWE set.
67
+ - **Benchmark 100+ (§65)**: corpus grown 70 → **101 fixtures** (70 vulnerable +
68
+ 31 patched), still scoring detection 1.0 / FP 0 / FN 0 / precision 1.0.
69
+
70
+ ### Fixed
71
+
72
+ - JS adapter now detects `el.innerHTML = <tainted>` (property assignment) as a
73
+ DOM XSS sink — previously only `innerHTML` inside call expressions was caught.
74
+
8
75
  ## [1.0.39] — 2026-09-13
9
76
 
10
77
  ### Added
@@ -102,6 +169,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
102
169
  now use `\b` word boundaries, so `outer_exec(...)` is no longer misclassified
103
170
  as command execution.
104
171
 
172
+ ## [1.0.34] — 2026-09-13
173
+
174
+ ### Fixed (Phase 1 review)
175
+
176
+ - `hasSecrets` rewritten as `redactSecrets(text) !== text` — deterministic
177
+ (fixed the `g`-flag `.test()` state bug) and no false positive on already-redacted
178
+ content (`password=[REDACTED]` is no longer flagged).
179
+ - Wired `finding_updated` audit event into `transition()` / `rejectFinding()`.
180
+
105
181
  ## [1.0.33] — 2026-09-13
106
182
 
107
183
  ### Added
@@ -128,6 +204,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
128
204
  - Shared `src/version.ts` — single source of truth for the package version
129
205
  (removed the duplicate VERSION readers in `server.ts`).
130
206
 
207
+ ## [1.0.32] — 2026-09-13
208
+
209
+ ### Added
210
+
211
+ - Self-update: `blitzstrike update` CLI command + `check_update` MCP tool
212
+ (`src/update.ts` with semver-aware version comparison).
213
+
214
+ ## [1.0.31] — 2026-09-13
215
+
216
+ ### Changed
217
+
218
+ - npm publish pipeline debug (version bump only, no functional change).
219
+
220
+ ## [1.0.30] — 2026-09-13
221
+
222
+ ### Changed
223
+
224
+ - Removed source attribution from the attack-vector taxonomy data (first-party
225
+ content; `_comment` and notices stripped).
226
+
227
+ ## [1.0.29] — 2026-09-13
228
+
229
+ ### Added
230
+
231
+ - Attack-vector taxonomy (`intelligence/attack_vectors.json`, 34 categories /
232
+ 588 vectors) + `list_attack_vectors` + `attack_vectors` MCP tools.
233
+
234
+ ## [1.0.28] — 2026-09-13
235
+
236
+ ### Fixed
237
+
238
+ - Benchmark corpus writes to the OS temp directory (hermetic; no writes inside
239
+ the read-only package).
240
+
241
+ ## [1.0.27] — 2026-09-13
242
+
243
+ ### Added
244
+
245
+ - Documentation milestone: CHANGELOG / ROADMAP / README + docs metrics.
246
+
131
247
  ## [1.0.26] — 2026-09-13
132
248
 
133
249
  The first hardening milestone: the whole roadmap is implemented, measured, and
@@ -0,0 +1,8 @@
1
+ - id: java-cmd-processbuilder
2
+ - language: java
3
+ - vulnerable: true
4
+ - sink_type: command_execution
5
+ ```java
6
+ String cmd = request.getParameter("cmd");
7
+ new ProcessBuilder(cmd).start();
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: java-deser-readunshared
2
+ - language: java
3
+ - vulnerable: true
4
+ - sink_type: deserialization
5
+ ```java
6
+ ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
7
+ ois.readUnshared();
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: java-sqli-execute
2
+ - language: java
3
+ - vulnerable: true
4
+ - sink_type: sql_execution
5
+ ```java
6
+ String id = request.getParameter("id");
7
+ st.execute("DELETE FROM t WHERE id=" + id);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: java-ssrf-httpurl
2
+ - language: java
3
+ - vulnerable: true
4
+ - sink_type: http_request
5
+ ```java
6
+ String url = request.getParameter("url");
7
+ HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: java-xss-escape
2
+ - language: java
3
+ - vulnerable: false
4
+ - sink_type: html_render
5
+ ```java
6
+ String x = request.getParameter("x");
7
+ response.getWriter().write(HtmlUtils.htmlEscape(x));
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: java-xss-println
2
+ - language: java
3
+ - vulnerable: true
4
+ - sink_type: html_render
5
+ ```java
6
+ String x = request.getParameter("x");
7
+ response.getWriter().println(x);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-cmd-execsync
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: command_execution
5
+ ```javascript
6
+ const c = req.query.cmd;
7
+ execSync("ls " + c);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-filewrite
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: file_operations
5
+ ```javascript
6
+ const d = req.body.data;
7
+ fs.writeFile("/tmp/x", d);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-pathtraversal-readsync
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: path_traversal
5
+ ```javascript
6
+ const f = req.query.file;
7
+ fs.readFileSync("/data/" + f);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-redirect-encodeuri
2
+ - language: javascript
3
+ - vulnerable: false
4
+ - sink_type: redirect
5
+ ```javascript
6
+ const u = req.query.u;
7
+ res.redirect(encodeURIComponent(u));
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-ssrf-axios
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: http_request
5
+ ```javascript
6
+ const u = req.query.url;
7
+ axios.get(u);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-ssrf-fetch
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: http_request
5
+ ```javascript
6
+ const u = req.query.url;
7
+ fetch(u);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-xss-documentwrite
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: html_render
5
+ ```javascript
6
+ const x = req.query.x;
7
+ document.write(x);
8
+ ```
@@ -0,0 +1,8 @@
1
+ - id: js-xss-innerhtml
2
+ - language: javascript
3
+ - vulnerable: true
4
+ - sink_type: html_render
5
+ ```javascript
6
+ const x = req.query.x;
7
+ el.innerHTML = x;
8
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-cmd-absint
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: command_execution
5
+ ```php
6
+ <?php
7
+ $n = $_GET["n"];
8
+ system("ping -c " . absint($n));
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-cmd-popen
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: command_execution
5
+ ```php
6
+ <?php
7
+ $cmd = $_GET["cmd"];
8
+ popen($cmd, "r");
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-deser-json
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: deserialization
5
+ ```php
6
+ <?php
7
+ $data = $_COOKIE["data"];
8
+ json_decode($data);
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-include-once
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: file_inclusion
5
+ ```php
6
+ <?php
7
+ $page = $_GET["page"];
8
+ include_once($page . ".php");
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-include-sanitized
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: file_inclusion
5
+ ```php
6
+ <?php
7
+ $f = $_GET["f"];
8
+ include(sanitize_file_name($f));
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-sqli-filterinput
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ $id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
8
+ $wpdb->query("SELECT * FROM t WHERE id=$id");
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-sqli-getresults
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ $id = $_GET["id"];
8
+ $wpdb->get_results("SELECT * FROM t WHERE id=$id");
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-ssrf-wppost
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: http_request
5
+ ```php
6
+ <?php
7
+ $url = $_GET["url"];
8
+ wp_remote_post($url, []);
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-xss-escurl
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: redirect
5
+ ```php
6
+ <?php
7
+ $url = $_GET["url"];
8
+ wp_safe_redirect(esc_url($url));
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-xss-print
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: html_render
5
+ ```php
6
+ <?php
7
+ $name = $_GET["name"];
8
+ print $name;
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: php-xss-sanitizetext
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: html_render
5
+ ```php
6
+ <?php
7
+ $v = $_GET["v"];
8
+ echo sanitize_text_field($v);
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: py-cmd-popen
2
+ - language: python
3
+ - vulnerable: true
4
+ - sink_type: command_execution
5
+ ```python
6
+ import os
7
+ cmd = request.args.get("cmd")
8
+ os.popen(cmd)
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: py-cmd-shlex
2
+ - language: python
3
+ - vulnerable: false
4
+ - sink_type: command_execution
5
+ ```python
6
+ import shlex, subprocess
7
+ cmd = request.args.get("cmd")
8
+ subprocess.run(shlex.quote(cmd))
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: py-deser-yaml
2
+ - language: python
3
+ - vulnerable: true
4
+ - sink_type: deserialization
5
+ ```python
6
+ import yaml
7
+ data = request.get_data()
8
+ yaml.load(data)
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: py-sqli-executescript
2
+ - language: python
3
+ - vulnerable: true
4
+ - sink_type: sql_execution
5
+ ```python
6
+ import sqlite3
7
+ id = request.args.get("id")
8
+ cursor.executescript(f"DELETE FROM t WHERE id={id}")
9
+ ```
@@ -0,0 +1,9 @@
1
+ - id: py-ssrf-urllib
2
+ - language: python
3
+ - vulnerable: true
4
+ - sink_type: http_request
5
+ ```python
6
+ import urllib.request
7
+ url = request.args.get("url")
8
+ urllib.request.urlopen(url)
9
+ ```
@@ -0,0 +1,8 @@
1
+ - id: py-xss-escape
2
+ - language: python
3
+ - vulnerable: false
4
+ - sink_type: html_render
5
+ ```python
6
+ name = request.args.get("name")
7
+ return render_template_string(escape(name))
8
+ ```
package/dist/index.js CHANGED
@@ -30238,6 +30238,87 @@ function rejectFinding(finding, status, reason, evidence) {
30238
30238
  };
30239
30239
  }
30240
30240
 
30241
+ // src/orchestration.ts
30242
+ var STATE_MACHINE = [
30243
+ { state: "scope_check", description: "Validate target against scope + risk policy.", recommended_tools: ["scope_check"], required_evidence: ["scope_decision"], next: ["plan"], confidence: "high" },
30244
+ { state: "plan", description: "Derive the engagement plan from surface + scope.", recommended_tools: ["list_chains"], required_evidence: ["plan_steps"], next: ["recon"], confidence: "high" },
30245
+ { state: "recon", description: "Map the attack surface (endpoints, tech, WAF).", recommended_tools: ["blitz_scan", "active_scan", "fofa_search", "detect_waf"], required_evidence: ["attack_surface"], next: ["analyze"], confidence: "medium" },
30246
+ { state: "analyze", description: "Trace source→sink flows (interprocedural taint).", recommended_tools: ["eagle_eye2", "blitz_scan", "blitz_file"], required_evidence: ["sink_hits", "taint_flow"], next: ["hypothesis"], confidence: "medium" },
30247
+ { state: "hypothesis", description: "Form defensible hypotheses (static finding).", recommended_tools: ["blitz_file", "enrich_scan"], required_evidence: ["static_finding"], next: ["select_chain"], blocking: "live_validation_required", confidence: "high" },
30248
+ { state: "select_chain", description: "Match the hypothesis to an escalation chain.", recommended_tools: ["list_chains", "read_playbook"], required_evidence: ["matched_chain"], next: ["validate"], confidence: "high" },
30249
+ { state: "validate", description: "Verify live with baseline + marker + negative control.", recommended_tools: ["strike_verify", "browser_validate"], required_evidence: ["baseline", "marker_reflection", "negative_control"], next: ["evidence"], confidence: "medium" },
30250
+ { state: "evidence", description: "Attach hashable, provenance-bound evidence.", recommended_tools: ["finding_attach_evidence"], required_evidence: ["evidence_artifact"], next: ["finding"], confidence: "high" },
30251
+ { state: "finding", description: "Confirm the finding (invariants enforced).", recommended_tools: ["confirm_finding"], required_evidence: ["confirmed_finding"], next: ["report"], confidence: "high" },
30252
+ { state: "report", description: "Produce a reproducible report.", recommended_tools: ["report", "security-report"], required_evidence: ["report"], next: [], confidence: "high" }
30253
+ ];
30254
+ function stateDefinition(state) {
30255
+ return STATE_MACHINE.find((s) => s.state === state);
30256
+ }
30257
+ var RISK_POLICY = [
30258
+ { action: "scope_check", risk: "low", requires_scope: false, destructive: false },
30259
+ { action: "blitz_scan", risk: "low", requires_scope: false, destructive: false },
30260
+ { action: "blitz_file", risk: "low", requires_scope: false, destructive: false },
30261
+ { action: "eagle_eye2", risk: "low", requires_scope: false, destructive: false },
30262
+ { action: "enrich_scan", risk: "low", requires_scope: false, destructive: false },
30263
+ { action: "detect_waf", risk: "low", requires_scope: false, destructive: false },
30264
+ { action: "fofa_search", risk: "low", requires_scope: false, destructive: false },
30265
+ { action: "list_chains", risk: "low", requires_scope: false, destructive: false },
30266
+ { action: "read_playbook", risk: "low", requires_scope: false, destructive: false },
30267
+ { action: "active_scan", risk: "medium", requires_scope: true, destructive: false },
30268
+ { action: "browser_validate", risk: "medium", requires_scope: true, destructive: false },
30269
+ { action: "strike_verify", risk: "high", requires_scope: true, destructive: false },
30270
+ { action: "confirm_finding", risk: "high", requires_scope: true, destructive: false }
30271
+ ];
30272
+ function riskOf(action) {
30273
+ return RISK_POLICY.find((r) => r.action === action) ?? null;
30274
+ }
30275
+ function actionPermitted(action, scopeAllowed, mode) {
30276
+ const rule = riskOf(action);
30277
+ if (!rule)
30278
+ return true;
30279
+ if (mode === "ctf" || mode === "offensive" || mode === "reverse-engineering")
30280
+ return true;
30281
+ if (rule.requires_scope && !scopeAllowed)
30282
+ return false;
30283
+ return true;
30284
+ }
30285
+ function buildPlan(scopeAllowed, mode, executedState) {
30286
+ const order = ["scope_check", "plan", "recon", "analyze", "hypothesis", "select_chain", "validate", "evidence", "finding", "report"];
30287
+ const executedIdx = order.indexOf(executedState);
30288
+ return order.map((state, i) => {
30289
+ const def = stateDefinition(state);
30290
+ const tool = def.recommended_tools[0] ?? "";
30291
+ const rule = riskOf(tool);
30292
+ const permitted = actionPermitted(tool, scopeAllowed, mode);
30293
+ const status = i <= executedIdx ? "done" : permitted ? "pending" : "blocked";
30294
+ return {
30295
+ order: i + 1,
30296
+ state,
30297
+ action: def.description,
30298
+ tool,
30299
+ evidence_required: def.required_evidence,
30300
+ risk: rule?.risk ?? "low",
30301
+ permitted,
30302
+ status
30303
+ };
30304
+ });
30305
+ }
30306
+ function computeGuidance(state, scopeAllowed) {
30307
+ const def = stateDefinition(state);
30308
+ const next = def.next[0];
30309
+ const nextDef = next ? stateDefinition(next) : null;
30310
+ const tool = nextDef?.recommended_tools[0] ?? "";
30311
+ const permitted = actionPermitted(tool, scopeAllowed, "bug-bounty");
30312
+ return {
30313
+ state,
30314
+ confidence: def.confidence,
30315
+ blocking_reason: !scopeAllowed && nextDef && riskOf(tool)?.requires_scope ? "scope_required" : def.blocking ?? null,
30316
+ required_evidence: def.required_evidence,
30317
+ recommended_next_action: nextDef?.description ?? "complete",
30318
+ recommended_tool: permitted ? tool : ""
30319
+ };
30320
+ }
30321
+
30241
30322
  // src/orchestrator.ts
30242
30323
  var _chainsCache = null;
30243
30324
  function loadChains() {
@@ -30451,6 +30532,17 @@ function runEngagement(target, scope = "", mode = "bug-bounty", maxFiles = 2000,
30451
30532
  }));
30452
30533
  report.status = "COMPLETE";
30453
30534
  report.note = "All findings are HYPOTHESES until verified live with strike_verify. " + "Apply each chain's negative_control before reporting. Confidence reflects static evidence only.";
30535
+ const executedState = "select_chain";
30536
+ const scopeAllowed = report.scope?.allowed ?? false;
30537
+ const plan = buildPlan(scopeAllowed, mode, executedState);
30538
+ report.state = executedState;
30539
+ report.state_description = stateDefinition(executedState).description;
30540
+ report.plan = plan;
30541
+ report.guidance = computeGuidance(executedState, scopeAllowed);
30542
+ report.risk_policy = {
30543
+ note: "High-risk actions (strike_verify, confirm_finding) require scope + are never run server-side automatically.",
30544
+ gated: plan.filter((p) => !p.permitted).map((p) => p.tool)
30545
+ };
30454
30546
  if (remember) {
30455
30547
  let captured = 0;
30456
30548
  for (const mc of blitz.matched_chains) {
@@ -31803,6 +31895,29 @@ function entryPoints(g) {
31803
31895
  return entries;
31804
31896
  }
31805
31897
 
31898
+ // src/cache.ts
31899
+ import { createHash as createHash3 } from "node:crypto";
31900
+ var mem = new Map;
31901
+ var hits = 0;
31902
+ var misses = 0;
31903
+ function cacheKey(kind, source) {
31904
+ const h = createHash3("sha256").update(source).digest("hex").slice(0, 20);
31905
+ return `${kind}:${VERSION}:${h}`;
31906
+ }
31907
+ function getCache(kind, source) {
31908
+ const k = cacheKey(kind, source);
31909
+ if (mem.has(k)) {
31910
+ hits += 1;
31911
+ return mem.get(k);
31912
+ }
31913
+ misses += 1;
31914
+ return;
31915
+ }
31916
+ function setCache(kind, source, value) {
31917
+ mem.set(cacheKey(kind, source), value);
31918
+ return value;
31919
+ }
31920
+
31806
31921
  // src/eagle2.ts
31807
31922
  var parser3 = new import_php_parser3.Engine({
31808
31923
  parser: { extractDoc: false, php7: true },
@@ -32230,6 +32345,15 @@ function resolveFuncId(name, summaries) {
32230
32345
  return null;
32231
32346
  }
32232
32347
  function analyzeDataFlow2(path) {
32348
+ let cacheCode = "";
32349
+ try {
32350
+ cacheCode = readFileSync12(path, "utf8");
32351
+ } catch {}
32352
+ if (cacheCode) {
32353
+ const cached = getCache("eagle2", cacheCode);
32354
+ if (cached)
32355
+ return cached;
32356
+ }
32233
32357
  const cg = buildCallGraph(path);
32234
32358
  const entries = entryPoints(cg);
32235
32359
  const reachable = new Set;
@@ -32245,7 +32369,7 @@ function analyzeDataFlow2(path) {
32245
32369
  };
32246
32370
  let code;
32247
32371
  try {
32248
- code = readFileSync12(path, "utf8");
32372
+ code = cacheCode || readFileSync12(path, "utf8");
32249
32373
  } catch {
32250
32374
  return result;
32251
32375
  }
@@ -32354,6 +32478,8 @@ function analyzeDataFlow2(path) {
32354
32478
  queue.push({ funcId: calleeId, param: calleeParam, source: item.source, sourceLine: item.sourceLine, viaReturn: false });
32355
32479
  }
32356
32480
  }
32481
+ if (cacheCode)
32482
+ setCache("eagle2", cacheCode, result);
32357
32483
  return result;
32358
32484
  }
32359
32485
 
@@ -50492,13 +50618,23 @@ var jsAdapter = {
50492
50618
  rhsVars.push(c.name);
50493
50619
  assigns.push({ target: name, sources: rhsVars, line: n.loc?.start?.line ?? 0 });
50494
50620
  }
50495
- if (n.type === "AssignmentExpression" && n.left?.type === "Identifier") {
50496
- const name = n.left.name;
50497
- const rhsVars = [];
50498
- for (const c of jsWalk(n.right, []))
50499
- if (c.type === "Identifier" && c.name)
50500
- rhsVars.push(c.name);
50501
- assigns.push({ target: name, sources: rhsVars, line: n.loc?.start?.line ?? 0 });
50621
+ if (n.type === "AssignmentExpression") {
50622
+ if (n.left?.type === "MemberExpression" && n.left.property?.name === "innerHTML") {
50623
+ const rhsVars = [];
50624
+ for (const c of jsWalk(n.right, []))
50625
+ if (c.type === "Identifier" && c.name)
50626
+ rhsVars.push(c.name);
50627
+ for (const v of rhsVars)
50628
+ sinks.push({ variable: v, kind: "html_render", line: n.loc?.start?.line ?? 0, cwe: "CWE-79" });
50629
+ }
50630
+ if (n.left?.type === "Identifier") {
50631
+ const name = n.left.name;
50632
+ const rhsVars = [];
50633
+ for (const c of jsWalk(n.right, []))
50634
+ if (c.type === "Identifier" && c.name)
50635
+ rhsVars.push(c.name);
50636
+ assigns.push({ target: name, sources: rhsVars, line: n.loc?.start?.line ?? 0 });
50637
+ }
50502
50638
  }
50503
50639
  if (n.type === "CallExpression") {
50504
50640
  const calleeText = srcText(n.callee);
@@ -51030,6 +51166,162 @@ function scanFramework(code, frameworkId) {
51030
51166
  };
51031
51167
  }
51032
51168
 
51169
+ // src/browser.ts
51170
+ var chromiumCache = null;
51171
+ async function ensureChromium() {
51172
+ if (chromiumCache)
51173
+ return chromiumCache;
51174
+ try {
51175
+ chromiumCache = await import("playwright-core");
51176
+ return chromiumCache;
51177
+ } catch {
51178
+ return null;
51179
+ }
51180
+ }
51181
+ function unavailable(check, target, reason) {
51182
+ return {
51183
+ check,
51184
+ target: redactSecrets(target),
51185
+ verdict: "unavailable",
51186
+ detail: reason,
51187
+ evidence: { console_errors: [], page_errors: [] }
51188
+ };
51189
+ }
51190
+ function errorEvidence(check, target, e) {
51191
+ return {
51192
+ check,
51193
+ target: redactSecrets(target),
51194
+ verdict: "error",
51195
+ detail: e instanceof Error ? e.message : String(e),
51196
+ evidence: { console_errors: [], page_errors: [] }
51197
+ };
51198
+ }
51199
+ async function validateDomXss(target, payload = '<img src=x onerror="window.__blitz_xss__=true">') {
51200
+ const chr = await ensureChromium();
51201
+ if (!chr)
51202
+ return unavailable("dom_xss", target, "playwright-core not installed (optional peer dependency)");
51203
+ const browser = await chr.chromium.launch({ headless: true });
51204
+ try {
51205
+ const page = await browser.newPage();
51206
+ const consoleErrors = [];
51207
+ const pageErrors = [];
51208
+ page.on("console", (m) => {
51209
+ if (m.type() === "error")
51210
+ consoleErrors.push(m.text());
51211
+ });
51212
+ page.on("pageerror", (e) => pageErrors.push(String(e)));
51213
+ const url = target.includes("{{payload}}") ? target.replace("{{payload}}", encodeURIComponent(payload)) : target;
51214
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 });
51215
+ await page.waitForTimeout(600);
51216
+ const marker = await page.evaluate(() => window.__blitz_xss__ === true);
51217
+ return {
51218
+ check: "dom_xss",
51219
+ target: redactSecrets(target),
51220
+ verdict: marker ? "confirmed" : "not_confirmed",
51221
+ detail: marker ? "payload executed (marker set)" : "payload did not execute (no marker)",
51222
+ evidence: { final_url: page.url(), dom_marker: marker, console_errors: consoleErrors, page_errors: pageErrors }
51223
+ };
51224
+ } catch (e) {
51225
+ return errorEvidence("dom_xss", target, e);
51226
+ } finally {
51227
+ await browser.close();
51228
+ }
51229
+ }
51230
+ async function validateOpenRedirect(target, payload) {
51231
+ const chr = await ensureChromium();
51232
+ if (!chr)
51233
+ return unavailable("open_redirect", target, "playwright-core not installed (optional peer dependency)");
51234
+ const browser = await chr.chromium.launch({ headless: true });
51235
+ try {
51236
+ const page = await browser.newPage();
51237
+ const url = target.includes("{{payload}}") ? target.replace("{{payload}}", encodeURIComponent(payload)) : target;
51238
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 });
51239
+ await page.waitForTimeout(400);
51240
+ const finalUrl = page.url();
51241
+ const redirected = finalUrl !== url && finalUrl.includes(payload);
51242
+ return {
51243
+ check: "open_redirect",
51244
+ target: redactSecrets(target),
51245
+ verdict: redirected ? "confirmed" : "not_confirmed",
51246
+ detail: redirected ? `browser redirected to ${finalUrl}` : `no redirect to attacker URL (final: ${finalUrl})`,
51247
+ evidence: { final_url: finalUrl, console_errors: [], page_errors: [] }
51248
+ };
51249
+ } catch (e) {
51250
+ return errorEvidence("open_redirect", target, e);
51251
+ } finally {
51252
+ await browser.close();
51253
+ }
51254
+ }
51255
+ async function validateAuthBypass(target) {
51256
+ const chr = await ensureChromium();
51257
+ if (!chr)
51258
+ return unavailable("auth_bypass", target, "playwright-core not installed (optional peer dependency)");
51259
+ const browser = await chr.chromium.launch({ headless: true });
51260
+ try {
51261
+ const page = await browser.newPage();
51262
+ const resp = await page.goto(target, { waitUntil: "domcontentloaded", timeout: 15000 });
51263
+ const status = resp?.status() ?? 0;
51264
+ const finalUrl = page.url();
51265
+ const enforced = status === 401 || status === 403 || /login|signin|auth/.test(finalUrl);
51266
+ return {
51267
+ check: "auth_bypass",
51268
+ target: redactSecrets(target),
51269
+ verdict: enforced ? "not_confirmed" : "confirmed",
51270
+ detail: enforced ? `auth enforced (status ${status}, final ${finalUrl})` : `no auth enforcement observed (status ${status}, final ${finalUrl})`,
51271
+ evidence: { final_url: finalUrl, status, console_errors: [], page_errors: [] }
51272
+ };
51273
+ } catch (e) {
51274
+ return errorEvidence("auth_bypass", target, e);
51275
+ } finally {
51276
+ await browser.close();
51277
+ }
51278
+ }
51279
+ async function validateCsrf(target) {
51280
+ const chr = await ensureChromium();
51281
+ if (!chr)
51282
+ return unavailable("csrf", target, "playwright-core not installed (optional peer dependency)");
51283
+ const browser = await chr.chromium.launch({ headless: true });
51284
+ try {
51285
+ const page = await browser.newPage();
51286
+ await page.goto(target, { waitUntil: "domcontentloaded", timeout: 15000 });
51287
+ const result = await page.evaluate(() => {
51288
+ const forms = Array.from(document.querySelectorAll("form"));
51289
+ const stateChanging = forms.filter((f) => /post/i.test(f.method) || f.method === "");
51290
+ if (stateChanging.length === 0)
51291
+ return { token: true, count: 0 };
51292
+ for (const f of stateChanging) {
51293
+ const token = f.querySelector('input[name*="csrf"], input[name*="token"], input[name*="_token"], input[name*="nonce"]');
51294
+ if (!token)
51295
+ return { token: false, count: stateChanging.length };
51296
+ }
51297
+ return { token: true, count: stateChanging.length };
51298
+ });
51299
+ return {
51300
+ check: "csrf",
51301
+ target: redactSecrets(target),
51302
+ verdict: result.token ? "not_confirmed" : "confirmed",
51303
+ detail: result.token ? `CSRF token present (${result.count} form(s))` : `state-changing form missing CSRF token (${result.count} form(s))`,
51304
+ evidence: { csrf_token_present: result.token, forms_count: result.count, console_errors: [], page_errors: [] }
51305
+ };
51306
+ } catch (e) {
51307
+ return errorEvidence("csrf", target, e);
51308
+ } finally {
51309
+ await browser.close();
51310
+ }
51311
+ }
51312
+ async function browserValidate(check, target, payload) {
51313
+ switch (check) {
51314
+ case "dom_xss":
51315
+ return validateDomXss(target, payload);
51316
+ case "open_redirect":
51317
+ return validateOpenRedirect(target, payload ?? "https://attacker.example/");
51318
+ case "auth_bypass":
51319
+ return validateAuthBypass(target);
51320
+ case "csrf":
51321
+ return validateCsrf(target);
51322
+ }
51323
+ }
51324
+
51033
51325
  // src/guidance.ts
51034
51326
  var SINK_DIRECTIVES = {
51035
51327
  sql_execution: "Validate the SQL injection live with strike_verify (use a unique MARKER and a NEGATIVE CONTROL that stays inert), or read_tool_manual for sqlmap. Only a reflected marker + inert control = confirmed.",
@@ -52039,6 +52331,19 @@ function coverageMatrix() {
52039
52331
  const covered = langs.map((l) => l.language).filter((lang) => SINK_BY_LANG[lang]?.[sink] === true);
52040
52332
  return { sink, languages: covered };
52041
52333
  });
52334
+ const frameworkCoverage = listFrameworks().map(({ id, language }) => {
52335
+ const p = frameworkProfile(id);
52336
+ const cwe = p ? [...new Set(p.sinks.map((s) => s.cwe).filter((c) => !!c))].sort() : [];
52337
+ return {
52338
+ framework: id,
52339
+ language,
52340
+ sinks: p?.sinks.length ?? 0,
52341
+ sources: p?.sources.length ?? 0,
52342
+ sanitizers: p?.sanitizers.length ?? 0,
52343
+ auth: p?.auth.length ?? 0,
52344
+ cwe
52345
+ };
52346
+ });
52042
52347
  let coveredPairs = 0;
52043
52348
  for (const lang of langs) {
52044
52349
  for (const sink of SINK_CLASSES) {
@@ -52050,11 +52355,13 @@ function coverageMatrix() {
52050
52355
  return {
52051
52356
  languages: langs,
52052
52357
  sink_coverage: sinkCoverage,
52358
+ framework_coverage: frameworkCoverage,
52053
52359
  counts: {
52054
52360
  languages: langs.length,
52055
52361
  sink_classes: SINK_CLASSES.length,
52056
52362
  covered_pairs: coveredPairs,
52057
- total_pairs: totalPairs
52363
+ total_pairs: totalPairs,
52364
+ frameworks: frameworkCoverage.length
52058
52365
  },
52059
52366
  coverage_ratio: Number((coveredPairs / totalPairs).toFixed(3))
52060
52367
  };
@@ -52818,6 +53125,18 @@ function createServer() {
52818
53125
  out.scan = scanFramework(code, framework);
52819
53126
  return { content: [{ type: "text", text: JSON.stringify(out) }] };
52820
53127
  });
53128
+ server.registerTool("browser_validate", {
53129
+ title: "Browser validation (Playwright)",
53130
+ description: "PHASE 6: validate a static-analysis HYPOTHESIS by driving a real headless Chromium. Checks: dom_xss, open_redirect, auth_bypass, csrf. Use only when necessary — to confirm a hypothesis with browser evidence, not as a first-pass scan. Requires playwright-core (optional peer dependency).",
53131
+ inputSchema: {
53132
+ check: _enum(["dom_xss", "open_redirect", "auth_bypass", "csrf"]).describe("Validation check to run"),
53133
+ target: string2().describe("Target URL (use {{payload}} placeholder for dom_xss/open_redirect injection)"),
53134
+ payload: string2().optional().describe("Payload to inject (defaults to an XSS marker for dom_xss)")
53135
+ }
53136
+ }, async ({ check, target, payload }) => {
53137
+ const result = await browserValidate(check, target, payload);
53138
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
53139
+ });
52821
53140
  server.registerTool("taint_tree", {
52822
53141
  title: "Whole-tree taint scan",
52823
53142
  description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
@@ -52971,12 +53290,89 @@ async function serve() {
52971
53290
  }
52972
53291
 
52973
53292
  // src/cli.ts
52974
- import { readFileSync as readFileSync18, existsSync as existsSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
52975
- import { join as join13, dirname as dirname2 } from "node:path";
53293
+ import { readFileSync as readFileSync20, existsSync as existsSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
53294
+ import { join as join15, dirname as dirname2 } from "node:path";
52976
53295
  import { homedir as homedir5 } from "node:os";
52977
- import { fileURLToPath as fileURLToPath9 } from "node:url";
53296
+ import { fileURLToPath as fileURLToPath11 } from "node:url";
52978
53297
  import { spawnSync as spawnSync3 } from "node:child_process";
53298
+
53299
+ // src/release.ts
53300
+ import { readFileSync as readFileSync18, existsSync as existsSync7 } from "node:fs";
53301
+ import { join as join13 } from "node:path";
53302
+ import { fileURLToPath as fileURLToPath9 } from "node:url";
53303
+ import { execSync as execSync2 } from "node:child_process";
52979
53304
  var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
53305
+ function validateRelease() {
53306
+ const checks = [];
53307
+ checks.push({ name: "package.json version", ok: true, detail: VERSION });
53308
+ try {
53309
+ const changelog = readFileSync18(join13(ROOT7, "CHANGELOG.md"), "utf8");
53310
+ const m = changelog.match(/^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]/m);
53311
+ const changelogVersion = m?.[1] ?? null;
53312
+ checks.push({
53313
+ name: "CHANGELOG version",
53314
+ ok: changelogVersion === VERSION,
53315
+ detail: changelogVersion ?? "no version header found"
53316
+ });
53317
+ } catch {
53318
+ checks.push({ name: "CHANGELOG version", ok: false, detail: "CHANGELOG.md not found" });
53319
+ }
53320
+ try {
53321
+ const tag = execSync2("git describe --tags --abbrev=0", { cwd: ROOT7, encoding: "utf8" }).trim();
53322
+ checks.push({ name: "Git tag", ok: tag === `v${VERSION}`, detail: tag, soft: true });
53323
+ } catch {
53324
+ checks.push({ name: "Git tag", ok: true, detail: "unavailable (no git)", soft: true });
53325
+ }
53326
+ try {
53327
+ const published = execSync2("npm view blitzstrike version", { encoding: "utf8", timeout: 8000 }).trim();
53328
+ checks.push({ name: "npm registry version", ok: published === VERSION, detail: published, soft: true });
53329
+ } catch {
53330
+ checks.push({ name: "npm registry version", ok: true, detail: "unavailable (offline)", soft: true });
53331
+ }
53332
+ checks.push({
53333
+ name: "Lockfile",
53334
+ ok: lockfilePresent(),
53335
+ detail: lockfilePresent() ? "present (bun.lock / package-lock.json)" : "missing",
53336
+ soft: true
53337
+ });
53338
+ return { version: VERSION, checks, consistent: checks.filter((c) => !c.soft).every((c) => c.ok) };
53339
+ }
53340
+ function lockfilePresent() {
53341
+ return existsSync7(join13(ROOT7, "package-lock.json")) || existsSync7(join13(ROOT7, "bun.lock")) || existsSync7(join13(ROOT7, "bun.lockb"));
53342
+ }
53343
+
53344
+ // src/dependency.ts
53345
+ import { readFileSync as readFileSync19 } from "node:fs";
53346
+ import { join as join14 } from "node:path";
53347
+ import { fileURLToPath as fileURLToPath10 } from "node:url";
53348
+ var ROOT8 = join14(fileURLToPath10(new URL(".", import.meta.url)), "..");
53349
+ function checkDependencies() {
53350
+ let pkg = {};
53351
+ try {
53352
+ pkg = JSON.parse(readFileSync19(join14(ROOT8, "package.json"), "utf8"));
53353
+ } catch {
53354
+ return { install_scripts: [], git_dependencies: [], total_dependencies: 0, safe: false };
53355
+ }
53356
+ const installScriptNames = ["preinstall", "install", "postinstall", "prepare"];
53357
+ const installScripts = installScriptNames.filter((n) => pkg.scripts?.[n]).map((n) => ({ name: n, command: pkg.scripts[n] }));
53358
+ const allDeps = {
53359
+ ...pkg.dependencies ?? {},
53360
+ ...pkg.devDependencies ?? {},
53361
+ ...pkg.peerDependencies ?? {},
53362
+ ...pkg.optionalDependencies ?? {}
53363
+ };
53364
+ const gitDependencies = Object.entries(allDeps).filter(([, v]) => /^(git\+|git:|github:|gitlab:|bitbucket:|\w+:\/\/)/.test(v)).map(([name, v]) => `${name}@${v}`);
53365
+ const total = Object.keys(allDeps).length;
53366
+ return {
53367
+ install_scripts: installScripts,
53368
+ git_dependencies: gitDependencies,
53369
+ total_dependencies: total,
53370
+ safe: installScripts.length === 0 && gitDependencies.length === 0
53371
+ };
53372
+ }
53373
+
53374
+ // src/cli.ts
53375
+ var ROOT9 = join15(fileURLToPath11(new URL(".", import.meta.url)), "..");
52980
53376
  function which(cmd) {
52981
53377
  return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
52982
53378
  }
@@ -53011,13 +53407,27 @@ function runDoctor() {
53011
53407
  detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
53012
53408
  fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
53013
53409
  });
53014
- const dataOk = existsSync7(join13(ROOT7, "chains.json")) && existsSync7(join13(ROOT7, "tools-catalog.json"));
53410
+ const dataOk = existsSync8(join15(ROOT9, "chains.json")) && existsSync8(join15(ROOT9, "tools-catalog.json"));
53015
53411
  issues.push({
53016
53412
  name: "Data layers (chains + tools-catalog)",
53017
53413
  status: dataOk ? "ok" : "fail",
53018
53414
  detail: dataOk ? "present" : "chains.json or tools-catalog.json missing",
53019
53415
  fix: dataOk ? undefined : "re-install blitzstrike"
53020
53416
  });
53417
+ const release = validateRelease();
53418
+ issues.push({
53419
+ name: "Release metadata",
53420
+ status: release.consistent ? "ok" : "fail",
53421
+ detail: release.consistent ? `v${release.version} consistent` : release.checks.filter((c) => !c.ok && !c.soft).map((c) => c.name).join(", ") || "drift",
53422
+ fix: release.consistent ? undefined : "align package.json version with the latest CHANGELOG entry"
53423
+ });
53424
+ const deps = checkDependencies();
53425
+ issues.push({
53426
+ name: "Dependency surface",
53427
+ status: deps.safe ? "ok" : "warn",
53428
+ detail: deps.safe ? `${deps.total_dependencies} deps, no install scripts` : `${deps.install_scripts.length} install script(s) + ${deps.git_dependencies.length} git dep(s)`,
53429
+ fix: deps.safe ? undefined : "remove install-time scripts and git dependencies"
53430
+ });
53021
53431
  const symbol = { ok: "OK ", warn: "WARN", fail: "FAIL" };
53022
53432
  for (const i of issues) {
53023
53433
  console.log(`${symbol[i.status]} ${i.name} ${i.detail}`);
@@ -53039,21 +53449,21 @@ BlitzStrike — ${verdict}`);
53039
53449
  function resolveCommand() {
53040
53450
  if (which("blitzstrike"))
53041
53451
  return { command: "blitzstrike", args: ["serve", "--mcp"] };
53042
- const src = join13(ROOT7, "src", "index.ts");
53043
- if (which("bun") && existsSync7(src))
53452
+ const src = join15(ROOT9, "src", "index.ts");
53453
+ if (which("bun") && existsSync8(src))
53044
53454
  return { command: "bun", args: ["run", src, "serve", "--mcp"] };
53045
53455
  if (which("npx"))
53046
53456
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
53047
53457
  if (which("bunx"))
53048
53458
  return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
53049
- const dist = join13(ROOT7, "dist", "index.js");
53050
- if (existsSync7(dist))
53459
+ const dist = join15(ROOT9, "dist", "index.js");
53460
+ if (existsSync8(dist))
53051
53461
  return { command: "bun", args: [dist, "serve", "--mcp"] };
53052
53462
  return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
53053
53463
  }
53054
53464
  function readJson(p) {
53055
53465
  try {
53056
- return JSON.parse(readFileSync18(p, "utf8"));
53466
+ return JSON.parse(readFileSync20(p, "utf8"));
53057
53467
  } catch {
53058
53468
  return null;
53059
53469
  }
@@ -53061,7 +53471,7 @@ function readJson(p) {
53061
53471
  function jsonMcpServersWrite(p) {
53062
53472
  return (command, args) => {
53063
53473
  const dir = dirname2(p);
53064
- if (!existsSync7(dir))
53474
+ if (!existsSync8(dir))
53065
53475
  mkdirSync4(dir, { recursive: true });
53066
53476
  const existing = readJson(p) ?? {};
53067
53477
  existing.mcpServers = {
@@ -53074,7 +53484,7 @@ function jsonMcpServersWrite(p) {
53074
53484
  function opencodeWrite(p) {
53075
53485
  return (_command, _args) => {
53076
53486
  const dir = dirname2(p);
53077
- if (!existsSync7(dir))
53487
+ if (!existsSync8(dir))
53078
53488
  mkdirSync4(dir, { recursive: true });
53079
53489
  const existing = readJson(p) ?? {};
53080
53490
  existing.mcp = {
@@ -53086,26 +53496,26 @@ function opencodeWrite(p) {
53086
53496
  };
53087
53497
  }
53088
53498
  function copyOpenCodeAgents() {
53089
- const srcDir = join13(ROOT7, "opencode-agents");
53090
- const dstDir = join13(homedir5(), ".config", "opencode", "agents");
53091
- if (!existsSync7(srcDir))
53499
+ const srcDir = join15(ROOT9, "opencode-agents");
53500
+ const dstDir = join15(homedir5(), ".config", "opencode", "agents");
53501
+ if (!existsSync8(srcDir))
53092
53502
  return;
53093
- if (!existsSync7(dstDir))
53503
+ if (!existsSync8(dstDir))
53094
53504
  mkdirSync4(dstDir, { recursive: true });
53095
53505
  for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
53096
- const src = join13(srcDir, f);
53097
- const dst = join13(dstDir, f);
53098
- if (existsSync7(src)) {
53099
- writeFileSync5(dst, readFileSync18(src, "utf8"));
53506
+ const src = join15(srcDir, f);
53507
+ const dst = join15(dstDir, f);
53508
+ if (existsSync8(src)) {
53509
+ writeFileSync5(dst, readFileSync20(src, "utf8"));
53100
53510
  }
53101
53511
  }
53102
53512
  }
53103
53513
  function codexWrite(p) {
53104
53514
  return (command, args) => {
53105
53515
  const dir = dirname2(p);
53106
- if (!existsSync7(dir))
53516
+ if (!existsSync8(dir))
53107
53517
  mkdirSync4(dir, { recursive: true });
53108
- let existing = existsSync7(p) ? readFileSync18(p, "utf8") : "";
53518
+ let existing = existsSync8(p) ? readFileSync20(p, "utf8") : "";
53109
53519
  if (!existing.trimEnd().endsWith(`
53110
53520
  `))
53111
53521
  existing += `
@@ -53122,9 +53532,9 @@ args = ${JSON.stringify(args)}
53122
53532
  function hermesWrite(p) {
53123
53533
  return (command, args) => {
53124
53534
  const dir = dirname2(p);
53125
- if (!existsSync7(dir))
53535
+ if (!existsSync8(dir))
53126
53536
  mkdirSync4(dir, { recursive: true });
53127
- let existing = existsSync7(p) ? readFileSync18(p, "utf8") : "";
53537
+ let existing = existsSync8(p) ? readFileSync20(p, "utf8") : "";
53128
53538
  existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
53129
53539
  if (!existing.trimEnd().endsWith(`
53130
53540
  `))
@@ -53151,74 +53561,74 @@ mcp_servers:
53151
53561
  function detectAgents() {
53152
53562
  const home = homedir5();
53153
53563
  const agents = [];
53154
- const claudeJson = join13(home, ".claude.json");
53564
+ const claudeJson = join15(home, ".claude.json");
53155
53565
  agents.push({
53156
53566
  name: "Claude Code",
53157
53567
  detectPath: claudeJson,
53158
- installed: () => existsSync7(claudeJson) || which("claude"),
53568
+ installed: () => existsSync8(claudeJson) || which("claude"),
53159
53569
  write: jsonMcpServersWrite(claudeJson)
53160
53570
  });
53161
- const claudeDesktop = join13(home, ".config", "Claude", "claude_desktop_config.json");
53571
+ const claudeDesktop = join15(home, ".config", "Claude", "claude_desktop_config.json");
53162
53572
  agents.push({
53163
53573
  name: "Claude Desktop",
53164
53574
  detectPath: claudeDesktop,
53165
- installed: () => existsSync7(claudeDesktop),
53575
+ installed: () => existsSync8(claudeDesktop),
53166
53576
  write: jsonMcpServersWrite(claudeDesktop)
53167
53577
  });
53168
- const cursor = join13(home, ".cursor", "mcp.json");
53578
+ const cursor = join15(home, ".cursor", "mcp.json");
53169
53579
  agents.push({
53170
53580
  name: "Cursor",
53171
53581
  detectPath: cursor,
53172
- installed: () => existsSync7(join13(home, ".cursor")) || which("cursor"),
53582
+ installed: () => existsSync8(join15(home, ".cursor")) || which("cursor"),
53173
53583
  write: jsonMcpServersWrite(cursor)
53174
53584
  });
53175
- const opencode = join13(home, ".config", "opencode", "opencode.jsonc");
53585
+ const opencode = join15(home, ".config", "opencode", "opencode.jsonc");
53176
53586
  agents.push({
53177
53587
  name: "OpenCode",
53178
53588
  detectPath: opencode,
53179
- installed: () => existsSync7(join13(home, ".config", "opencode")) || which("opencode"),
53589
+ installed: () => existsSync8(join15(home, ".config", "opencode")) || which("opencode"),
53180
53590
  write: opencodeWrite(opencode)
53181
53591
  });
53182
- const codex = join13(home, ".codex", "config.toml");
53592
+ const codex = join15(home, ".codex", "config.toml");
53183
53593
  agents.push({
53184
53594
  name: "Codex",
53185
53595
  detectPath: codex,
53186
- installed: () => existsSync7(join13(home, ".codex")) || which("codex"),
53596
+ installed: () => existsSync8(join15(home, ".codex")) || which("codex"),
53187
53597
  write: codexWrite(codex)
53188
53598
  });
53189
- const hermes = join13(home, ".hermes", "config.yaml");
53599
+ const hermes = join15(home, ".hermes", "config.yaml");
53190
53600
  agents.push({
53191
53601
  name: "Hermes",
53192
53602
  detectPath: hermes,
53193
- installed: () => existsSync7(join13(home, ".hermes")) || which("hermes"),
53603
+ installed: () => existsSync8(join15(home, ".hermes")) || which("hermes"),
53194
53604
  write: hermesWrite(hermes)
53195
53605
  });
53196
- const gemini = join13(home, ".gemini", "settings.json");
53606
+ const gemini = join15(home, ".gemini", "settings.json");
53197
53607
  agents.push({
53198
53608
  name: "Gemini CLI",
53199
53609
  detectPath: gemini,
53200
- installed: () => existsSync7(join13(home, ".gemini")) || which("gemini"),
53610
+ installed: () => existsSync8(join15(home, ".gemini")) || which("gemini"),
53201
53611
  write: jsonMcpServersWrite(gemini)
53202
53612
  });
53203
- const windsurf = join13(home, ".codeium", "windsurf", "mcp_config.json");
53613
+ const windsurf = join15(home, ".codeium", "windsurf", "mcp_config.json");
53204
53614
  agents.push({
53205
53615
  name: "Windsurf",
53206
53616
  detectPath: windsurf,
53207
- installed: () => existsSync7(join13(home, ".codeium")) || which("windsurf"),
53617
+ installed: () => existsSync8(join15(home, ".codeium")) || which("windsurf"),
53208
53618
  write: jsonMcpServersWrite(windsurf)
53209
53619
  });
53210
- const copilot = join13(home, ".copilot", "mcp.json");
53620
+ const copilot = join15(home, ".copilot", "mcp.json");
53211
53621
  agents.push({
53212
53622
  name: "Copilot",
53213
53623
  detectPath: copilot,
53214
- installed: () => existsSync7(join13(home, ".copilot")),
53624
+ installed: () => existsSync8(join15(home, ".copilot")),
53215
53625
  write: jsonMcpServersWrite(copilot)
53216
53626
  });
53217
- const cline = join13(home, ".cline", "mcp_settings.json");
53627
+ const cline = join15(home, ".cline", "mcp_settings.json");
53218
53628
  agents.push({
53219
53629
  name: "Cline",
53220
53630
  detectPath: cline,
53221
- installed: () => existsSync7(join13(home, ".cline")),
53631
+ installed: () => existsSync8(join15(home, ".cline")),
53222
53632
  write: jsonMcpServersWrite(cline)
53223
53633
  });
53224
53634
  return agents;
@@ -53262,15 +53672,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
53262
53672
  }
53263
53673
 
53264
53674
  // src/index.ts
53265
- import { readFileSync as readFileSync19, existsSync as existsSync8 } from "node:fs";
53266
- import { join as join14 } from "node:path";
53267
- import { fileURLToPath as fileURLToPath10 } from "node:url";
53268
- var ROOT8 = join14(fileURLToPath10(new URL(".", import.meta.url)), "..");
53675
+ import { readFileSync as readFileSync21, existsSync as existsSync9 } from "node:fs";
53676
+ import { join as join16 } from "node:path";
53677
+ import { fileURLToPath as fileURLToPath12 } from "node:url";
53678
+ var ROOT10 = join16(fileURLToPath12(new URL(".", import.meta.url)), "..");
53269
53679
  function readVersion() {
53270
53680
  try {
53271
- const p = join14(ROOT8, "package.json");
53272
- if (existsSync8(p))
53273
- return JSON.parse(readFileSync19(p, "utf8")).version ?? "1.0.0";
53681
+ const p = join16(ROOT10, "package.json");
53682
+ if (existsSync9(p))
53683
+ return JSON.parse(readFileSync21(p, "utf8")).version ?? "1.0.0";
53274
53684
  } catch {}
53275
53685
  return "1.0.0";
53276
53686
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.39",
3
+ "version": "1.0.43",
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": {
@@ -25,12 +25,13 @@
25
25
  "tools-catalog.json"
26
26
  ],
27
27
  "scripts": {
28
- "build": "bun build src/index.ts --outdir dist --target node --format esm",
28
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --external playwright-core",
29
29
  "typecheck": "tsc --noEmit",
30
30
  "serve": "bun run src/index.ts serve",
31
31
  "doctor": "bun run src/index.ts doctor",
32
32
  "compile": "bun build src/index.ts --compile --outfile dist/blitzstrike",
33
33
  "test": "bun run test/verify.ts",
34
+ "test:browser": "bun run test/browser-verify.ts",
34
35
  "prepublishOnly": "bun run typecheck && bun run build",
35
36
  "bench": "bun run test/verify.ts"
36
37
  },
@@ -74,6 +75,15 @@
74
75
  },
75
76
  "devDependencies": {
76
77
  "@types/node": "^22.20.2",
78
+ "playwright-core": "1.61.0",
77
79
  "typescript": "^5.5.0"
80
+ },
81
+ "peerDependencies": {
82
+ "playwright-core": ">=1.61.0"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "playwright-core": {
86
+ "optional": true
87
+ }
78
88
  }
79
89
  }