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.
- package/CHANGELOG.md +116 -0
- package/bench/corpus/java-cmd-processbuilder.md +8 -0
- package/bench/corpus/java-deser-readunshared.md +8 -0
- package/bench/corpus/java-sqli-execute.md +8 -0
- package/bench/corpus/java-ssrf-httpurl.md +8 -0
- package/bench/corpus/java-xss-escape.md +8 -0
- package/bench/corpus/java-xss-println.md +8 -0
- package/bench/corpus/js-cmd-execsync.md +8 -0
- package/bench/corpus/js-filewrite.md +8 -0
- package/bench/corpus/js-pathtraversal-readsync.md +8 -0
- package/bench/corpus/js-redirect-encodeuri.md +8 -0
- package/bench/corpus/js-ssrf-axios.md +8 -0
- package/bench/corpus/js-ssrf-fetch.md +8 -0
- package/bench/corpus/js-xss-documentwrite.md +8 -0
- package/bench/corpus/js-xss-innerhtml.md +8 -0
- package/bench/corpus/php-cmd-absint.md +9 -0
- package/bench/corpus/php-cmd-popen.md +9 -0
- package/bench/corpus/php-deser-json.md +9 -0
- package/bench/corpus/php-include-once.md +9 -0
- package/bench/corpus/php-include-sanitized.md +9 -0
- package/bench/corpus/php-sqli-filterinput.md +9 -0
- package/bench/corpus/php-sqli-getresults.md +9 -0
- package/bench/corpus/php-ssrf-wppost.md +9 -0
- package/bench/corpus/php-xss-escurl.md +9 -0
- package/bench/corpus/php-xss-print.md +9 -0
- package/bench/corpus/php-xss-sanitizetext.md +9 -0
- package/bench/corpus/py-cmd-popen.md +9 -0
- package/bench/corpus/py-cmd-shlex.md +9 -0
- package/bench/corpus/py-deser-yaml.md +9 -0
- package/bench/corpus/py-sqli-executescript.md +9 -0
- package/bench/corpus/py-ssrf-urllib.md +9 -0
- package/bench/corpus/py-xss-escape.md +8 -0
- package/dist/index.js +469 -59
- 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
|
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"
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50499
|
-
|
|
50500
|
-
|
|
50501
|
-
|
|
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
|
|
52975
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
53043
|
-
if (which("bun") &&
|
|
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 =
|
|
53050
|
-
if (
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
53090
|
-
const dstDir =
|
|
53091
|
-
if (!
|
|
53499
|
+
const srcDir = join15(ROOT9, "opencode-agents");
|
|
53500
|
+
const dstDir = join15(homedir5(), ".config", "opencode", "agents");
|
|
53501
|
+
if (!existsSync8(srcDir))
|
|
53092
53502
|
return;
|
|
53093
|
-
if (!
|
|
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 =
|
|
53097
|
-
const dst =
|
|
53098
|
-
if (
|
|
53099
|
-
writeFileSync5(dst,
|
|
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 (!
|
|
53516
|
+
if (!existsSync8(dir))
|
|
53107
53517
|
mkdirSync4(dir, { recursive: true });
|
|
53108
|
-
let existing =
|
|
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 (!
|
|
53535
|
+
if (!existsSync8(dir))
|
|
53126
53536
|
mkdirSync4(dir, { recursive: true });
|
|
53127
|
-
let existing =
|
|
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 =
|
|
53564
|
+
const claudeJson = join15(home, ".claude.json");
|
|
53155
53565
|
agents.push({
|
|
53156
53566
|
name: "Claude Code",
|
|
53157
53567
|
detectPath: claudeJson,
|
|
53158
|
-
installed: () =>
|
|
53568
|
+
installed: () => existsSync8(claudeJson) || which("claude"),
|
|
53159
53569
|
write: jsonMcpServersWrite(claudeJson)
|
|
53160
53570
|
});
|
|
53161
|
-
const claudeDesktop =
|
|
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: () =>
|
|
53575
|
+
installed: () => existsSync8(claudeDesktop),
|
|
53166
53576
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
53167
53577
|
});
|
|
53168
|
-
const cursor =
|
|
53578
|
+
const cursor = join15(home, ".cursor", "mcp.json");
|
|
53169
53579
|
agents.push({
|
|
53170
53580
|
name: "Cursor",
|
|
53171
53581
|
detectPath: cursor,
|
|
53172
|
-
installed: () =>
|
|
53582
|
+
installed: () => existsSync8(join15(home, ".cursor")) || which("cursor"),
|
|
53173
53583
|
write: jsonMcpServersWrite(cursor)
|
|
53174
53584
|
});
|
|
53175
|
-
const opencode =
|
|
53585
|
+
const opencode = join15(home, ".config", "opencode", "opencode.jsonc");
|
|
53176
53586
|
agents.push({
|
|
53177
53587
|
name: "OpenCode",
|
|
53178
53588
|
detectPath: opencode,
|
|
53179
|
-
installed: () =>
|
|
53589
|
+
installed: () => existsSync8(join15(home, ".config", "opencode")) || which("opencode"),
|
|
53180
53590
|
write: opencodeWrite(opencode)
|
|
53181
53591
|
});
|
|
53182
|
-
const codex =
|
|
53592
|
+
const codex = join15(home, ".codex", "config.toml");
|
|
53183
53593
|
agents.push({
|
|
53184
53594
|
name: "Codex",
|
|
53185
53595
|
detectPath: codex,
|
|
53186
|
-
installed: () =>
|
|
53596
|
+
installed: () => existsSync8(join15(home, ".codex")) || which("codex"),
|
|
53187
53597
|
write: codexWrite(codex)
|
|
53188
53598
|
});
|
|
53189
|
-
const hermes =
|
|
53599
|
+
const hermes = join15(home, ".hermes", "config.yaml");
|
|
53190
53600
|
agents.push({
|
|
53191
53601
|
name: "Hermes",
|
|
53192
53602
|
detectPath: hermes,
|
|
53193
|
-
installed: () =>
|
|
53603
|
+
installed: () => existsSync8(join15(home, ".hermes")) || which("hermes"),
|
|
53194
53604
|
write: hermesWrite(hermes)
|
|
53195
53605
|
});
|
|
53196
|
-
const gemini =
|
|
53606
|
+
const gemini = join15(home, ".gemini", "settings.json");
|
|
53197
53607
|
agents.push({
|
|
53198
53608
|
name: "Gemini CLI",
|
|
53199
53609
|
detectPath: gemini,
|
|
53200
|
-
installed: () =>
|
|
53610
|
+
installed: () => existsSync8(join15(home, ".gemini")) || which("gemini"),
|
|
53201
53611
|
write: jsonMcpServersWrite(gemini)
|
|
53202
53612
|
});
|
|
53203
|
-
const windsurf =
|
|
53613
|
+
const windsurf = join15(home, ".codeium", "windsurf", "mcp_config.json");
|
|
53204
53614
|
agents.push({
|
|
53205
53615
|
name: "Windsurf",
|
|
53206
53616
|
detectPath: windsurf,
|
|
53207
|
-
installed: () =>
|
|
53617
|
+
installed: () => existsSync8(join15(home, ".codeium")) || which("windsurf"),
|
|
53208
53618
|
write: jsonMcpServersWrite(windsurf)
|
|
53209
53619
|
});
|
|
53210
|
-
const copilot =
|
|
53620
|
+
const copilot = join15(home, ".copilot", "mcp.json");
|
|
53211
53621
|
agents.push({
|
|
53212
53622
|
name: "Copilot",
|
|
53213
53623
|
detectPath: copilot,
|
|
53214
|
-
installed: () =>
|
|
53624
|
+
installed: () => existsSync8(join15(home, ".copilot")),
|
|
53215
53625
|
write: jsonMcpServersWrite(copilot)
|
|
53216
53626
|
});
|
|
53217
|
-
const cline =
|
|
53627
|
+
const cline = join15(home, ".cline", "mcp_settings.json");
|
|
53218
53628
|
agents.push({
|
|
53219
53629
|
name: "Cline",
|
|
53220
53630
|
detectPath: cline,
|
|
53221
|
-
installed: () =>
|
|
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
|
|
53266
|
-
import { join as
|
|
53267
|
-
import { fileURLToPath as
|
|
53268
|
-
var
|
|
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 =
|
|
53272
|
-
if (
|
|
53273
|
-
return JSON.parse(
|
|
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.
|
|
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
|
}
|