Skip to content

Commit e9b9ccb

Browse files
ivanstanevagatakrajewska
authored andcommitted
feat: normalize all paths used to support scanning on Windows
The plugin currently works only with Unix/POSIX-style paths and cannot scan container images while running on Windows.
1 parent 91ee5b6 commit e9b9ccb

File tree

13 files changed

+64
-33
lines changed

13 files changed

+64
-33
lines changed

lib/analyzer/docker-analyzer.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as Debug from "debug";
2+
import { normalize as normalizePath } from "path";
23
import { DockerOptions } from "../docker";
34
import * as dockerFile from "../docker-file";
45
import * as binariesAnalyzer from "../inputs/binaries/docker";
@@ -83,7 +84,9 @@ export async function analyze(
8384
osRelease,
8485
results: pkgManagerAnalysis,
8586
binaries: binariesAnalysis,
86-
imageLayers: imageInspection.RootFS && imageInspection.RootFS.Layers,
87+
imageLayers:
88+
imageInspection.RootFS &&
89+
imageInspection.RootFS.Layers.map((layer) => normalizePath(layer)),
8790
};
8891
}
8992

lib/docker-file.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DockerfileParser } from "dockerfile-ast";
22
import * as fs from "fs";
3+
import { normalize as normalizePath } from "path";
34
import {
45
DockerFileLayers,
56
DockerFilePackages,
@@ -23,7 +24,7 @@ async function readDockerfileAndAnalyse(
2324
return undefined;
2425
}
2526

26-
const contents = await readFile(targetFilePath);
27+
const contents = await readFile(normalizePath(targetFilePath));
2728
return analyseDockerfile(contents);
2829
}
2930

