blitzstrike 1.0.37 → 1.0.39
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 +27 -0
- package/dist/index.js +261 -78
- package/intelligence/frameworks.json +1781 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.39] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Phase 5 — Framework Intelligence** (`src/frameworks.ts` + `intelligence/frameworks.json`):
|
|
13
|
+
- Data-driven framework knowledge for 7 frameworks (361 patterns), extensible
|
|
14
|
+
without code changes: Laravel, WordPress (PHP); Express, Next.js (JS/TS);
|
|
15
|
+
Django, FastAPI (Python); Spring (Java). Rails (Ruby) deferred until Ruby support.
|
|
16
|
+
- Each framework defines sources, sinks, sanitizers, auth gates, routing, ORM,
|
|
17
|
+
and middleware patterns (with CWE tags on sinks).
|
|
18
|
+
- `detectFramework(code, language)` — heuristic detection via scored signals.
|
|
19
|
+
- `scanFramework(code, framework)` — framework-aware source/sink/sanitizer/auth hits.
|
|
20
|
+
- New MCP tools: `list_frameworks`, `detect_framework`, `framework_intel`.
|
|
21
|
+
|
|
22
|
+
## [1.0.38] — 2026-09-13
|
|
23
|
+
|
|
24
|
+
### Fixed (Phase 4 review)
|
|
25
|
+
|
|
26
|
+
- `diff_analyze` ignored the `language` argument for non-PHP code (bare `file`
|
|
27
|
+
without an extension resolved to `unknown`, silently returning 0 findings).
|
|
28
|
+
The engine now derives a probe extension from the declared language.
|
|
29
|
+
- `eagle_eye2` now surfaces `entry_points` (uncalled functions = potential unauth
|
|
30
|
+
entry points) and `unreachable` (dead code) — previously computed but hidden.
|
|
31
|
+
- Removed the unused `call_graph` field from `Eagle2Result.summary` (Map/Set-based,
|
|
32
|
+
not JSON-safe, never consumed).
|
|
33
|
+
- Removed the dead `nextCorrelationId` export from `audit.ts`.
|
|
34
|
+
|
|
8
35
|
## [1.0.37] — 2026-09-13
|
|
9
36
|
|
|
10
37
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -14820,10 +14820,10 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as
|
|
14824
|
-
import { join as
|
|
14823
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync16, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
|
|
14824
|
+
import { join as join12 } from "node:path";
|
|
14825
14825
|
import { homedir as homedir4 } from "node:os";
|
|
14826
|
-
import { fileURLToPath as
|
|
14826
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
14827
14827
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14828
14828
|
function dirExists(p) {
|
|
14829
14829
|
try {
|
|
@@ -14838,7 +14838,7 @@ function countFiles(dir) {
|
|
|
14838
14838
|
const walk = (d) => {
|
|
14839
14839
|
for (const e of readdirSync5(d, { withFileTypes: true })) {
|
|
14840
14840
|
if (e.isDirectory())
|
|
14841
|
-
walk(
|
|
14841
|
+
walk(join12(d, e.name));
|
|
14842
14842
|
else
|
|
14843
14843
|
n++;
|
|
14844
14844
|
}
|
|
@@ -14852,8 +14852,8 @@ function countFiles(dir) {
|
|
|
14852
14852
|
function syncData() {
|
|
14853
14853
|
if (!dirExists(DATA_ROOT2))
|
|
14854
14854
|
mkdirSync3(DATA_ROOT2, { recursive: true });
|
|
14855
|
-
const payloadsPath =
|
|
14856
|
-
const templatesPath =
|
|
14855
|
+
const payloadsPath = join12(DATA_ROOT2, "payloads");
|
|
14856
|
+
const templatesPath = join12(DATA_ROOT2, "templates");
|
|
14857
14857
|
const hasPayloads = dirExists(payloadsPath) && countFiles(payloadsPath) > 0;
|
|
14858
14858
|
const hasTemplates = dirExists(templatesPath) && countFiles(templatesPath) > 0;
|
|
14859
14859
|
if (hasPayloads && hasTemplates) {
|
|
@@ -14863,8 +14863,8 @@ function syncData() {
|
|
|
14863
14863
|
data_root: DATA_ROOT2
|
|
14864
14864
|
};
|
|
14865
14865
|
}
|
|
14866
|
-
const localPayloads =
|
|
14867
|
-
const localTemplates =
|
|
14866
|
+
const localPayloads = join12(ROOT6, "payloads");
|
|
14867
|
+
const localTemplates = join12(ROOT6, "templates");
|
|
14868
14868
|
let payloadsSynced = false;
|
|
14869
14869
|
let templatesSynced = false;
|
|
14870
14870
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14877,7 +14877,7 @@ function syncData() {
|
|
|
14877
14877
|
}
|
|
14878
14878
|
const gitOk = spawnSync2("git", ["--version"]).status === 0;
|
|
14879
14879
|
if (gitOk && (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0 || !dirExists(templatesPath) || countFiles(templatesPath) === 0)) {
|
|
14880
|
-
const tmp =
|
|
14880
|
+
const tmp = join12(DATA_ROOT2, ".tmp-clone");
|
|
14881
14881
|
if (dirExists(tmp))
|
|
14882
14882
|
rmSync3(tmp, { recursive: true, force: true });
|
|
14883
14883
|
const r = spawnSync2("git", ["clone", "--depth", "1", "--filter=blob:none", "--sparse", GITHUB_REPO, tmp], { timeout: 120000 });
|
|
@@ -14885,11 +14885,11 @@ function syncData() {
|
|
|
14885
14885
|
const sparse = spawnSync2("git", ["-C", tmp, "sparse-checkout", "set", "payloads", "templates"], { timeout: 120000 });
|
|
14886
14886
|
if (sparse.status === 0) {
|
|
14887
14887
|
if (!dirExists(payloadsPath) || countFiles(payloadsPath) === 0) {
|
|
14888
|
-
copyDir(
|
|
14888
|
+
copyDir(join12(tmp, "payloads"), payloadsPath);
|
|
14889
14889
|
payloadsSynced = true;
|
|
14890
14890
|
}
|
|
14891
14891
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14892
|
+
copyDir(join12(tmp, "templates"), templatesPath);
|
|
14893
14893
|
templatesSynced = true;
|
|
14894
14894
|
}
|
|
14895
14895
|
}
|
|
@@ -14911,18 +14911,18 @@ function copyDir(src, dst) {
|
|
|
14911
14911
|
function manualCopy(src, dst) {
|
|
14912
14912
|
mkdirSync3(dst, { recursive: true });
|
|
14913
14913
|
for (const e of readdirSync5(src, { withFileTypes: true })) {
|
|
14914
|
-
const s =
|
|
14915
|
-
const d =
|
|
14914
|
+
const s = join12(src, e.name);
|
|
14915
|
+
const d = join12(dst, e.name);
|
|
14916
14916
|
if (e.isDirectory())
|
|
14917
14917
|
manualCopy(s, d);
|
|
14918
14918
|
else
|
|
14919
|
-
writeFileSync4(d,
|
|
14919
|
+
writeFileSync4(d, readFileSync16(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
|
-
var
|
|
14922
|
+
var ROOT6, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14924
|
+
ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
14925
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join12(homedir4(), ".blitzstrike", "data");
|
|
14926
14926
|
});
|
|
14927
14927
|
|
|
14928
14928
|
// src/update.ts
|
|
@@ -29601,6 +29601,7 @@ class StdioServerTransport {
|
|
|
29601
29601
|
|
|
29602
29602
|
// src/server.ts
|
|
29603
29603
|
init_scanner();
|
|
29604
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
29604
29605
|
|
|
29605
29606
|
// src/recon.ts
|
|
29606
29607
|
var TIMEOUT_MS = 25000;
|
|
@@ -31777,6 +31778,30 @@ function resolveCall(g, call, callerId, ctx, file, line) {
|
|
|
31777
31778
|
return;
|
|
31778
31779
|
}
|
|
31779
31780
|
}
|
|
31781
|
+
function reachableFrom(g, entry) {
|
|
31782
|
+
const seen = new Set;
|
|
31783
|
+
const stack = [entry];
|
|
31784
|
+
while (stack.length) {
|
|
31785
|
+
const cur = stack.pop();
|
|
31786
|
+
if (seen.has(cur))
|
|
31787
|
+
continue;
|
|
31788
|
+
seen.add(cur);
|
|
31789
|
+
const n = g.nodes.get(cur);
|
|
31790
|
+
if (!n)
|
|
31791
|
+
continue;
|
|
31792
|
+
for (const c of n.callees)
|
|
31793
|
+
stack.push(c);
|
|
31794
|
+
}
|
|
31795
|
+
return seen;
|
|
31796
|
+
}
|
|
31797
|
+
function entryPoints(g) {
|
|
31798
|
+
const entries = [];
|
|
31799
|
+
for (const [id, n] of g.nodes) {
|
|
31800
|
+
if (n.callers.size === 0)
|
|
31801
|
+
entries.push(id);
|
|
31802
|
+
}
|
|
31803
|
+
return entries;
|
|
31804
|
+
}
|
|
31780
31805
|
|
|
31781
31806
|
// src/eagle2.ts
|
|
31782
31807
|
var parser3 = new import_php_parser3.Engine({
|
|
@@ -32206,11 +32231,17 @@ function resolveFuncId(name, summaries) {
|
|
|
32206
32231
|
}
|
|
32207
32232
|
function analyzeDataFlow2(path) {
|
|
32208
32233
|
const cg = buildCallGraph(path);
|
|
32234
|
+
const entries = entryPoints(cg);
|
|
32235
|
+
const reachable = new Set;
|
|
32236
|
+
for (const e of entries)
|
|
32237
|
+
for (const n of reachableFrom(cg, e))
|
|
32238
|
+
reachable.add(n);
|
|
32239
|
+
const unreachable = [...cg.nodes.keys()].filter((id) => !reachable.has(id));
|
|
32209
32240
|
const result = {
|
|
32210
32241
|
file: path,
|
|
32211
32242
|
findings: [],
|
|
32212
32243
|
suppressed: 0,
|
|
32213
|
-
summary: { functions: 0, methods: 0, edges: cg.edges.length,
|
|
32244
|
+
summary: { functions: 0, methods: 0, edges: cg.edges.length, entry_points: entries, unreachable }
|
|
32214
32245
|
};
|
|
32215
32246
|
let code;
|
|
32216
32247
|
try {
|
|
@@ -50732,7 +50763,9 @@ function runEngine(code, language, file) {
|
|
|
50732
50763
|
rmSync(dir, { recursive: true, force: true });
|
|
50733
50764
|
}
|
|
50734
50765
|
}
|
|
50735
|
-
const
|
|
50766
|
+
const extMap = { javascript: ".js", typescript: ".ts", python: ".py", java: ".java" };
|
|
50767
|
+
const probe = file.includes(".") ? file : `target${extMap[language] ?? ""}`;
|
|
50768
|
+
const r = analyzeTaintUniversal(code, probe);
|
|
50736
50769
|
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
|
|
50737
50770
|
}
|
|
50738
50771
|
function diffLines(oldText, newText) {
|
|
@@ -50905,6 +50938,98 @@ function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
|
50905
50938
|
return { repo, base, head, files, total_changes, security_sensitive };
|
|
50906
50939
|
}
|
|
50907
50940
|
|
|
50941
|
+
// src/frameworks.ts
|
|
50942
|
+
import { readFileSync as readFileSync14, existsSync as existsSync6 } from "node:fs";
|
|
50943
|
+
import { join as join10 } from "node:path";
|
|
50944
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
50945
|
+
var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
50946
|
+
var INTEL2 = join10(ROOT4, "intelligence");
|
|
50947
|
+
var cache = null;
|
|
50948
|
+
function load() {
|
|
50949
|
+
if (cache)
|
|
50950
|
+
return cache;
|
|
50951
|
+
const p = join10(INTEL2, "frameworks.json");
|
|
50952
|
+
if (!existsSync6(p))
|
|
50953
|
+
return null;
|
|
50954
|
+
try {
|
|
50955
|
+
cache = JSON.parse(readFileSync14(p, "utf8"));
|
|
50956
|
+
return cache;
|
|
50957
|
+
} catch {
|
|
50958
|
+
return null;
|
|
50959
|
+
}
|
|
50960
|
+
}
|
|
50961
|
+
function listFrameworks() {
|
|
50962
|
+
const data = load();
|
|
50963
|
+
if (!data)
|
|
50964
|
+
return [];
|
|
50965
|
+
return data.frameworks.map((f) => ({ id: f.id, name: f.name, language: f.language }));
|
|
50966
|
+
}
|
|
50967
|
+
function frameworkProfile(id) {
|
|
50968
|
+
const data = load();
|
|
50969
|
+
if (!data)
|
|
50970
|
+
return null;
|
|
50971
|
+
return data.frameworks.find((f) => f.id === id) ?? null;
|
|
50972
|
+
}
|
|
50973
|
+
function detectFramework(code, language) {
|
|
50974
|
+
const data = load();
|
|
50975
|
+
const hay = code.toLowerCase();
|
|
50976
|
+
const candidates = [];
|
|
50977
|
+
if (data) {
|
|
50978
|
+
for (const f of data.frameworks) {
|
|
50979
|
+
if (f.language !== language)
|
|
50980
|
+
continue;
|
|
50981
|
+
const score = f.detect.reduce((n, sig) => hay.includes(sig.toLowerCase()) ? n + 1 : n, 0);
|
|
50982
|
+
if (score > 0)
|
|
50983
|
+
candidates.push({ id: f.id, name: f.name, score });
|
|
50984
|
+
}
|
|
50985
|
+
}
|
|
50986
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
50987
|
+
return { framework: candidates[0]?.id ?? null, language, candidates };
|
|
50988
|
+
}
|
|
50989
|
+
function scanFramework(code, frameworkId) {
|
|
50990
|
+
const profile = frameworkProfile(frameworkId);
|
|
50991
|
+
const empty = {
|
|
50992
|
+
framework: frameworkId,
|
|
50993
|
+
language: profile?.language ?? "unknown",
|
|
50994
|
+
detected: false,
|
|
50995
|
+
sources: [],
|
|
50996
|
+
sinks: [],
|
|
50997
|
+
sanitizers: [],
|
|
50998
|
+
auth: [],
|
|
50999
|
+
summary: { sources: 0, sinks: 0, sanitizers: 0, auth: 0 }
|
|
51000
|
+
};
|
|
51001
|
+
if (!profile)
|
|
51002
|
+
return empty;
|
|
51003
|
+
const lines = code.split(`
|
|
51004
|
+
`);
|
|
51005
|
+
const scan = (patterns) => {
|
|
51006
|
+
const hits = [];
|
|
51007
|
+
for (let i = 0;i < lines.length; i++) {
|
|
51008
|
+
const line = lines[i].toLowerCase();
|
|
51009
|
+
for (const p of patterns) {
|
|
51010
|
+
if (line.includes(p.pattern.toLowerCase())) {
|
|
51011
|
+
hits.push({ line: i + 1, pattern: p.pattern, label: p.label, cwe: p.cwe });
|
|
51012
|
+
}
|
|
51013
|
+
}
|
|
51014
|
+
}
|
|
51015
|
+
return hits;
|
|
51016
|
+
};
|
|
51017
|
+
const sources = scan(profile.sources);
|
|
51018
|
+
const sinks = scan(profile.sinks);
|
|
51019
|
+
const sanitizers = scan(profile.sanitizers);
|
|
51020
|
+
const auth = scan(profile.auth);
|
|
51021
|
+
return {
|
|
51022
|
+
framework: frameworkId,
|
|
51023
|
+
language: profile.language,
|
|
51024
|
+
detected: true,
|
|
51025
|
+
sources,
|
|
51026
|
+
sinks,
|
|
51027
|
+
sanitizers,
|
|
51028
|
+
auth,
|
|
51029
|
+
summary: { sources: sources.length, sinks: sinks.length, sanitizers: sanitizers.length, auth: auth.length }
|
|
51030
|
+
};
|
|
51031
|
+
}
|
|
51032
|
+
|
|
50908
51033
|
// src/guidance.ts
|
|
50909
51034
|
var SINK_DIRECTIVES = {
|
|
50910
51035
|
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.",
|
|
@@ -51718,16 +51843,16 @@ function reportJson(findings, opts = {}) {
|
|
|
51718
51843
|
}
|
|
51719
51844
|
|
|
51720
51845
|
// src/benchmark.ts
|
|
51721
|
-
import { readFileSync as
|
|
51722
|
-
import { join as
|
|
51723
|
-
import { fileURLToPath as
|
|
51846
|
+
import { readFileSync as readFileSync15, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
51847
|
+
import { join as join11 } from "node:path";
|
|
51848
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
51724
51849
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
51725
|
-
var
|
|
51726
|
-
var CORPUS =
|
|
51850
|
+
var ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
51851
|
+
var CORPUS = join11(ROOT5, "bench", "corpus");
|
|
51727
51852
|
function parseCorpusFile(path) {
|
|
51728
51853
|
let text;
|
|
51729
51854
|
try {
|
|
51730
|
-
text =
|
|
51855
|
+
text = readFileSync15(path, "utf8");
|
|
51731
51856
|
} catch {
|
|
51732
51857
|
return null;
|
|
51733
51858
|
}
|
|
@@ -51777,7 +51902,7 @@ function loadCorpus() {
|
|
|
51777
51902
|
for (const f of readdirSync4(CORPUS)) {
|
|
51778
51903
|
if (!f.endsWith(".md"))
|
|
51779
51904
|
continue;
|
|
51780
|
-
const e = parseCorpusFile(
|
|
51905
|
+
const e = parseCorpusFile(join11(CORPUS, f));
|
|
51781
51906
|
if (e)
|
|
51782
51907
|
entries.push(e);
|
|
51783
51908
|
}
|
|
@@ -51789,7 +51914,7 @@ function loadCorpus() {
|
|
|
51789
51914
|
function detect(entry) {
|
|
51790
51915
|
const lang = entry.language;
|
|
51791
51916
|
if (lang === "php") {
|
|
51792
|
-
const tmp =
|
|
51917
|
+
const tmp = join11(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
51793
51918
|
writeFileSync3(tmp, entry.code);
|
|
51794
51919
|
const r = analyzeDataFlow2(tmp);
|
|
51795
51920
|
rmSync2(tmp, { force: true });
|
|
@@ -52601,6 +52726,8 @@ function createServer() {
|
|
|
52601
52726
|
functions: r.summary.functions,
|
|
52602
52727
|
methods: r.summary.methods,
|
|
52603
52728
|
call_edges: r.summary.edges,
|
|
52729
|
+
entry_points: r.summary.entry_points,
|
|
52730
|
+
unreachable: r.summary.unreachable,
|
|
52604
52731
|
results: r.findings,
|
|
52605
52732
|
next_steps: guidance
|
|
52606
52733
|
})
|
|
@@ -52635,6 +52762,62 @@ function createServer() {
|
|
|
52635
52762
|
}
|
|
52636
52763
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
52637
52764
|
});
|
|
52765
|
+
server.registerTool("list_frameworks", {
|
|
52766
|
+
title: "List supported frameworks",
|
|
52767
|
+
description: "PHASE 5: list the web frameworks Blitz Strike understands for framework-aware security analysis (Laravel, WordPress, Express, Next.js, Django, FastAPI, Spring)."
|
|
52768
|
+
}, async () => {
|
|
52769
|
+
return { content: [{ type: "text", text: JSON.stringify({ frameworks: listFrameworks() }) }] };
|
|
52770
|
+
});
|
|
52771
|
+
server.registerTool("detect_framework", {
|
|
52772
|
+
title: "Detect web framework",
|
|
52773
|
+
description: "PHASE 5: detect the web framework used by a source file (reads the file, then scores each framework's detection signals for the file's language).",
|
|
52774
|
+
inputSchema: {
|
|
52775
|
+
path: string2().optional().describe("Source file path (alternative: code)"),
|
|
52776
|
+
code: string2().optional().describe("Source code directly (alternative to path)"),
|
|
52777
|
+
language: string2().optional().describe("Language (auto-detected from path if omitted)")
|
|
52778
|
+
}
|
|
52779
|
+
}, async ({ path, code, language }) => {
|
|
52780
|
+
let src = code ?? "";
|
|
52781
|
+
let lang = language;
|
|
52782
|
+
if (path && !code) {
|
|
52783
|
+
try {
|
|
52784
|
+
src = readFileSync17(path, "utf8");
|
|
52785
|
+
} catch {
|
|
52786
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `cannot read ${path}` }) }] };
|
|
52787
|
+
}
|
|
52788
|
+
lang = lang ?? detectLanguage(path)?.language ?? "php";
|
|
52789
|
+
}
|
|
52790
|
+
const d = detectFramework(src, lang ?? "php");
|
|
52791
|
+
const scan = d.framework ? scanFramework(src, d.framework) : null;
|
|
52792
|
+
return {
|
|
52793
|
+
content: [{
|
|
52794
|
+
type: "text",
|
|
52795
|
+
text: JSON.stringify({
|
|
52796
|
+
framework: d.framework,
|
|
52797
|
+
language: d.language,
|
|
52798
|
+
candidates: d.candidates,
|
|
52799
|
+
scan
|
|
52800
|
+
})
|
|
52801
|
+
}]
|
|
52802
|
+
};
|
|
52803
|
+
});
|
|
52804
|
+
server.registerTool("framework_intel", {
|
|
52805
|
+
title: "Framework security knowledge",
|
|
52806
|
+
description: "PHASE 5: return a framework's security knowledge (sources, sinks, sanitizers, auth gates, routing, ORM, middleware) and optionally scan code with those patterns.",
|
|
52807
|
+
inputSchema: {
|
|
52808
|
+
framework: string2().describe("Framework id (see list_frameworks)"),
|
|
52809
|
+
code: string2().optional().describe("Optional source code to scan with the framework's patterns")
|
|
52810
|
+
}
|
|
52811
|
+
}, async ({ framework, code }) => {
|
|
52812
|
+
const profile = frameworkProfile(framework);
|
|
52813
|
+
if (!profile) {
|
|
52814
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `unknown framework: ${framework}` }) }] };
|
|
52815
|
+
}
|
|
52816
|
+
const out = { profile };
|
|
52817
|
+
if (code !== undefined)
|
|
52818
|
+
out.scan = scanFramework(code, framework);
|
|
52819
|
+
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
52820
|
+
});
|
|
52638
52821
|
server.registerTool("taint_tree", {
|
|
52639
52822
|
title: "Whole-tree taint scan",
|
|
52640
52823
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
|
@@ -52788,12 +52971,12 @@ async function serve() {
|
|
|
52788
52971
|
}
|
|
52789
52972
|
|
|
52790
52973
|
// src/cli.ts
|
|
52791
|
-
import { readFileSync as
|
|
52792
|
-
import { join as
|
|
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";
|
|
52793
52976
|
import { homedir as homedir5 } from "node:os";
|
|
52794
|
-
import { fileURLToPath as
|
|
52977
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52795
52978
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
52796
|
-
var
|
|
52979
|
+
var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
52797
52980
|
function which(cmd) {
|
|
52798
52981
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
52799
52982
|
}
|
|
@@ -52828,7 +53011,7 @@ function runDoctor() {
|
|
|
52828
53011
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
52829
53012
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
52830
53013
|
});
|
|
52831
|
-
const dataOk =
|
|
53014
|
+
const dataOk = existsSync7(join13(ROOT7, "chains.json")) && existsSync7(join13(ROOT7, "tools-catalog.json"));
|
|
52832
53015
|
issues.push({
|
|
52833
53016
|
name: "Data layers (chains + tools-catalog)",
|
|
52834
53017
|
status: dataOk ? "ok" : "fail",
|
|
@@ -52856,21 +53039,21 @@ BlitzStrike — ${verdict}`);
|
|
|
52856
53039
|
function resolveCommand() {
|
|
52857
53040
|
if (which("blitzstrike"))
|
|
52858
53041
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
52859
|
-
const src =
|
|
52860
|
-
if (which("bun") &&
|
|
53042
|
+
const src = join13(ROOT7, "src", "index.ts");
|
|
53043
|
+
if (which("bun") && existsSync7(src))
|
|
52861
53044
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
52862
53045
|
if (which("npx"))
|
|
52863
53046
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52864
53047
|
if (which("bunx"))
|
|
52865
53048
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
52866
|
-
const dist =
|
|
52867
|
-
if (
|
|
53049
|
+
const dist = join13(ROOT7, "dist", "index.js");
|
|
53050
|
+
if (existsSync7(dist))
|
|
52868
53051
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
52869
53052
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52870
53053
|
}
|
|
52871
53054
|
function readJson(p) {
|
|
52872
53055
|
try {
|
|
52873
|
-
return JSON.parse(
|
|
53056
|
+
return JSON.parse(readFileSync18(p, "utf8"));
|
|
52874
53057
|
} catch {
|
|
52875
53058
|
return null;
|
|
52876
53059
|
}
|
|
@@ -52878,7 +53061,7 @@ function readJson(p) {
|
|
|
52878
53061
|
function jsonMcpServersWrite(p) {
|
|
52879
53062
|
return (command, args) => {
|
|
52880
53063
|
const dir = dirname2(p);
|
|
52881
|
-
if (!
|
|
53064
|
+
if (!existsSync7(dir))
|
|
52882
53065
|
mkdirSync4(dir, { recursive: true });
|
|
52883
53066
|
const existing = readJson(p) ?? {};
|
|
52884
53067
|
existing.mcpServers = {
|
|
@@ -52891,7 +53074,7 @@ function jsonMcpServersWrite(p) {
|
|
|
52891
53074
|
function opencodeWrite(p) {
|
|
52892
53075
|
return (_command, _args) => {
|
|
52893
53076
|
const dir = dirname2(p);
|
|
52894
|
-
if (!
|
|
53077
|
+
if (!existsSync7(dir))
|
|
52895
53078
|
mkdirSync4(dir, { recursive: true });
|
|
52896
53079
|
const existing = readJson(p) ?? {};
|
|
52897
53080
|
existing.mcp = {
|
|
@@ -52903,26 +53086,26 @@ function opencodeWrite(p) {
|
|
|
52903
53086
|
};
|
|
52904
53087
|
}
|
|
52905
53088
|
function copyOpenCodeAgents() {
|
|
52906
|
-
const srcDir =
|
|
52907
|
-
const dstDir =
|
|
52908
|
-
if (!
|
|
53089
|
+
const srcDir = join13(ROOT7, "opencode-agents");
|
|
53090
|
+
const dstDir = join13(homedir5(), ".config", "opencode", "agents");
|
|
53091
|
+
if (!existsSync7(srcDir))
|
|
52909
53092
|
return;
|
|
52910
|
-
if (!
|
|
53093
|
+
if (!existsSync7(dstDir))
|
|
52911
53094
|
mkdirSync4(dstDir, { recursive: true });
|
|
52912
53095
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
52913
|
-
const src =
|
|
52914
|
-
const dst =
|
|
52915
|
-
if (
|
|
52916
|
-
writeFileSync5(dst,
|
|
53096
|
+
const src = join13(srcDir, f);
|
|
53097
|
+
const dst = join13(dstDir, f);
|
|
53098
|
+
if (existsSync7(src)) {
|
|
53099
|
+
writeFileSync5(dst, readFileSync18(src, "utf8"));
|
|
52917
53100
|
}
|
|
52918
53101
|
}
|
|
52919
53102
|
}
|
|
52920
53103
|
function codexWrite(p) {
|
|
52921
53104
|
return (command, args) => {
|
|
52922
53105
|
const dir = dirname2(p);
|
|
52923
|
-
if (!
|
|
53106
|
+
if (!existsSync7(dir))
|
|
52924
53107
|
mkdirSync4(dir, { recursive: true });
|
|
52925
|
-
let existing =
|
|
53108
|
+
let existing = existsSync7(p) ? readFileSync18(p, "utf8") : "";
|
|
52926
53109
|
if (!existing.trimEnd().endsWith(`
|
|
52927
53110
|
`))
|
|
52928
53111
|
existing += `
|
|
@@ -52939,9 +53122,9 @@ args = ${JSON.stringify(args)}
|
|
|
52939
53122
|
function hermesWrite(p) {
|
|
52940
53123
|
return (command, args) => {
|
|
52941
53124
|
const dir = dirname2(p);
|
|
52942
|
-
if (!
|
|
53125
|
+
if (!existsSync7(dir))
|
|
52943
53126
|
mkdirSync4(dir, { recursive: true });
|
|
52944
|
-
let existing =
|
|
53127
|
+
let existing = existsSync7(p) ? readFileSync18(p, "utf8") : "";
|
|
52945
53128
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
52946
53129
|
if (!existing.trimEnd().endsWith(`
|
|
52947
53130
|
`))
|
|
@@ -52968,74 +53151,74 @@ mcp_servers:
|
|
|
52968
53151
|
function detectAgents() {
|
|
52969
53152
|
const home = homedir5();
|
|
52970
53153
|
const agents = [];
|
|
52971
|
-
const claudeJson =
|
|
53154
|
+
const claudeJson = join13(home, ".claude.json");
|
|
52972
53155
|
agents.push({
|
|
52973
53156
|
name: "Claude Code",
|
|
52974
53157
|
detectPath: claudeJson,
|
|
52975
|
-
installed: () =>
|
|
53158
|
+
installed: () => existsSync7(claudeJson) || which("claude"),
|
|
52976
53159
|
write: jsonMcpServersWrite(claudeJson)
|
|
52977
53160
|
});
|
|
52978
|
-
const claudeDesktop =
|
|
53161
|
+
const claudeDesktop = join13(home, ".config", "Claude", "claude_desktop_config.json");
|
|
52979
53162
|
agents.push({
|
|
52980
53163
|
name: "Claude Desktop",
|
|
52981
53164
|
detectPath: claudeDesktop,
|
|
52982
|
-
installed: () =>
|
|
53165
|
+
installed: () => existsSync7(claudeDesktop),
|
|
52983
53166
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
52984
53167
|
});
|
|
52985
|
-
const cursor =
|
|
53168
|
+
const cursor = join13(home, ".cursor", "mcp.json");
|
|
52986
53169
|
agents.push({
|
|
52987
53170
|
name: "Cursor",
|
|
52988
53171
|
detectPath: cursor,
|
|
52989
|
-
installed: () =>
|
|
53172
|
+
installed: () => existsSync7(join13(home, ".cursor")) || which("cursor"),
|
|
52990
53173
|
write: jsonMcpServersWrite(cursor)
|
|
52991
53174
|
});
|
|
52992
|
-
const opencode =
|
|
53175
|
+
const opencode = join13(home, ".config", "opencode", "opencode.jsonc");
|
|
52993
53176
|
agents.push({
|
|
52994
53177
|
name: "OpenCode",
|
|
52995
53178
|
detectPath: opencode,
|
|
52996
|
-
installed: () =>
|
|
53179
|
+
installed: () => existsSync7(join13(home, ".config", "opencode")) || which("opencode"),
|
|
52997
53180
|
write: opencodeWrite(opencode)
|
|
52998
53181
|
});
|
|
52999
|
-
const codex =
|
|
53182
|
+
const codex = join13(home, ".codex", "config.toml");
|
|
53000
53183
|
agents.push({
|
|
53001
53184
|
name: "Codex",
|
|
53002
53185
|
detectPath: codex,
|
|
53003
|
-
installed: () =>
|
|
53186
|
+
installed: () => existsSync7(join13(home, ".codex")) || which("codex"),
|
|
53004
53187
|
write: codexWrite(codex)
|
|
53005
53188
|
});
|
|
53006
|
-
const hermes =
|
|
53189
|
+
const hermes = join13(home, ".hermes", "config.yaml");
|
|
53007
53190
|
agents.push({
|
|
53008
53191
|
name: "Hermes",
|
|
53009
53192
|
detectPath: hermes,
|
|
53010
|
-
installed: () =>
|
|
53193
|
+
installed: () => existsSync7(join13(home, ".hermes")) || which("hermes"),
|
|
53011
53194
|
write: hermesWrite(hermes)
|
|
53012
53195
|
});
|
|
53013
|
-
const gemini =
|
|
53196
|
+
const gemini = join13(home, ".gemini", "settings.json");
|
|
53014
53197
|
agents.push({
|
|
53015
53198
|
name: "Gemini CLI",
|
|
53016
53199
|
detectPath: gemini,
|
|
53017
|
-
installed: () =>
|
|
53200
|
+
installed: () => existsSync7(join13(home, ".gemini")) || which("gemini"),
|
|
53018
53201
|
write: jsonMcpServersWrite(gemini)
|
|
53019
53202
|
});
|
|
53020
|
-
const windsurf =
|
|
53203
|
+
const windsurf = join13(home, ".codeium", "windsurf", "mcp_config.json");
|
|
53021
53204
|
agents.push({
|
|
53022
53205
|
name: "Windsurf",
|
|
53023
53206
|
detectPath: windsurf,
|
|
53024
|
-
installed: () =>
|
|
53207
|
+
installed: () => existsSync7(join13(home, ".codeium")) || which("windsurf"),
|
|
53025
53208
|
write: jsonMcpServersWrite(windsurf)
|
|
53026
53209
|
});
|
|
53027
|
-
const copilot =
|
|
53210
|
+
const copilot = join13(home, ".copilot", "mcp.json");
|
|
53028
53211
|
agents.push({
|
|
53029
53212
|
name: "Copilot",
|
|
53030
53213
|
detectPath: copilot,
|
|
53031
|
-
installed: () =>
|
|
53214
|
+
installed: () => existsSync7(join13(home, ".copilot")),
|
|
53032
53215
|
write: jsonMcpServersWrite(copilot)
|
|
53033
53216
|
});
|
|
53034
|
-
const cline =
|
|
53217
|
+
const cline = join13(home, ".cline", "mcp_settings.json");
|
|
53035
53218
|
agents.push({
|
|
53036
53219
|
name: "Cline",
|
|
53037
53220
|
detectPath: cline,
|
|
53038
|
-
installed: () =>
|
|
53221
|
+
installed: () => existsSync7(join13(home, ".cline")),
|
|
53039
53222
|
write: jsonMcpServersWrite(cline)
|
|
53040
53223
|
});
|
|
53041
53224
|
return agents;
|
|
@@ -53079,15 +53262,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
53079
53262
|
}
|
|
53080
53263
|
|
|
53081
53264
|
// src/index.ts
|
|
53082
|
-
import { readFileSync as
|
|
53083
|
-
import { join as
|
|
53084
|
-
import { fileURLToPath as
|
|
53085
|
-
var
|
|
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)), "..");
|
|
53086
53269
|
function readVersion() {
|
|
53087
53270
|
try {
|
|
53088
|
-
const p =
|
|
53089
|
-
if (
|
|
53090
|
-
return JSON.parse(
|
|
53271
|
+
const p = join14(ROOT8, "package.json");
|
|
53272
|
+
if (existsSync8(p))
|
|
53273
|
+
return JSON.parse(readFileSync19(p, "utf8")).version ?? "1.0.0";
|
|
53091
53274
|
} catch {}
|
|
53092
53275
|
return "1.0.0";
|
|
53093
53276
|
}
|