Skip to content

Commit 7519d7e

Browse files
committed
chore: lint
1 parent 4056040 commit 7519d7e

File tree

6 files changed

+396
-2275
lines changed

6 files changed

+396
-2275
lines changed

lib/go-parser/go-binary.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,25 @@ export function parseGoVersion(rawVersion: string): string {
223223
return ver.includes(".", ver.indexOf(".") + 1) ? ver : ver + ".0";
224224
}
225225

226+
/**
227+
* Strips the "go" prefix from a Go version string and validates the format.
228+
* Returns the cleaned version (e.g., "1.21.0") or empty string if invalid.
229+
* Rejects RC/beta/devel versions since we cannot accurately match vulnerabilities
230+
* against pre-release builds.
231+
*/
232+
export function parseGoVersion(rawVersion: string): string {
233+
// Only match release versions (e.g., "go1.21" or "go1.21.5").
234+
// Reject RC/beta (go1.21rc1, go1.22beta2) and devel builds.
235+
const match = rawVersion.match(/^go(\d+\.\d+(?:\.\d+)?)$/);
236+
if (!match) {
237+
return "";
238+
}
239+
const ver = match[1];
240+
// Ensure three-segment semver (e.g., "1.19" → "1.19.0") because
241+
// @snyk/vuln uses node's semver library which requires three segments.
242+
return ver.includes(".", ver.indexOf(".") + 1) ? ver : ver + ".0";
243+
}
244+
226245
export function extractModuleInformation(
227246
binary: Elf,
228247
): [name: string, deps: GoModule[], goVersion: string] {

lib/response-builder.ts

Lines changed: 75 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ import * as types from "./types";
1212
import { truncateAdditionalFacts } from "./utils";
1313
import { PLUGIN_VERSION } from "./version";
1414

15-
export {
16-
buildResponse,
17-
expandDockerfilePackages,
18-
excludeBaseImageDeps,
19-
annotateWithLayerIds,
20-
};
15+
export { buildResponse };
2116

2217
async function buildResponse(
2318
depsAnalysis: StaticAnalysis & {
@@ -31,39 +26,14 @@ async function buildResponse(
3126
options?: Partial<types.PluginOptions>,
3227
): Promise<types.PluginResponse> {
3328
const deps = depsAnalysis.depTree.dependencies;
34-
35-
// Expand both the Dockerfile packages and the auto-detected user instructions packages,
36-
// storing the results back to the original objects.
37-
if (dockerfileAnalysis?.dockerfilePackages) {
38-
dockerfileAnalysis.dockerfilePackages = expandDockerfilePackages(
39-
dockerfileAnalysis.dockerfilePackages,
40-
deps,
41-
);
42-
}
43-
44-
if (depsAnalysis.autoDetectedUserInstructions?.dockerfilePackages) {
45-
depsAnalysis.autoDetectedUserInstructions.dockerfilePackages =
46-
expandDockerfilePackages(
47-
depsAnalysis.autoDetectedUserInstructions.dockerfilePackages,
48-
deps,
49-
);
50-
}
51-
52-
// Select a dockerfilePackages object to use for the annotation and exclusion of base image dependencies.
53-
// Prioritize the Dockerfile packages over the auto-detected user instructions packages.
54-
const dockerfilePkgs =
55-
dockerfileAnalysis?.dockerfilePackages ||
56-
depsAnalysis.autoDetectedUserInstructions?.dockerfilePackages;
57-
29+
const dockerfilePkgs = collectDockerfilePkgs(dockerfileAnalysis, deps);
5830
const finalDeps = excludeBaseImageDeps(
5931
deps,
6032
dockerfilePkgs,
6133
excludeBaseImageVulns,
6234
);
63-
annotateWithLayerIds(finalDeps, dockerfilePkgs);
64-
65-
// Apply the filtered dependencies back to the depTree
66-
depsAnalysis.depTree.dependencies = finalDeps;
35+
/** WARNING! Mutates the depTree.dependencies! */
36+
annotateLayerIds(finalDeps, dockerfilePkgs);
6737

6838
/** This must be called after all final changes to the DependencyTree. */
6939
const depGraph = await legacy.depTreeToGraph(
@@ -227,12 +197,17 @@ async function buildResponse(
227197
autoDetectedLayers &&
228198
Object.keys(autoDetectedLayers).length > 0
229199
) {
200+
const autoDetectedPackagesWithChildren = getUserInstructionDeps(
201+
autoDetectedPackages,
202+
deps,
203+
);
204+
230205
const autoDetectedUserInstructionsFact: facts.AutoDetectedUserInstructionsFact =
231206
{
232207
type: "autoDetectedUserInstructions",
233208
data: {
234209
dockerfileLayers: autoDetectedLayers,
235-
dockerfilePackages: autoDetectedPackages!,
210+
dockerfilePackages: autoDetectedPackagesWithChildren!,
236211
},
237212
};
238213
additionalFacts.push(autoDetectedUserInstructionsFact);
@@ -364,68 +339,59 @@ async function buildResponse(
364339
};
365340
}
366341

367-
/**
368-
* Returns the package source name from a full dependency name.
369-
*
370-
* A package source refers to the top-level package name, such as "bzip2" in "bzip2/libbz2-dev".
371-
*
372-
* @param depName - The full dependency name.
373-
* @returns The package source name.
374-
*/
375-
function packageSource(depName: string): string {
376-
return depName.split("/")[0];
342+
function collectDockerfilePkgs(
343+
dockerAnalysis: DockerFileAnalysis | undefined,
344+
deps: {
345+
[depName: string]: types.DepTreeDep;
346+
},
347+
) {
348+
if (!dockerAnalysis) {
349+
return;
350+
}
351+
352+
return getUserInstructionDeps(dockerAnalysis.dockerfilePackages, deps);
377353
}
378354

379-
/**
380-
* Expands the list of packages explicitly requested in the Dockerfile to include all transitive dependencies.
381-
*
382-
* The returned package map is keyed by the full dependency names. Package names extracted from the Dockerfile
383-
* (typically in the form of source segments) are copied from the input map into the returned map to maintain
384-
* compatibility with the CLI dockerfile-attribution logic.
385-
*
386-
* @param dockerfilePackages - The packages explicitly requested in a Dockerfile.
387-
* @param deps - The dependencies of the image.
388-
* @returns A map of packages attributed to the Dockerfile.
389-
*/
390-
function expandDockerfilePackages(
355+
// Iterate over the dependencies list; if one is introduced by the dockerfile,
356+
// flatten its dependencies and append them to the list of dockerfile
357+
// packages. This gives us a reference of all transitive deps installed via
358+
// the dockerfile, and the instruction that installed it.
359+
function getUserInstructionDeps(
391360
dockerfilePackages: DockerFilePackages,
392-
deps: { [depName: string]: types.DepTreeDep },
361+
dependencies: {
362+
[depName: string]: types.DepTreeDep;
363+
},
393364
): DockerFilePackages {
394-
const expandedPkgs = { ...dockerfilePackages };
395-
396-
function collectChildPackages(node: types.DepTreeDep, parentEntry: any) {
397-
if (!node.dependencies) {
398-
return;
399-
}
400-
for (const childKey of Object.keys(node.dependencies)) {
401-
if (!expandedPkgs[childKey]) {
402-
expandedPkgs[childKey] = parentEntry;
403-
collectChildPackages(node.dependencies[childKey], parentEntry);
365+
for (const dependencyName in dependencies) {
366+
if (dependencies.hasOwnProperty(dependencyName)) {
367+
const sourceOrName = dependencyName.split("/")[0];
368+
const dockerfilePackage = dockerfilePackages[sourceOrName];
369+
370+
if (dockerfilePackage) {
371+
for (const dep of collectDeps(dependencies[dependencyName])) {
372+
dockerfilePackages[dep.split("/")[0]] = { ...dockerfilePackage };
373+
}
404374
}
405375
}
406376
}
407377

408-
for (const rootKey of Object.keys(deps)) {
409-
const source = packageSource(rootKey);
410-
const dockerfileEntry = expandedPkgs[rootKey] || expandedPkgs[source];
411-
if (dockerfileEntry) {
412-
// Ensure the full dependency name is in the expanded packages.
413-
expandedPkgs[rootKey] = dockerfileEntry;
414-
collectChildPackages(deps[rootKey], dockerfileEntry);
415-
}
416-
}
378+
return dockerfilePackages;
379+
}
417380

418-
return expandedPkgs;
381+
function collectDeps(pkg) {
382+
// ES5 doesn't have Object.values, so replace with Object.keys() and map()
383+
return pkg.dependencies
384+
? Object.keys(pkg.dependencies)
385+
.map((name) => pkg.dependencies[name])
386+
.reduce((allDeps, pkg) => {
387+
return [...allDeps, ...collectDeps(pkg)];
388+
}, Object.keys(pkg.dependencies))
389+
: [];
419390
}
420391

421-
/**
422-
* Excludes base image dependencies from the dependency tree if excludeBaseImageVulns is true.
423-
*
424-
* @param deps - The dependencies of the image.
425-
* @param dockerfilePkgs - The expanded packages attributed to the Dockerfile.
426-
* @param excludeBaseImageVulns - Whether to exclude base image dependencies.
427-
* @returns The dependencies of the image.
428-
*/
392+
// Skip processing if option disabled or dockerfilePkgs is undefined. We
393+
// can't exclude anything in that case, because we can't tell which deps are
394+
// from dockerfile and which from base image.
429395
function excludeBaseImageDeps(
430396
deps: {
431397
[depName: string]: types.DepTreeDep;
@@ -437,48 +403,39 @@ function excludeBaseImageDeps(
437403
return deps;
438404
}
439405

440-
return Object.keys(deps)
406+
return extractDockerfileDeps(deps, dockerfilePkgs);
407+
}
408+
409+
function extractDockerfileDeps(
410+
allDeps: {
411+
[depName: string]: types.DepTreeDep;
412+
},
413+
dockerfilePkgs: DockerFilePackages,
414+
) {
415+
return Object.keys(allDeps)
441416
.filter((depName) => dockerfilePkgs[depName])
442417
.reduce((extractedDeps, depName) => {
443-
extractedDeps[depName] = deps[depName];
418+
extractedDeps[depName] = allDeps[depName];
444419
return extractedDeps;
445420
}, {});
446421
}
447422

448-
/**
449-
* Annotates the dependency tree with layer IDs. Mutates the recieved dependency tree.
450-
*
451-
* @param deps - The dependencies of the image.
452-
* @param dockerfilePkgs - The expanded packages attributed to the Dockerfile.
453-
*/
454-
function annotateWithLayerIds(
455-
deps: { [depName: string]: types.DepTreeDep },
456-
dockerfilePkgs: DockerFilePackages | undefined,
457-
): void {
423+
function annotateLayerIds(deps, dockerfilePkgs) {
458424
if (!dockerfilePkgs) {
459425
return;
460426
}
461427

462-
function annotateRecursive(currentDeps: {
463-
[depName: string]: types.DepTreeDep;
464-
}) {
465-
for (const depKey of Object.keys(currentDeps)) {
466-
const node = currentDeps[depKey];
467-
const dockerfileEntry = dockerfilePkgs![depKey];
468-
469-
if (dockerfileEntry) {
470-
node.labels = {
471-
...(node.labels || {}),
472-
dockerLayerId: instructionDigest(dockerfileEntry.instruction),
473-
};
474-
475-
// Only progress down the dependency tree if the current node is a dockerfile package.
476-
if (node.dependencies) {
477-
annotateRecursive(node.dependencies);
478-
}
479-
}
428+
for (const dep of Object.keys(deps)) {
429+
const pkg = deps[dep];
430+
const dockerfilePkg = dockerfilePkgs[dep];
431+
if (dockerfilePkg) {
432+
pkg.labels = {
433+
...(pkg.labels || {}),
434+
dockerLayerId: instructionDigest(dockerfilePkg.instruction),
435+
};
436+
}
437+
if (pkg.dependencies) {
438+
annotateLayerIds(pkg.dependencies, dockerfilePkgs);
480439
}
481440
}
482-
483-
annotateRecursive(deps);
484441
}

0 commit comments

Comments
 (0)