lib/experimental.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async function getStaticAnalysisResult(
115115
export function fullImageSavePath(imageSavePath: string | undefined): string {
116116
let imagePath = tmp.dirSync().name;
117117
if (imageSavePath) {
118-
imagePath = imageSavePath;
118+
imagePath = path.normalize(imageSavePath);
119119
}
120120

121121
return path.join(imagePath, uuidv4());

lib/extractor/docker-archive/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { normalize as normalizePath } from "path";
12
import { DockerArchiveManifest } from "../types";
23

34
export { extractArchive } from "./layer";
45

56
export function getManifestLayers(manifest: DockerArchiveManifest) {
6-
return manifest.Layers;
7+
return manifest.Layers.map((layer) => normalizePath(layer));
78
}
89

910
export function getImageIdFromManifest(

lib/extractor/docker-archive/layer.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Debug from "debug";
22
import { createReadStream } from "fs";
33
import * as gunzip from "gunzip-maybe";
4-
import { basename } from "path";
4+
import { basename, normalize as normalizePath } from "path";
55
import { Readable } from "stream";
66
import { extract, Extract } from "tar-stream";
77
import { streamToJson } from "../../stream-utils";
@@ -32,17 +32,18 @@ export async function extractArchive(
3232

3333
tarExtractor.on("entry", async (header, stream, next) => {
3434
if (header.type === "file") {
35-
if (isTarFile(header.name)) {
35+
const normalizedName = normalizePath(header.name);
36+
if (isTarFile(normalizedName)) {
3637
try {
37-
layers[header.name] = await extractImageLayer(
38+
layers[normalizedName] = await extractImageLayer(
3839
stream,
3940
extractActions,
4041
);
4142
} catch (error) {
4243
debug(`Error extracting layer content from: '${error}'`);
4344
reject(new Error("Error reading tar archive"));
4445
}
45-
} else if (isManifestFile(header.name)) {
46+
} else if (isManifestFile(normalizedName)) {
4647
manifest = await getManifestFile(stream);
4748
}
4849
}
@@ -77,9 +78,11 @@ function getLayersContentAndArchiveManifest(
7778
// skip (ignore) non-existent layers
7879
// get the layers content without the name
7980
// reverse layers order from last to first
80-
const filteredLayers = manifest.Layers.filter(
81-
(layersName) => layers[layersName],
82-
)
81+
const layersWithNormalizedNames = manifest.Layers.map((layersName) =>
82+
normalizePath(layersName),
83+
);
84+
const filteredLayers = layersWithNormalizedNames
85+
.filter((layersName) => layers[layersName])
8386
.map((layerName) => layers[layerName])
8487
.reverse();
8588

lib/extractor/layer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as gunzip from "gunzip-maybe";
2-
import { resolve as resolvePath } from "path";
2+
import * as path from "path";
33
import { Readable } from "stream";
44
import { extract, Extract } from "tar-stream";
55
import { applyCallbacks } from "./callbacks";
@@ -21,7 +21,7 @@ export async function extractImageLayer(
2121

2222
tarExtractor.on("entry", async (headers, stream, next) => {
2323
if (headers.type === "file") {
24-
const absoluteFileName = resolvePath("/", headers.name);
24+
const absoluteFileName = path.join(path.sep, headers.name);
2525
// TODO wouldn't it be simpler to first check
2626
// if the filename matches any patterns?
2727
const processedResult = await extractFileAndProcess(

lib/extractor/oci-archive/layer.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Debug from "debug";
22
import { createReadStream } from "fs";
33
import * as gunzip from "gunzip-maybe";
4+
import { normalize as normalizePath, sep as pathSeparator } from "path";
45
import { PassThrough } from "stream";
56
import { extract, Extract } from "tar-stream";
67
import { streamToJson } from "../../stream-utils";
@@ -37,7 +38,8 @@ export async function extractArchive(
3738

3839
tarExtractor.on("entry", async (header, stream, next) => {
3940
if (header.type === "file") {
40-
if (isImageIndexFile(header.name)) {
41+
const normalizedHeaderName = normalizePath(header.name);
42+
if (isImageIndexFile(normalizedHeaderName)) {
4143
imageIndex = await streamToJson<OciImageIndex>(stream);
4244
} else {
4345
const jsonStream = new PassThrough();
@@ -55,7 +57,7 @@ export async function extractArchive(
5557

5658
// header format is /blobs/hash_name/hash_value
5759
// we're extracting hash_name:hash_value format to match manifest digest
58-
const headerParts = header.name.split("/");
60+
const headerParts = normalizedHeaderName.split(pathSeparator);
5961
const hashName = headerParts[1];
6062
const hashValue = headerParts[headerParts.length - 1];
6163
const digest = `${hashName}:${hashValue}`;

lib/image-type.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { normalize as normalizePath } from "path";
12
import { ImageType } from "./types";
23

34
export function getImageType(targetImage: string): ImageType {
@@ -15,14 +16,16 @@ export function getImageType(targetImage: string): ImageType {
1516
}
1617

1718
export function getArchivePath(targetImage: string): string {
18-
// strip the "docker-archive:" or "oci-archive:" prefix
19-
20-
const path = targetImage.split(":")[1];
21-
if (!path) {
19+
if (
20+
!targetImage.startsWith("docker-archive:") &&
21+
!targetImage.startsWith("oci-archive:")
22+
) {
2223
throw new Error(
2324
'The provided archive path is missing image specific prefix, eg."docker-archive:" or "oci-archive:"',
2425
);
2526
}
2627

27-
return path;
28+
return targetImage.indexOf("docker-archive:") !== -1
29+
? normalizePath(targetImage.substring("docker-archive:".length))
30+
: normalizePath(targetImage.substring("oci-archive:".length));
2831
}

lib/inputs/apk/static.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { normalize as normalizePath } from "path";
12
import { getContentAsString } from "../../extractor";
23
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
34
import { streamToString } from "../../stream-utils";
45

56
export const getApkDbFileContentAction: ExtractAction = {
67
actionName: "apk-db",
7-
filePathMatches: (filePath) => filePath === "/lib/apk/db/installed",
8+
filePathMatches: (filePath) =>
9+
filePath === normalizePath("/lib/apk/db/installed"),
810
callback: streamToString,
911
};
1012

lib/inputs/apt/static.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1+
import { normalize as normalizePath } from "path";
12
import { IAptFiles } from "../../analyzer/types";
23
import { getContentAsString } from "../../extractor";
34
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
45
import { streamToString } from "../../stream-utils";
56

67
export const getDpkgFileContentAction: ExtractAction = {
78
actionName: "dpkg",
8-
filePathMatches: (filePath) => filePath === "/var/lib/dpkg/status",
9+
filePathMatches: (filePath) =>
10+
filePath === normalizePath("/var/lib/dpkg/status"),
911
callback: streamToString,
1012
};
1113

1214
export const getExtFileContentAction: ExtractAction = {
1315
actionName: "ext",
14-
filePathMatches: (filePath) => filePath === "/var/lib/apt/extended_states",
16+
filePathMatches: (filePath) =>
17+
filePath === normalizePath("/var/lib/apt/extended_states"),
1518
callback: streamToString,
1619
};
1720

lib/inputs/distroless/static.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { normalize as normalizePath } from "path";
12
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
23
import { streamToString } from "../../stream-utils";
34

45
export const getDpkgPackageFileContentAction: ExtractAction = {
56
actionName: "dpkg",
6-
filePathMatches: (filePath) => filePath.startsWith("/var/lib/dpkg/status.d/"),
7+
filePathMatches: (filePath) =>
8+
filePath.startsWith(normalizePath("/var/lib/dpkg/status.d/")),
79
callback: streamToString, // TODO replace with a parser for apt data extractor
810
};
911

lib/inputs/os-release/static.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,62 @@
1+
import { normalize as normalizePath } from "path";
12
import { getContentAsString } from "../../extractor";
23
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
34
import { streamToString } from "../../stream-utils";
45
import { OsReleaseFilePath } from "../../types";
56

67
const getOsReleaseAction: ExtractAction = {
78
actionName: "os-release",
8-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Linux,
9+
filePathMatches: (filePath) =>
10+
filePath === normalizePath(OsReleaseFilePath.Linux),
911
callback: streamToString,
1012
};
1113

1214
const getFallbackOsReleaseAction: ExtractAction = {
1315
actionName: "os-release-fallback",
14-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.LinuxFallback,
16+
filePathMatches: (filePath) =>
17+
filePath === normalizePath(OsReleaseFilePath.LinuxFallback),
1518
callback: streamToString,
1619
};
1720

1821
const getLsbReleaseAction: ExtractAction = {
1922
actionName: "lsb-release",
20-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Lsb,
23+
filePathMatches: (filePath) =>
24+
filePath === normalizePath(OsReleaseFilePath.Lsb),
2125
callback: streamToString,
2226
};
2327

2428
const getDebianVersionAction: ExtractAction = {
2529
actionName: "debian-version",
26-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Debian,
30+
filePathMatches: (filePath) =>
31+
filePath === normalizePath(OsReleaseFilePath.Debian),
2732
callback: streamToString,
2833
};
2934

3035
const getAlpineReleaseAction: ExtractAction = {
3136
actionName: "alpine-release",
32-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Alpine,
37+
filePathMatches: (filePath) =>
38+
filePath === normalizePath(OsReleaseFilePath.Alpine),
3339
callback: streamToString,
3440
};
3541

3642
const getRedHatReleaseAction: ExtractAction = {
3743
actionName: "redhat-release",
38-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.RedHat,
44+
filePathMatches: (filePath) =>
45+
filePath === normalizePath(OsReleaseFilePath.RedHat),
3946
callback: streamToString,
4047
};
4148

4249
const getCentosReleaseAction: ExtractAction = {
4350
actionName: "centos-release",
44-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Centos,
51+
filePathMatches: (filePath) =>
52+
filePath === normalizePath(OsReleaseFilePath.Centos),
4553
callback: streamToString,
4654
};
4755

4856
const getOracleReleaseAction: ExtractAction = {
4957
actionName: "oracle-release",
50-
filePathMatches: (filePath) => filePath === OsReleaseFilePath.Oracle,
58+
filePathMatches: (filePath) =>
59+
filePath === normalizePath(OsReleaseFilePath.Oracle),
5160
callback: streamToString,
5261
};
5362

lib/inputs/rpm/static.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getPackages } from "@snyk/rpm-parser";
22
import * as Debug from "debug";
3+
import { normalize as normalizePath } from "path";
34
import { getContentAsBuffer } from "../../extractor";
45
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
56
import { streamToBuffer } from "../../stream-utils";
@@ -8,7 +9,8 @@ const debug = Debug("snyk");
89

910
export const getRpmDbFileContentAction: ExtractAction = {
1011
actionName: "rpm-db",
11-
filePathMatches: (filePath) => filePath === "/var/lib/rpm/Packages",
12+
filePathMatches: (filePath) =>
13+
filePath === normalizePath("/var/lib/rpm/Packages"),
1214
callback: streamToBuffer,
1315
};
1416

0 commit comments

Comments
 (0)