blitzstrike 1.0.36 → 1.0.37
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 +15 -0
- package/dist/index.js +289 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.37] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Phase 4 — Differential security analysis** (`src/differential.ts`):
|
|
13
|
+
- `analyzeDifferential(old, new, language)` — compares two code versions and
|
|
14
|
+
surfaces only security-sensitive changes:
|
|
15
|
+
- **new vulnerability** (source→sink path present only in new code — authoritative, engine-backed)
|
|
16
|
+
- **fixed vulnerability** (path present only in old code)
|
|
17
|
+
- **new sink** / **new source** (line-level hints)
|
|
18
|
+
- **sanitizer removed** / **authorization removed** (high-severity signals)
|
|
19
|
+
- `analyzeDifferentialFiles(old_path, new_path)` — file-based comparison.
|
|
20
|
+
- `analyzeGitDiff(repo, base, head)` — runs across a git commit range.
|
|
21
|
+
- New MCP tool `diff_analyze` (supports file-pair, code-pair, or git-diff mode).
|
|
22
|
+
|
|
8
23
|
## [1.0.36] — 2026-09-13
|
|
9
24
|
|
|
10
25
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -14820,8 +14820,8 @@ var require_src = __commonJS(function(exports, module) {
|
|
|
14820
14820
|
});
|
|
14821
14821
|
|
|
14822
14822
|
// src/sync.ts
|
|
14823
|
-
import { mkdirSync as mkdirSync3, writeFileSync as
|
|
14824
|
-
import { join as
|
|
14823
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync3, rmSync as rmSync3 } from "node:fs";
|
|
14824
|
+
import { join as join11 } from "node:path";
|
|
14825
14825
|
import { homedir as homedir4 } from "node:os";
|
|
14826
14826
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
14827
14827
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -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(join11(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 = join11(DATA_ROOT2, "payloads");
|
|
14856
|
+
const templatesPath = join11(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 = join11(ROOT5, "payloads");
|
|
14867
|
+
const localTemplates = join11(ROOT5, "templates");
|
|
14868
14868
|
let payloadsSynced = false;
|
|
14869
14869
|
let templatesSynced = false;
|
|
14870
14870
|
if (!hasPayloads && dirExists(localPayloads)) {
|
|
@@ -14877,23 +14877,23 @@ 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 = join11(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 });
|
|
14884
14884
|
if (r.status === 0) {
|
|
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(join11(tmp, "payloads"), payloadsPath);
|
|
14889
14889
|
payloadsSynced = true;
|
|
14890
14890
|
}
|
|
14891
14891
|
if (!dirExists(templatesPath) || countFiles(templatesPath) === 0) {
|
|
14892
|
-
copyDir(
|
|
14892
|
+
copyDir(join11(tmp, "templates"), templatesPath);
|
|
14893
14893
|
templatesSynced = true;
|
|
14894
14894
|
}
|
|
14895
14895
|
}
|
|
14896
|
-
|
|
14896
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
14897
14897
|
}
|
|
14898
14898
|
}
|
|
14899
14899
|
return {
|
|
@@ -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 = join11(src, e.name);
|
|
14915
|
+
const d = join11(dst, e.name);
|
|
14916
14916
|
if (e.isDirectory())
|
|
14917
14917
|
manualCopy(s, d);
|
|
14918
14918
|
else
|
|
14919
|
-
|
|
14919
|
+
writeFileSync4(d, readFileSync15(s));
|
|
14920
14920
|
}
|
|
14921
14921
|
}
|
|
14922
14922
|
var ROOT5, DATA_ROOT2, GITHUB_REPO = "https://github.com/shinthink/blitzstrike.git";
|
|
14923
14923
|
var init_sync = __esm(() => {
|
|
14924
|
-
ROOT5 =
|
|
14925
|
-
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ??
|
|
14924
|
+
ROOT5 = join11(fileURLToPath7(new URL(".", import.meta.url)), "..");
|
|
14925
|
+
DATA_ROOT2 = process.env.BLITZSTRIKE_DATA ?? join11(homedir4(), ".blitzstrike", "data");
|
|
14926
14926
|
});
|
|
14927
14927
|
|
|
14928
14928
|
// src/update.ts
|
|
@@ -32326,6 +32326,12 @@ function analyzeDataFlow2(path) {
|
|
|
32326
32326
|
return result;
|
|
32327
32327
|
}
|
|
32328
32328
|
|
|
32329
|
+
// src/differential.ts
|
|
32330
|
+
import { writeFileSync as writeFileSync2, rmSync, mkdtempSync, readFileSync as readFileSync13 } from "node:fs";
|
|
32331
|
+
import { join as join9 } from "node:path";
|
|
32332
|
+
import { tmpdir } from "node:os";
|
|
32333
|
+
import { execSync } from "node:child_process";
|
|
32334
|
+
|
|
32329
32335
|
// src/universal-taint.ts
|
|
32330
32336
|
function runTaintEngine(adapter, code, file) {
|
|
32331
32337
|
const result = { file, language: adapter.language, findings: [], suppressed: 0 };
|
|
@@ -50712,6 +50718,193 @@ registerLanguage(jsAdapter);
|
|
|
50712
50718
|
registerLanguage(pythonAdapter);
|
|
50713
50719
|
registerLanguage(javaAdapter);
|
|
50714
50720
|
|
|
50721
|
+
// src/differential.ts
|
|
50722
|
+
init_scanner();
|
|
50723
|
+
function runEngine(code, language, file) {
|
|
50724
|
+
if (language === "php") {
|
|
50725
|
+
const dir = mkdtempSync(join9(tmpdir(), "blitz-diff-"));
|
|
50726
|
+
const tmp = join9(dir, "old.php");
|
|
50727
|
+
writeFileSync2(tmp, code);
|
|
50728
|
+
try {
|
|
50729
|
+
const r = analyzeDataFlow2(tmp);
|
|
50730
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.category, source: f.source, cwe: f.cwe }));
|
|
50731
|
+
} finally {
|
|
50732
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50733
|
+
}
|
|
50734
|
+
}
|
|
50735
|
+
const r = analyzeTaintUniversal(code, file);
|
|
50736
|
+
return r.findings.map((f) => ({ sink: f.sink, sink_line: f.sink_line, category: f.sink, source: f.source_kind, cwe: f.cwe }));
|
|
50737
|
+
}
|
|
50738
|
+
function diffLines(oldText, newText) {
|
|
50739
|
+
const a = oldText.split(`
|
|
50740
|
+
`);
|
|
50741
|
+
const b = newText.split(`
|
|
50742
|
+
`);
|
|
50743
|
+
const m = a.length;
|
|
50744
|
+
const n = b.length;
|
|
50745
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
50746
|
+
for (let i = m - 1;i >= 0; i--) {
|
|
50747
|
+
for (let j = n - 1;j >= 0; j--) {
|
|
50748
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
50749
|
+
}
|
|
50750
|
+
}
|
|
50751
|
+
const added = [];
|
|
50752
|
+
const removed = [];
|
|
50753
|
+
let i = 0;
|
|
50754
|
+
let j = 0;
|
|
50755
|
+
while (i < m && j < n) {
|
|
50756
|
+
if (a[i] === b[j]) {
|
|
50757
|
+
i++;
|
|
50758
|
+
j++;
|
|
50759
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
50760
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50761
|
+
i++;
|
|
50762
|
+
} else {
|
|
50763
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50764
|
+
j++;
|
|
50765
|
+
}
|
|
50766
|
+
}
|
|
50767
|
+
while (i < m) {
|
|
50768
|
+
removed.push({ line: i + 1, text: a[i] });
|
|
50769
|
+
i++;
|
|
50770
|
+
}
|
|
50771
|
+
while (j < n) {
|
|
50772
|
+
added.push({ line: j + 1, text: b[j] });
|
|
50773
|
+
j++;
|
|
50774
|
+
}
|
|
50775
|
+
return { added, removed };
|
|
50776
|
+
}
|
|
50777
|
+
function analyzeDifferential(oldCode, newCode, language, file = "target") {
|
|
50778
|
+
const changes = [];
|
|
50779
|
+
const oldFindings = runEngine(oldCode, language, file);
|
|
50780
|
+
const newFindings = runEngine(newCode, language, file);
|
|
50781
|
+
const key = (f) => `${f.category}|${f.sink}|${f.sink_line}`;
|
|
50782
|
+
const oldKeys = new Set(oldFindings.map(key));
|
|
50783
|
+
const newKeys = new Set(newFindings.map(key));
|
|
50784
|
+
for (const f of newFindings) {
|
|
50785
|
+
if (!oldKeys.has(key(f))) {
|
|
50786
|
+
changes.push({
|
|
50787
|
+
kind: "new_vulnerability",
|
|
50788
|
+
severity: "high",
|
|
50789
|
+
sink: f.sink,
|
|
50790
|
+
category: f.category,
|
|
50791
|
+
source: f.source,
|
|
50792
|
+
cwe: f.cwe,
|
|
50793
|
+
line: f.sink_line,
|
|
50794
|
+
detail: `new ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50795
|
+
});
|
|
50796
|
+
}
|
|
50797
|
+
}
|
|
50798
|
+
for (const f of oldFindings) {
|
|
50799
|
+
if (!newKeys.has(key(f))) {
|
|
50800
|
+
changes.push({
|
|
50801
|
+
kind: "fixed_vulnerability",
|
|
50802
|
+
severity: "low",
|
|
50803
|
+
sink: f.sink,
|
|
50804
|
+
category: f.category,
|
|
50805
|
+
source: f.source,
|
|
50806
|
+
cwe: f.cwe,
|
|
50807
|
+
line: f.sink_line,
|
|
50808
|
+
detail: `removed ${f.category} at line ${f.sink_line} (${f.sink.trim()})`
|
|
50809
|
+
});
|
|
50810
|
+
}
|
|
50811
|
+
}
|
|
50812
|
+
const { added, removed } = diffLines(oldCode, newCode);
|
|
50813
|
+
for (const l of added) {
|
|
50814
|
+
const sink = classifySink2(l.text);
|
|
50815
|
+
if (sink) {
|
|
50816
|
+
changes.push({
|
|
50817
|
+
kind: "new_sink",
|
|
50818
|
+
severity: "medium",
|
|
50819
|
+
sink: l.text.trim(),
|
|
50820
|
+
category: sink.category,
|
|
50821
|
+
cwe: sink.cwe,
|
|
50822
|
+
line: l.line,
|
|
50823
|
+
detail: `added sink ${l.text.trim()} (${sink.category})`
|
|
50824
|
+
});
|
|
50825
|
+
}
|
|
50826
|
+
const source = classifySource(l.text);
|
|
50827
|
+
if (source) {
|
|
50828
|
+
changes.push({
|
|
50829
|
+
kind: "new_source",
|
|
50830
|
+
severity: source.attacker_controlled ? "medium" : "low",
|
|
50831
|
+
source: source.label,
|
|
50832
|
+
line: l.line,
|
|
50833
|
+
detail: `added source ${l.text.trim()} (${source.label})`
|
|
50834
|
+
});
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
for (const l of removed) {
|
|
50838
|
+
const san = findSanitizers(l.text);
|
|
50839
|
+
if (san.length > 0) {
|
|
50840
|
+
changes.push({
|
|
50841
|
+
kind: "sanitizer_removed",
|
|
50842
|
+
severity: "high",
|
|
50843
|
+
line: l.line,
|
|
50844
|
+
detail: `removed sanitizer ${san.map((s) => s.label).join(", ")} (${l.text.trim()})`
|
|
50845
|
+
});
|
|
50846
|
+
}
|
|
50847
|
+
const gates = AUTH_GATES.filter((g) => l.text.includes(g));
|
|
50848
|
+
if (gates.length > 0) {
|
|
50849
|
+
changes.push({
|
|
50850
|
+
kind: "authorization_removed",
|
|
50851
|
+
severity: "high",
|
|
50852
|
+
line: l.line,
|
|
50853
|
+
detail: `removed authorization gate ${gates.join(", ")} (${l.text.trim()})`
|
|
50854
|
+
});
|
|
50855
|
+
}
|
|
50856
|
+
}
|
|
50857
|
+
const rank = { high: 0, medium: 1, low: 2 };
|
|
50858
|
+
changes.sort((x, y) => rank[x.severity] - rank[y.severity] || x.line - y.line);
|
|
50859
|
+
const count = (k) => changes.filter((c) => c.kind === k).length;
|
|
50860
|
+
return {
|
|
50861
|
+
language,
|
|
50862
|
+
changes,
|
|
50863
|
+
summary: {
|
|
50864
|
+
new_vulnerabilities: count("new_vulnerability"),
|
|
50865
|
+
fixed_vulnerabilities: count("fixed_vulnerability"),
|
|
50866
|
+
new_sinks: count("new_sink"),
|
|
50867
|
+
new_sources: count("new_source"),
|
|
50868
|
+
sanitizers_removed: count("sanitizer_removed"),
|
|
50869
|
+
authorizations_removed: count("authorization_removed")
|
|
50870
|
+
}
|
|
50871
|
+
};
|
|
50872
|
+
}
|
|
50873
|
+
function languageOf(file) {
|
|
50874
|
+
const adapter = detectLanguage(file);
|
|
50875
|
+
return adapter?.language ?? "php";
|
|
50876
|
+
}
|
|
50877
|
+
function analyzeDifferentialFiles(oldPath, newPath, language) {
|
|
50878
|
+
const oldCode = readFileSync13(oldPath, "utf8");
|
|
50879
|
+
const newCode = readFileSync13(newPath, "utf8");
|
|
50880
|
+
const lang = language ?? languageOf(newPath);
|
|
50881
|
+
return analyzeDifferential(oldCode, newCode, lang, newPath);
|
|
50882
|
+
}
|
|
50883
|
+
var SOURCE_EXTS = [".php", ".phtml", ".php5", ".js", ".mjs", ".ts", ".tsx", ".py", ".pyw", ".java"];
|
|
50884
|
+
function analyzeGitDiff(repo, base = "HEAD~1", head = "HEAD") {
|
|
50885
|
+
const git = (args) => {
|
|
50886
|
+
try {
|
|
50887
|
+
return execSync(`git ${args}`, { cwd: repo, encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim();
|
|
50888
|
+
} catch {
|
|
50889
|
+
return "";
|
|
50890
|
+
}
|
|
50891
|
+
};
|
|
50892
|
+
const changed = git(`diff --name-only ${base} ${head}`).split(`
|
|
50893
|
+
`).filter((f) => f && SOURCE_EXTS.some((e) => f.endsWith(e)));
|
|
50894
|
+
const files = [];
|
|
50895
|
+
for (const file of changed) {
|
|
50896
|
+
const oldCode = git(`show ${base}:${file}`);
|
|
50897
|
+
const newCode = git(`show ${head}:${file}`);
|
|
50898
|
+
if (!newCode)
|
|
50899
|
+
continue;
|
|
50900
|
+
const lang = languageOf(file);
|
|
50901
|
+
files.push({ file, result: analyzeDifferential(oldCode || "", newCode, lang, file) });
|
|
50902
|
+
}
|
|
50903
|
+
const total_changes = files.reduce((n, f) => n + f.result.changes.length, 0);
|
|
50904
|
+
const security_sensitive = files.reduce((n, f) => n + f.result.changes.filter((c) => c.severity === "high").length, 0);
|
|
50905
|
+
return { repo, base, head, files, total_changes, security_sensitive };
|
|
50906
|
+
}
|
|
50907
|
+
|
|
50715
50908
|
// src/guidance.ts
|
|
50716
50909
|
var SINK_DIRECTIVES = {
|
|
50717
50910
|
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.",
|
|
@@ -51525,16 +51718,16 @@ function reportJson(findings, opts = {}) {
|
|
|
51525
51718
|
}
|
|
51526
51719
|
|
|
51527
51720
|
// src/benchmark.ts
|
|
51528
|
-
import { readFileSync as
|
|
51529
|
-
import { join as
|
|
51721
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "node:fs";
|
|
51722
|
+
import { join as join10 } from "node:path";
|
|
51530
51723
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
51531
|
-
import { tmpdir } from "node:os";
|
|
51532
|
-
var ROOT4 =
|
|
51533
|
-
var CORPUS =
|
|
51724
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
51725
|
+
var ROOT4 = join10(fileURLToPath6(new URL(".", import.meta.url)), "..");
|
|
51726
|
+
var CORPUS = join10(ROOT4, "bench", "corpus");
|
|
51534
51727
|
function parseCorpusFile(path) {
|
|
51535
51728
|
let text;
|
|
51536
51729
|
try {
|
|
51537
|
-
text =
|
|
51730
|
+
text = readFileSync14(path, "utf8");
|
|
51538
51731
|
} catch {
|
|
51539
51732
|
return null;
|
|
51540
51733
|
}
|
|
@@ -51584,7 +51777,7 @@ function loadCorpus() {
|
|
|
51584
51777
|
for (const f of readdirSync4(CORPUS)) {
|
|
51585
51778
|
if (!f.endsWith(".md"))
|
|
51586
51779
|
continue;
|
|
51587
|
-
const e = parseCorpusFile(
|
|
51780
|
+
const e = parseCorpusFile(join10(CORPUS, f));
|
|
51588
51781
|
if (e)
|
|
51589
51782
|
entries.push(e);
|
|
51590
51783
|
}
|
|
@@ -51596,10 +51789,10 @@ function loadCorpus() {
|
|
|
51596
51789
|
function detect(entry) {
|
|
51597
51790
|
const lang = entry.language;
|
|
51598
51791
|
if (lang === "php") {
|
|
51599
|
-
const tmp =
|
|
51600
|
-
|
|
51792
|
+
const tmp = join10(tmpdir2(), `blitzstrike-bench-${entry.id}-${process.pid}.php`);
|
|
51793
|
+
writeFileSync3(tmp, entry.code);
|
|
51601
51794
|
const r = analyzeDataFlow2(tmp);
|
|
51602
|
-
|
|
51795
|
+
rmSync2(tmp, { force: true });
|
|
51603
51796
|
if (r.findings.length > 0)
|
|
51604
51797
|
return { detected: true, sinkKind: r.findings[0].sink };
|
|
51605
51798
|
return { detected: false };
|
|
@@ -52414,6 +52607,34 @@ function createServer() {
|
|
|
52414
52607
|
}]
|
|
52415
52608
|
};
|
|
52416
52609
|
});
|
|
52610
|
+
server.registerTool("diff_analyze", {
|
|
52611
|
+
title: "Differential security analysis",
|
|
52612
|
+
description: "PHASE 4: compare two versions of code (files or a git commit range) and surface only security-sensitive changes — new vulnerabilities, fixed vulnerabilities, new sinks/sources, removed sanitizers, and removed authorization gates.",
|
|
52613
|
+
inputSchema: {
|
|
52614
|
+
old_path: string2().optional().describe("Old file path"),
|
|
52615
|
+
new_path: string2().optional().describe("New file path"),
|
|
52616
|
+
old_code: string2().optional().describe("Old source code (when not using paths)"),
|
|
52617
|
+
new_code: string2().optional().describe("New source code (when not using paths)"),
|
|
52618
|
+
language: string2().optional().describe("Language (php/javascript/python/java); auto-detected from path if omitted"),
|
|
52619
|
+
repo: string2().optional().describe("Git repo path (for git-diff mode)"),
|
|
52620
|
+
base: string2().optional().describe("Git base ref (default HEAD~1)"),
|
|
52621
|
+
head: string2().optional().describe("Git head ref (default HEAD)")
|
|
52622
|
+
}
|
|
52623
|
+
}, async ({ old_path, new_path, old_code, new_code, language, repo, base, head }) => {
|
|
52624
|
+
if (repo) {
|
|
52625
|
+
const r = analyzeGitDiff(repo, base ?? "HEAD~1", head ?? "HEAD");
|
|
52626
|
+
return { content: [{ type: "text", text: JSON.stringify(r) }] };
|
|
52627
|
+
}
|
|
52628
|
+
let result;
|
|
52629
|
+
if (old_path && new_path) {
|
|
52630
|
+
result = analyzeDifferentialFiles(old_path, new_path, language);
|
|
52631
|
+
} else if (old_code !== undefined && new_code !== undefined) {
|
|
52632
|
+
result = analyzeDifferential(old_code, new_code, language ?? "php", new_path ?? "target");
|
|
52633
|
+
} else {
|
|
52634
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "provide (old_path+new_path), (old_code+new_code), or (repo)" }) }] };
|
|
52635
|
+
}
|
|
52636
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
52637
|
+
});
|
|
52417
52638
|
server.registerTool("taint_tree", {
|
|
52418
52639
|
title: "Whole-tree taint scan",
|
|
52419
52640
|
description: "EAGLE-EYE: run inter-procedural taint analysis across an entire source tree.",
|
|
@@ -52567,12 +52788,12 @@ async function serve() {
|
|
|
52567
52788
|
}
|
|
52568
52789
|
|
|
52569
52790
|
// src/cli.ts
|
|
52570
|
-
import { readFileSync as
|
|
52571
|
-
import { join as
|
|
52791
|
+
import { readFileSync as readFileSync16, existsSync as existsSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
52792
|
+
import { join as join12, dirname as dirname2 } from "node:path";
|
|
52572
52793
|
import { homedir as homedir5 } from "node:os";
|
|
52573
52794
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
52574
52795
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
52575
|
-
var ROOT6 =
|
|
52796
|
+
var ROOT6 = join12(fileURLToPath8(new URL(".", import.meta.url)), "..");
|
|
52576
52797
|
function which(cmd) {
|
|
52577
52798
|
return spawnSync3(`command -v ${cmd}`, { shell: true }).status === 0;
|
|
52578
52799
|
}
|
|
@@ -52607,7 +52828,7 @@ function runDoctor() {
|
|
|
52607
52828
|
detail: fofa ? "set" : "FOFA_EMAIL/FOFA_KEY not set",
|
|
52608
52829
|
fix: fofa ? undefined : "export FOFA_EMAIL=... && export FOFA_KEY=... (enables fofa_search)"
|
|
52609
52830
|
});
|
|
52610
|
-
const dataOk = existsSync6(
|
|
52831
|
+
const dataOk = existsSync6(join12(ROOT6, "chains.json")) && existsSync6(join12(ROOT6, "tools-catalog.json"));
|
|
52611
52832
|
issues.push({
|
|
52612
52833
|
name: "Data layers (chains + tools-catalog)",
|
|
52613
52834
|
status: dataOk ? "ok" : "fail",
|
|
@@ -52635,21 +52856,21 @@ BlitzStrike — ${verdict}`);
|
|
|
52635
52856
|
function resolveCommand() {
|
|
52636
52857
|
if (which("blitzstrike"))
|
|
52637
52858
|
return { command: "blitzstrike", args: ["serve", "--mcp"] };
|
|
52638
|
-
const src =
|
|
52859
|
+
const src = join12(ROOT6, "src", "index.ts");
|
|
52639
52860
|
if (which("bun") && existsSync6(src))
|
|
52640
52861
|
return { command: "bun", args: ["run", src, "serve", "--mcp"] };
|
|
52641
52862
|
if (which("npx"))
|
|
52642
52863
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52643
52864
|
if (which("bunx"))
|
|
52644
52865
|
return { command: "bunx", args: ["blitzstrike", "serve", "--mcp"] };
|
|
52645
|
-
const dist =
|
|
52866
|
+
const dist = join12(ROOT6, "dist", "index.js");
|
|
52646
52867
|
if (existsSync6(dist))
|
|
52647
52868
|
return { command: "bun", args: [dist, "serve", "--mcp"] };
|
|
52648
52869
|
return { command: "npx", args: ["-y", "blitzstrike", "serve", "--mcp"] };
|
|
52649
52870
|
}
|
|
52650
52871
|
function readJson(p) {
|
|
52651
52872
|
try {
|
|
52652
|
-
return JSON.parse(
|
|
52873
|
+
return JSON.parse(readFileSync16(p, "utf8"));
|
|
52653
52874
|
} catch {
|
|
52654
52875
|
return null;
|
|
52655
52876
|
}
|
|
@@ -52664,7 +52885,7 @@ function jsonMcpServersWrite(p) {
|
|
|
52664
52885
|
...existing.mcpServers ?? {},
|
|
52665
52886
|
blitzstrike: { command, args }
|
|
52666
52887
|
};
|
|
52667
|
-
|
|
52888
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52668
52889
|
};
|
|
52669
52890
|
}
|
|
52670
52891
|
function opencodeWrite(p) {
|
|
@@ -52677,22 +52898,22 @@ function opencodeWrite(p) {
|
|
|
52677
52898
|
...existing.mcp ?? {},
|
|
52678
52899
|
blitzstrike: { type: "local", command: ["npx", "-y", "blitzstrike", "serve", "--mcp"], enabled: true }
|
|
52679
52900
|
};
|
|
52680
|
-
|
|
52901
|
+
writeFileSync5(p, JSON.stringify(existing, null, 2));
|
|
52681
52902
|
copyOpenCodeAgents();
|
|
52682
52903
|
};
|
|
52683
52904
|
}
|
|
52684
52905
|
function copyOpenCodeAgents() {
|
|
52685
|
-
const srcDir =
|
|
52686
|
-
const dstDir =
|
|
52906
|
+
const srcDir = join12(ROOT6, "opencode-agents");
|
|
52907
|
+
const dstDir = join12(homedir5(), ".config", "opencode", "agents");
|
|
52687
52908
|
if (!existsSync6(srcDir))
|
|
52688
52909
|
return;
|
|
52689
52910
|
if (!existsSync6(dstDir))
|
|
52690
52911
|
mkdirSync4(dstDir, { recursive: true });
|
|
52691
52912
|
for (const f of ["Blitz Strike.md", "Blitz.md", "Eagle Eye.md", "Strike.md"]) {
|
|
52692
|
-
const src =
|
|
52693
|
-
const dst =
|
|
52913
|
+
const src = join12(srcDir, f);
|
|
52914
|
+
const dst = join12(dstDir, f);
|
|
52694
52915
|
if (existsSync6(src)) {
|
|
52695
|
-
|
|
52916
|
+
writeFileSync5(dst, readFileSync16(src, "utf8"));
|
|
52696
52917
|
}
|
|
52697
52918
|
}
|
|
52698
52919
|
}
|
|
@@ -52701,7 +52922,7 @@ function codexWrite(p) {
|
|
|
52701
52922
|
const dir = dirname2(p);
|
|
52702
52923
|
if (!existsSync6(dir))
|
|
52703
52924
|
mkdirSync4(dir, { recursive: true });
|
|
52704
|
-
let existing = existsSync6(p) ?
|
|
52925
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52705
52926
|
if (!existing.trimEnd().endsWith(`
|
|
52706
52927
|
`))
|
|
52707
52928
|
existing += `
|
|
@@ -52712,7 +52933,7 @@ function codexWrite(p) {
|
|
|
52712
52933
|
command = "${command}"
|
|
52713
52934
|
args = ${JSON.stringify(args)}
|
|
52714
52935
|
`;
|
|
52715
|
-
|
|
52936
|
+
writeFileSync5(p, existing);
|
|
52716
52937
|
};
|
|
52717
52938
|
}
|
|
52718
52939
|
function hermesWrite(p) {
|
|
@@ -52720,7 +52941,7 @@ function hermesWrite(p) {
|
|
|
52720
52941
|
const dir = dirname2(p);
|
|
52721
52942
|
if (!existsSync6(dir))
|
|
52722
52943
|
mkdirSync4(dir, { recursive: true });
|
|
52723
|
-
let existing = existsSync6(p) ?
|
|
52944
|
+
let existing = existsSync6(p) ? readFileSync16(p, "utf8") : "";
|
|
52724
52945
|
existing = existing.replace(/^ blitzstrike:\n(?: .*\n?)*/m, "");
|
|
52725
52946
|
if (!existing.trimEnd().endsWith(`
|
|
52726
52947
|
`))
|
|
@@ -52741,80 +52962,80 @@ mcp_servers:
|
|
|
52741
52962
|
args: ${JSON.stringify(args)}
|
|
52742
52963
|
`;
|
|
52743
52964
|
}
|
|
52744
|
-
|
|
52965
|
+
writeFileSync5(p, existing);
|
|
52745
52966
|
};
|
|
52746
52967
|
}
|
|
52747
52968
|
function detectAgents() {
|
|
52748
52969
|
const home = homedir5();
|
|
52749
52970
|
const agents = [];
|
|
52750
|
-
const claudeJson =
|
|
52971
|
+
const claudeJson = join12(home, ".claude.json");
|
|
52751
52972
|
agents.push({
|
|
52752
52973
|
name: "Claude Code",
|
|
52753
52974
|
detectPath: claudeJson,
|
|
52754
52975
|
installed: () => existsSync6(claudeJson) || which("claude"),
|
|
52755
52976
|
write: jsonMcpServersWrite(claudeJson)
|
|
52756
52977
|
});
|
|
52757
|
-
const claudeDesktop =
|
|
52978
|
+
const claudeDesktop = join12(home, ".config", "Claude", "claude_desktop_config.json");
|
|
52758
52979
|
agents.push({
|
|
52759
52980
|
name: "Claude Desktop",
|
|
52760
52981
|
detectPath: claudeDesktop,
|
|
52761
52982
|
installed: () => existsSync6(claudeDesktop),
|
|
52762
52983
|
write: jsonMcpServersWrite(claudeDesktop)
|
|
52763
52984
|
});
|
|
52764
|
-
const cursor =
|
|
52985
|
+
const cursor = join12(home, ".cursor", "mcp.json");
|
|
52765
52986
|
agents.push({
|
|
52766
52987
|
name: "Cursor",
|
|
52767
52988
|
detectPath: cursor,
|
|
52768
|
-
installed: () => existsSync6(
|
|
52989
|
+
installed: () => existsSync6(join12(home, ".cursor")) || which("cursor"),
|
|
52769
52990
|
write: jsonMcpServersWrite(cursor)
|
|
52770
52991
|
});
|
|
52771
|
-
const opencode =
|
|
52992
|
+
const opencode = join12(home, ".config", "opencode", "opencode.jsonc");
|
|
52772
52993
|
agents.push({
|
|
52773
52994
|
name: "OpenCode",
|
|
52774
52995
|
detectPath: opencode,
|
|
52775
|
-
installed: () => existsSync6(
|
|
52996
|
+
installed: () => existsSync6(join12(home, ".config", "opencode")) || which("opencode"),
|
|
52776
52997
|
write: opencodeWrite(opencode)
|
|
52777
52998
|
});
|
|
52778
|
-
const codex =
|
|
52999
|
+
const codex = join12(home, ".codex", "config.toml");
|
|
52779
53000
|
agents.push({
|
|
52780
53001
|
name: "Codex",
|
|
52781
53002
|
detectPath: codex,
|
|
52782
|
-
installed: () => existsSync6(
|
|
53003
|
+
installed: () => existsSync6(join12(home, ".codex")) || which("codex"),
|
|
52783
53004
|
write: codexWrite(codex)
|
|
52784
53005
|
});
|
|
52785
|
-
const hermes =
|
|
53006
|
+
const hermes = join12(home, ".hermes", "config.yaml");
|
|
52786
53007
|
agents.push({
|
|
52787
53008
|
name: "Hermes",
|
|
52788
53009
|
detectPath: hermes,
|
|
52789
|
-
installed: () => existsSync6(
|
|
53010
|
+
installed: () => existsSync6(join12(home, ".hermes")) || which("hermes"),
|
|
52790
53011
|
write: hermesWrite(hermes)
|
|
52791
53012
|
});
|
|
52792
|
-
const gemini =
|
|
53013
|
+
const gemini = join12(home, ".gemini", "settings.json");
|
|
52793
53014
|
agents.push({
|
|
52794
53015
|
name: "Gemini CLI",
|
|
52795
53016
|
detectPath: gemini,
|
|
52796
|
-
installed: () => existsSync6(
|
|
53017
|
+
installed: () => existsSync6(join12(home, ".gemini")) || which("gemini"),
|
|
52797
53018
|
write: jsonMcpServersWrite(gemini)
|
|
52798
53019
|
});
|
|
52799
|
-
const windsurf =
|
|
53020
|
+
const windsurf = join12(home, ".codeium", "windsurf", "mcp_config.json");
|
|
52800
53021
|
agents.push({
|
|
52801
53022
|
name: "Windsurf",
|
|
52802
53023
|
detectPath: windsurf,
|
|
52803
|
-
installed: () => existsSync6(
|
|
53024
|
+
installed: () => existsSync6(join12(home, ".codeium")) || which("windsurf"),
|
|
52804
53025
|
write: jsonMcpServersWrite(windsurf)
|
|
52805
53026
|
});
|
|
52806
|
-
const copilot =
|
|
53027
|
+
const copilot = join12(home, ".copilot", "mcp.json");
|
|
52807
53028
|
agents.push({
|
|
52808
53029
|
name: "Copilot",
|
|
52809
53030
|
detectPath: copilot,
|
|
52810
|
-
installed: () => existsSync6(
|
|
53031
|
+
installed: () => existsSync6(join12(home, ".copilot")),
|
|
52811
53032
|
write: jsonMcpServersWrite(copilot)
|
|
52812
53033
|
});
|
|
52813
|
-
const cline =
|
|
53034
|
+
const cline = join12(home, ".cline", "mcp_settings.json");
|
|
52814
53035
|
agents.push({
|
|
52815
53036
|
name: "Cline",
|
|
52816
53037
|
detectPath: cline,
|
|
52817
|
-
installed: () => existsSync6(
|
|
53038
|
+
installed: () => existsSync6(join12(home, ".cline")),
|
|
52818
53039
|
write: jsonMcpServersWrite(cline)
|
|
52819
53040
|
});
|
|
52820
53041
|
return agents;
|
|
@@ -52858,15 +53079,15 @@ Registered with ${ok}/${installed.length} agent(s).`);
|
|
|
52858
53079
|
}
|
|
52859
53080
|
|
|
52860
53081
|
// src/index.ts
|
|
52861
|
-
import { readFileSync as
|
|
52862
|
-
import { join as
|
|
53082
|
+
import { readFileSync as readFileSync17, existsSync as existsSync7 } from "node:fs";
|
|
53083
|
+
import { join as join13 } from "node:path";
|
|
52863
53084
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
52864
|
-
var ROOT7 =
|
|
53085
|
+
var ROOT7 = join13(fileURLToPath9(new URL(".", import.meta.url)), "..");
|
|
52865
53086
|
function readVersion() {
|
|
52866
53087
|
try {
|
|
52867
|
-
const p =
|
|
53088
|
+
const p = join13(ROOT7, "package.json");
|
|
52868
53089
|
if (existsSync7(p))
|
|
52869
|
-
return JSON.parse(
|
|
53090
|
+
return JSON.parse(readFileSync17(p, "utf8")).version ?? "1.0.0";
|
|
52870
53091
|
} catch {}
|
|
52871
53092
|
return "1.0.0";
|
|
52872
53093
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.37",
|
|
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": {
|