brainblast 0.5.2 → 0.6.0
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/dist/{chunk-EYFKA33G.js → chunk-A56IF3UX.js} +137 -940
- package/dist/chunk-SC6RNNDW.js +160 -0
- package/dist/chunk-ZZ6LBZV5.js +909 -0
- package/dist/cli.js +99 -4
- package/dist/diff-PZKZYBKF.js +12 -0
- package/dist/index.d.ts +26 -1
- package/dist/index.js +33 -19
- package/dist/mcp-RUVILE2Y.js +172 -0
- package/package.json +2 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/osv.ts
|
|
2
|
+
function mapSeverity(vuln) {
|
|
3
|
+
const severities = vuln["severity"] ?? [];
|
|
4
|
+
for (const sev of severities) {
|
|
5
|
+
if (sev.type === "CVSS_V3") {
|
|
6
|
+
const score = parseFloat(sev.score);
|
|
7
|
+
if (!isNaN(score)) {
|
|
8
|
+
if (score >= 9) return "critical";
|
|
9
|
+
if (score >= 7) return "high";
|
|
10
|
+
if (score >= 4) return "medium";
|
|
11
|
+
return "low";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const dbSpec = vuln["database_specific"] ?? {};
|
|
16
|
+
const ghsa = (dbSpec["severity"] ?? "").toUpperCase();
|
|
17
|
+
const map = {
|
|
18
|
+
CRITICAL: "critical",
|
|
19
|
+
HIGH: "high",
|
|
20
|
+
MODERATE: "medium",
|
|
21
|
+
LOW: "low"
|
|
22
|
+
};
|
|
23
|
+
return map[ghsa] ?? "high";
|
|
24
|
+
}
|
|
25
|
+
async function queryOsv(ecosystem, name, version) {
|
|
26
|
+
const body = JSON.stringify({ version, package: { name, ecosystem } });
|
|
27
|
+
let res;
|
|
28
|
+
try {
|
|
29
|
+
res = await fetch("https://api.osv.dev/v1/query", {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body,
|
|
33
|
+
signal: AbortSignal.timeout(15e3)
|
|
34
|
+
});
|
|
35
|
+
} catch (e) {
|
|
36
|
+
throw new Error(`OSV API request failed: ${e.message ?? String(e)}`);
|
|
37
|
+
}
|
|
38
|
+
if (!res.ok) throw new Error(`OSV API error: ${res.status} ${res.statusText}`);
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
return (data.vulns ?? []).map((v) => {
|
|
41
|
+
const id = v["id"];
|
|
42
|
+
const rawSummary = v["summary"] ?? "";
|
|
43
|
+
const rawDetails = v["details"] ?? "";
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
severity: mapSeverity(v),
|
|
47
|
+
summary: rawSummary || rawDetails.slice(0, 200),
|
|
48
|
+
url: `https://osv.dev/vulnerability/${id}`
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/diff.ts
|
|
54
|
+
async function diffVersions(ecosystem, packageName, fromVersion, toVersion) {
|
|
55
|
+
const [fromAdvisories, toAdvisories] = await Promise.all([
|
|
56
|
+
queryOsv(ecosystem, packageName, fromVersion),
|
|
57
|
+
queryOsv(ecosystem, packageName, toVersion)
|
|
58
|
+
]);
|
|
59
|
+
const fromIds = new Set(fromAdvisories.map((a) => a.id));
|
|
60
|
+
const toIds = new Set(toAdvisories.map((a) => a.id));
|
|
61
|
+
return {
|
|
62
|
+
package: packageName,
|
|
63
|
+
ecosystem,
|
|
64
|
+
fromVersion,
|
|
65
|
+
toVersion,
|
|
66
|
+
resolved: fromAdvisories.filter((a) => !toIds.has(a.id)),
|
|
67
|
+
introduced: toAdvisories.filter((a) => !fromIds.has(a.id)),
|
|
68
|
+
unchanged: fromAdvisories.filter((a) => toIds.has(a.id))
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
var SEV_WEIGHT = { critical: 8, high: 4, medium: 2, low: 1 };
|
|
72
|
+
function riskScore(result) {
|
|
73
|
+
const sum = (list) => list.reduce((n, a) => n + (SEV_WEIGHT[a.severity] ?? 0), 0);
|
|
74
|
+
return sum(result.introduced) - sum(result.resolved);
|
|
75
|
+
}
|
|
76
|
+
function badge(sev) {
|
|
77
|
+
return { critical: "[CRITICAL]", high: "[HIGH]", medium: "[MEDIUM]", low: "[LOW]" }[sev] ?? `[${sev.toUpperCase()}]`;
|
|
78
|
+
}
|
|
79
|
+
function renderDiffText(result) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
lines.push(`brainblast diff: ${result.package} ${result.fromVersion} \u2192 ${result.toVersion} (${result.ecosystem})
|
|
82
|
+
`);
|
|
83
|
+
const total = result.introduced.length + result.resolved.length + result.unchanged.length;
|
|
84
|
+
if (total === 0) {
|
|
85
|
+
lines.push(" No known OSV advisories for either version.");
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
if (result.introduced.length > 0) {
|
|
89
|
+
lines.push(` INTRODUCED (${result.introduced.length}):`);
|
|
90
|
+
for (const a of result.introduced) {
|
|
91
|
+
lines.push(` + ${badge(a.severity)} ${a.id} \u2014 ${a.summary}`);
|
|
92
|
+
lines.push(` ${a.url}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (result.resolved.length > 0) {
|
|
96
|
+
lines.push(` RESOLVED (${result.resolved.length}):`);
|
|
97
|
+
for (const a of result.resolved) {
|
|
98
|
+
lines.push(` - ${badge(a.severity)} ${a.id} \u2014 ${a.summary}`);
|
|
99
|
+
lines.push(` ${a.url}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (result.unchanged.length > 0) {
|
|
103
|
+
lines.push(` UNCHANGED (${result.unchanged.length}):`);
|
|
104
|
+
for (const a of result.unchanged) {
|
|
105
|
+
lines.push(` ~ ${badge(a.severity)} ${a.id} \u2014 ${a.summary}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
function renderDiffMd(result) {
|
|
111
|
+
const lines = [];
|
|
112
|
+
lines.push(`## brainblast diff: \`${result.package}\` ${result.fromVersion} \u2192 ${result.toVersion} (${result.ecosystem})
|
|
113
|
+
`);
|
|
114
|
+
const total = result.introduced.length + result.resolved.length + result.unchanged.length;
|
|
115
|
+
if (total === 0) {
|
|
116
|
+
lines.push("No known OSV advisories for either version.");
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
if (result.introduced.length > 0) {
|
|
120
|
+
lines.push(`### \u26A0\uFE0F Introduced (${result.introduced.length})
|
|
121
|
+
`);
|
|
122
|
+
for (const a of result.introduced) {
|
|
123
|
+
lines.push(`- **${badge(a.severity)}** [${a.id}](${a.url}) \u2014 ${a.summary}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push("");
|
|
126
|
+
}
|
|
127
|
+
if (result.resolved.length > 0) {
|
|
128
|
+
lines.push(`### \u2705 Resolved (${result.resolved.length})
|
|
129
|
+
`);
|
|
130
|
+
for (const a of result.resolved) {
|
|
131
|
+
lines.push(`- **${badge(a.severity)}** [${a.id}](${a.url}) \u2014 ${a.summary}`);
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
}
|
|
135
|
+
if (result.unchanged.length > 0) {
|
|
136
|
+
lines.push(`### ~ Unchanged (${result.unchanged.length})
|
|
137
|
+
`);
|
|
138
|
+
for (const a of result.unchanged) {
|
|
139
|
+
lines.push(`- **${badge(a.severity)}** [${a.id}](${a.url}) \u2014 ${a.summary}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push("");
|
|
142
|
+
}
|
|
143
|
+
const score = riskScore(result);
|
|
144
|
+
if (score > 0) {
|
|
145
|
+
lines.push(`> **\u26D4 Upgrade increases risk (score +${score}). Review introduced advisories before bumping.**`);
|
|
146
|
+
} else if (score < 0) {
|
|
147
|
+
lines.push(`> **\u2705 Upgrade decreases risk (score ${score}). Upgrade recommended.**`);
|
|
148
|
+
} else if (result.unchanged.length > 0) {
|
|
149
|
+
lines.push(`> **~ Risk profile unchanged (${result.unchanged.length} advisory${result.unchanged.length !== 1 ? "ies" : ""} persist).**`);
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
queryOsv,
|
|
156
|
+
diffVersions,
|
|
157
|
+
riskScore,
|
|
158
|
+
renderDiffText,
|
|
159
|
+
renderDiffMd
|
|
160
|
+
};
|