Skip to content

Commit 7df5940

Browse files
author
Ye Zhu
committed
fix comments
1 parent 6e02353 commit 7df5940

File tree

4 files changed

+186
-160
lines changed

4 files changed

+186
-160
lines changed

src/upgrade/assessmentManager.ts

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Upgrade } from '../constants';
1010
import { buildPackageId } from './utility';
1111
import metadataManager from './metadataManager';
1212
import { sendInfo } from 'vscode-extension-telemetry-wrapper';
13-
import { batchGetCVEs } from './cve';
13+
import { batchGetCVEIssues } from './cve';
1414

1515
function packageNodeToDescription(node: INodeData): PackageDescription | null {
1616
const version = node.metaData?.["maven.version"];
@@ -122,11 +122,10 @@ function getDependencyIssue(pkg: PackageDescription): UpgradeIssue | null {
122122
return getUpgradeForDependency(version, supportedVersionDefinition, packageId);
123123
}
124124

125-
async function getDependencyIssues(projectNode: INodeData): Promise<UpgradeIssue[]> {
126-
const packages = await getAllDependencies(projectNode);
125+
async function getDependencyIssues(dependencies: PackageDescription[]): Promise<UpgradeIssue[]> {
127126

128-
const issues = packages.map(getDependencyIssue).filter((x): x is UpgradeIssue => Boolean(x));
129-
const versionRangeByGroupId = collectVersionRange(packages.filter(pkg => getPackageUpgradeMetadata(pkg)));
127+
const issues = dependencies.map(getDependencyIssue).filter((x): x is UpgradeIssue => Boolean(x));
128+
const versionRangeByGroupId = collectVersionRange(dependencies.filter(pkg => getPackageUpgradeMetadata(pkg)));
130129
if (Object.keys(versionRangeByGroupId).length > 0) {
131130
sendInfo("", {
132131
operationName: "java.dependency.assessmentManager.getDependencyVersionRange",
@@ -138,16 +137,13 @@ async function getDependencyIssues(projectNode: INodeData): Promise<UpgradeIssue
138137
}
139138

140139
async function getProjectIssues(projectNode: INodeData): Promise<UpgradeIssue[]> {
141-
const cveIssues = await getCVEIssues(projectNode);
142-
if (cveIssues.length > 0) {
143-
return cveIssues;
144-
}
145-
const javaIssues = getJavaIssues(projectNode);
146-
if (javaIssues.length > 0) {
147-
return javaIssues;
148-
}
149-
const dependencyIssues = await getDependencyIssues(projectNode);
150-
return dependencyIssues;
140+
const issues: UpgradeIssue[] = [];
141+
const dependencies = await getAllDependencies(projectNode);
142+
issues.push(...await getCVEIssues(dependencies));
143+
issues.push(...getJavaIssues(projectNode));
144+
issues.push(...await getDependencyIssues(dependencies));
145+
146+
return issues;
151147

152148
}
153149

@@ -200,16 +196,9 @@ async function getAllDependencies(projectNode: INodeData): Promise<PackageDescri
200196
.flat();
201197
}
202198

203-
async function getCVEIssues(projectNode: INodeData): Promise<UpgradeIssue[]> {
204-
205-
// 1. Getting all dependencies from the project
206-
const dependencies = await getAllDependencies(projectNode);
207-
// 2. Convert to GAV (groupId:artifactId:version) format
199+
async function getCVEIssues(dependencies: PackageDescription[]): Promise<UpgradeIssue[]> {
208200
const gavCoordinates = dependencies.map(pkg => `${pkg.groupId}:${pkg.artifactId}:${pkg.version}`);
209-
// 3. Checking them against a CVE database to find known vulnerabilities
210-
const cveResults = await batchGetCVEs(gavCoordinates);
211-
212-
return cveResults;
201+
return batchGetCVEIssues(gavCoordinates);
213202
}
214203

215204
export default {

src/upgrade/cve.ts

Lines changed: 158 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,189 @@
11
import { UpgradeIssue, UpgradeReason } from "./type";
22
import { Octokit } from "@octokit/rest";
3-
import * as semver from 'semver';
3+
import * as semver from "semver";
44

55
/**
66
* Severity levels ordered by criticality (higher number = more critical)
77
* The official doc about the severity levels can be found at:
88
* https://docs.github.com/en/rest/security-advisories/global-advisories?apiVersion=2022-11-28
99
*/
10-
export const severityOrder = {
11-
critical: 4,
12-
high: 3,
13-
medium: 2,
14-
low: 1,
15-
unknown: 0,
16-
} as const;
17-
18-
export type Severity = keyof typeof severityOrder;
10+
export enum Severity {
11+
unknown = 0,
12+
low = 1,
13+
medium = 2,
14+
high = 3,
15+
critical = 4,
16+
}
1917

2018
export interface CVE {
21-
id: string;
22-
ghsa_id: string;
23-
severity: Severity;
24-
summary: string;
25-
description: string;
26-
html_url: string;
27-
affectedDeps: {
28-
name?: string | null;
29-
vulVersions?: string | null;
30-
patchedVersion?: string | null;
31-
}[];
19+
id: string;
20+
ghsa_id: string;
21+
severity: keyof typeof Severity;
22+
summary: string;
23+
description: string;
24+
html_url: string;
25+
affectedDeps: {
26+
name?: string | null;
27+
vulVersions?: string | null;
28+
patchedVersion?: string | null;
29+
}[];
3230
}
3331

34-
export type CveUpgradeIssue = UpgradeIssue & { reason: UpgradeReason.CVE; severity: string; link: string };
32+
export type CveUpgradeIssue = UpgradeIssue & {
33+
reason: UpgradeReason.CVE;
34+
severity: string;
35+
link: string;
36+
};
3537

36-
export async function batchGetCVEs(
38+
export async function batchGetCVEIssues(
3739
coordinates: string[]
3840
): Promise<CveUpgradeIssue[]> {
39-
4041
// Split dependencies into smaller batches to avoid URL length limit
4142
const BATCH_SIZE = 30;
42-
const allCVEDeps: CveUpgradeIssue[] = [];
43+
const allCVEUpgradeIssues: CveUpgradeIssue[] = [];
4344

4445
// Process dependencies in batches
4546
for (let i = 0; i < coordinates.length; i += BATCH_SIZE) {
4647
const batchCoordinates = coordinates.slice(i, i + BATCH_SIZE);
47-
const batchCVEDeps = await getCVEs(batchCoordinates);
48-
allCVEDeps.push(...batchCVEDeps);
48+
const cveUpgradeIssues = await getCveUpgradeIssues(batchCoordinates);
49+
allCVEUpgradeIssues.push(...cveUpgradeIssues);
4950
}
5051

51-
return allCVEDeps;
52+
return allCVEUpgradeIssues;
5253
}
5354

54-
async function getCVEs(
55-
coordinates: string[]
55+
async function getCveUpgradeIssues(
56+
coordinates: string[]
5657
): Promise<CveUpgradeIssue[]> {
57-
try {
58-
const octokit = new Octokit();
59-
60-
const deps = coordinates
61-
.map((d) => d.split(':', 3))
62-
.map((p) => ({ name: `${p[0]}:${p[1]}`, version: p[2] }))
63-
.filter((d) => d.version);
64-
const response = await octokit.securityAdvisories.listGlobalAdvisories({
65-
ecosystem: 'maven',
66-
affects: deps.map((p) => `${p.name}@${p.version}`),
67-
direction: 'asc',
68-
sort: 'published',
69-
per_page: 100
70-
});
71-
72-
const allCves: CVE[] = response.data
73-
.filter((c) => !c.withdrawn_at?.trim() &&
74-
(c.severity === 'critical' || c.severity === 'high')) // only consider critical and high severity CVEs
75-
.map((cve) => ({
76-
id: cve.cve_id || cve.ghsa_id,
77-
ghsa_id: cve.ghsa_id,
78-
severity: cve.severity,
79-
summary: cve.summary,
80-
description: cve.description || cve.summary,
81-
html_url: cve.html_url,
82-
affectedDeps: (cve.vulnerabilities ?? []).map((v) => ({
83-
name: v.package?.name,
84-
vulVersions: v.vulnerable_version_range,
85-
patchedVersion: v.first_patched_version
86-
}))
87-
}));
88-
89-
// group the cves by coordinate
90-
const depsCves: { dep: string; cves: CVE[]; minVersion?: string | null }[] = [];
91-
for (const dep of deps) {
92-
const depCves: CVE[] = allCves.filter((cve) => cve.affectedDeps.some((d) => d.name === dep.name));
93-
if (depCves.length < 1) {
94-
continue;
95-
}
96-
// find the min patched version for each coordinate
97-
let maxPatchedVersion: string | undefined | null;
98-
for (const cve of depCves) {
99-
const patchedVersion = cve.affectedDeps.find((d) => d.name === dep.name && d.patchedVersion)?.patchedVersion;
100-
const coercedPatchedVersion = semver.coerce(patchedVersion);
101-
const coercedMaxPatchedVersion = semver.coerce(maxPatchedVersion);
102-
if (
103-
!maxPatchedVersion ||
104-
(coercedPatchedVersion &&
105-
coercedMaxPatchedVersion &&
106-
semver.gt(coercedPatchedVersion, coercedMaxPatchedVersion))
107-
) {
108-
maxPatchedVersion = patchedVersion;
109-
}
110-
}
111-
112-
depsCves.push({
113-
dep: dep.name,
114-
cves: depCves,
115-
minVersion: maxPatchedVersion
116-
});
117-
}
118-
119-
const upgradeIssues = depsCves.map(depCve => {
120-
const currentDep = deps.find(d => d.name === depCve.dep);
121-
const mostCriticalCve = findMostCriticalCve(depCve.cves);
122-
return {
123-
packageId: depCve.dep,
124-
packageDisplayName: depCve.dep,
125-
currentVersion: currentDep?.version || 'unknown',
126-
name: `${mostCriticalCve.id || 'CVE'}`,
127-
reason: UpgradeReason.CVE as const,
128-
suggestedVersion: {
129-
name: depCve.minVersion || 'unknown',
130-
description: mostCriticalCve.description || mostCriticalCve.summary || 'Security vulnerability detected'
131-
},
132-
severity: mostCriticalCve.severity,
133-
description: mostCriticalCve.description || mostCriticalCve.summary || 'Security vulnerability detected',
134-
link: mostCriticalCve.html_url,
135-
};
136-
});
137-
return upgradeIssues;
138-
} catch (error) {
139-
throw error;
140-
}
58+
const deps = coordinates
59+
.map((d) => d.split(":", 3))
60+
.map((p) => ({ name: `${p[0]}:${p[1]}`, version: p[2] }))
61+
.filter((d) => d.version);
62+
63+
const depsCves = await fetchCves(deps);
64+
return mapCvesToUpgradeIssues(depsCves, deps);
14165
}
14266

143-
function findMostCriticalCve(depCves: CVE[]) {
144-
let mostCriticalSeverity: Severity = 'unknown';
145-
let mostCriticalCve = depCves[0];
67+
async function fetchCves(deps: { name: string; version: string }[]) {
68+
const allCves: CVE[] = await retrieveVulnerabilityData(deps);
14669

147-
for (const cve of depCves) {
148-
if (severityOrder[cve.severity] > severityOrder[mostCriticalSeverity]) {
149-
mostCriticalSeverity = cve.severity;
150-
mostCriticalCve = cve;
151-
}
70+
// group the cves by coordinate
71+
const depsCves: { dep: string; cves: CVE[]; minVersion?: string | null }[] =
72+
[];
73+
74+
for (const dep of deps) {
75+
const depCves: CVE[] = allCves.filter((cve) =>
76+
cve.affectedDeps.some((d) => d.name === dep.name)
77+
);
78+
79+
if (depCves.length < 1) {
80+
continue;
15281
}
153-
return mostCriticalCve;
82+
83+
// find the min patched version for each coordinate
84+
const maxPatchedVersion = calculateMaxPatchedVersion(depCves, dep);
85+
86+
depsCves.push({
87+
dep: dep.name,
88+
cves: depCves,
89+
minVersion: maxPatchedVersion,
90+
});
91+
}
92+
93+
return depsCves;
94+
}
95+
96+
function calculateMaxPatchedVersion(
97+
depCves: CVE[],
98+
dep: { name: string; version: string }
99+
) {
100+
let maxPatchedVersion: string | undefined | null;
101+
102+
for (const cve of depCves) {
103+
const patchedVersion = cve.affectedDeps.find(
104+
(d) => d.name === dep.name && d.patchedVersion
105+
)?.patchedVersion;
106+
107+
const coercedPatchedVersion = semver.coerce(patchedVersion);
108+
const coercedMaxPatchedVersion = semver.coerce(maxPatchedVersion);
109+
110+
if (
111+
!maxPatchedVersion ||
112+
(coercedPatchedVersion &&
113+
coercedMaxPatchedVersion &&
114+
semver.gt(coercedPatchedVersion, coercedMaxPatchedVersion))
115+
) {
116+
maxPatchedVersion = patchedVersion;
117+
}
118+
}
119+
120+
return maxPatchedVersion;
121+
}
122+
123+
async function retrieveVulnerabilityData(
124+
deps: { name: string; version: string }[]
125+
) {
126+
const octokit = new Octokit();
127+
128+
const response = await octokit.securityAdvisories.listGlobalAdvisories({
129+
ecosystem: "maven",
130+
affects: deps.map((p) => `${p.name}@${p.version}`),
131+
direction: "asc",
132+
sort: "published",
133+
per_page: 100,
134+
});
135+
136+
const allCves: CVE[] = response.data
137+
.filter(
138+
(c) =>
139+
!c.withdrawn_at?.trim() &&
140+
(c.severity === "critical" || c.severity === "high")
141+
) // only consider critical and high severity CVEs
142+
.map((cve) => ({
143+
id: cve.cve_id || cve.ghsa_id,
144+
ghsa_id: cve.ghsa_id,
145+
severity: cve.severity,
146+
summary: cve.summary,
147+
description: cve.description || cve.summary,
148+
html_url: cve.html_url,
149+
affectedDeps: (cve.vulnerabilities ?? []).map((v) => ({
150+
name: v.package?.name,
151+
vulVersions: v.vulnerable_version_range,
152+
patchedVersion: v.first_patched_version,
153+
})),
154+
}));
155+
return allCves;
156+
}
157+
158+
function mapCvesToUpgradeIssues(
159+
depsCves: { dep: string; cves: CVE[]; minVersion?: string | null }[],
160+
deps: { name: string; version: string }[]
161+
) {
162+
const upgradeIssues = depsCves.map((depCve) => {
163+
const currentDep = deps.find((d) => d.name === depCve.dep);
164+
const mostCriticalCve = [...depCve.cves].sort(
165+
(a, b) => Severity[b.severity] - Severity[a.severity]
166+
)[0];
167+
return {
168+
packageId: depCve.dep,
169+
packageDisplayName: depCve.dep,
170+
currentVersion: currentDep?.version || "unknown",
171+
name: `${mostCriticalCve.id || "CVE"}`,
172+
reason: UpgradeReason.CVE as const,
173+
suggestedVersion: {
174+
name: depCve.minVersion || "unknown",
175+
description:
176+
mostCriticalCve.description ||
177+
mostCriticalCve.summary ||
178+
"Security vulnerability detected",
179+
},
180+
severity: mostCriticalCve.severity,
181+
description:
182+
mostCriticalCve.description ||
183+
mostCriticalCve.summary ||
184+
"Security vulnerability detected",
185+
link: mostCriticalCve.html_url,
186+
};
187+
});
188+
return upgradeIssues;
154189
}

0 commit comments

Comments
 (0)