Skip to content

Commit 241f813

Browse files
authored
Merge pull request #652 from snyk/feat/support-unspecified-archive-type
feat: support scan with unspecified archive type
2 parents 0557591 + 7ecc5f6 commit 241f813

File tree

8 files changed

+98
-18
lines changed

8 files changed

+98
-18
lines changed

.snyk

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
22
version: v1.25.0
33
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
4-
ignore: {}
4+
ignore:
5+
SNYK-JS-TARFS-9535930:
6+
- '*':
7+
reason: "Temporary ignore"
8+
expires: "2025-05-31T23:59:59.000Z"
59
patch: {}

lib/extractor/index.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,9 @@ export async function extractImageContent(
127127
} catch (err) {
128128
if (err instanceof InvalidArchiveError) {
129129
// fallback to the other extractor if layer extraction failed
130-
if (imageType === ImageType.DockerArchive) {
131-
extractor = extractors.get(ImageType.OciArchive) as ArchiveExtractor;
132-
} else {
133-
extractor = extractors.get(ImageType.DockerArchive) as ArchiveExtractor;
134-
}
135-
archiveContent = await extractor.getLayersAndManifest();
130+
[archiveContent, extractor] = await extractArchiveContentFallback(
131+
extractors,
132+
);
136133
} else {
137134
throw err;
138135
}
@@ -152,6 +149,22 @@ export async function extractImageContent(
152149
};
153150
}
154151

152+
async function extractArchiveContentFallback(
153+
extractors: Map<ImageType, ArchiveExtractor>,
154+
): Promise<[ExtractedLayersAndManifest, ArchiveExtractor]> {
155+
for (const extractor of extractors.values()) {
156+
try {
157+
return [await extractor.getLayersAndManifest(), extractor];
158+
} catch (error) {
159+
continue;
160+
}
161+
}
162+
163+
throw new InvalidArchiveError(
164+
`Unsupported archive type. Please use a Docker archive, OCI image layout, or Kaniko-compatible tarball.`,
165+
);
166+
}
167+
155168
export function getRootFsLayersFromConfig(imageConfig: ImageConfig): string[] {
156169
try {
157170
return imageConfig.rootfs.diff_ids;

lib/image-type.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ export function getImageType(targetImage: string): ImageType {
1414
return ImageType.KanikoArchive;
1515

1616
default:
17-
return ImageType.Identifier;
17+
if (imageIdentifier.endsWith(".tar")) {
18+
return ImageType.UnspecifiedArchiveType;
19+
} else {
20+
return ImageType.Identifier;
21+
}
1822
}
1923
}
2024

@@ -24,11 +28,15 @@ export function getArchivePath(targetImage: string): string {
2428
"oci-archive",
2529
"kaniko-archive",
2630
];
31+
2732
for (const archiveType of possibleArchiveTypes) {
2833
if (targetImage.startsWith(archiveType)) {
2934
return normalizePath(targetImage.substring(`${archiveType}:`.length));
3035
}
3136
}
37+
if (targetImage.endsWith(".tar")) {
38+
return normalizePath(targetImage);
39+
}
3240

3341
throw new Error(
3442
'The provided archive path is missing a prefix, for example "docker-archive:", "oci-archive:" or "kaniko-archive"',

lib/scan.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export async function scan(
9595
case ImageType.DockerArchive:
9696
case ImageType.OciArchive:
9797
case ImageType.KanikoArchive:
98+
case ImageType.UnspecifiedArchiveType:
9899
return localArchiveAnalysis(
99100
targetImage,
100101
imageType,

lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "./dockerfile/types";
88

99
export enum ImageType {
10+
UnspecifiedArchiveType, // "e.g /path/nginx.tar"
1011
Identifier, // e.g. "nginx:latest"
1112
DockerArchive = "docker-archive", // e.g. "docker-archive:/tmp/nginx.tar"
1213
OciArchive = "oci-archive", // e.g. "oci-archive:/tmp/nginx.tar"
3.5 KB
Binary file not shown.

test/lib/extractor/extractor.spec.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,6 @@ describe("extractImageContent", () => {
8989
extractImageContent(ImageType.KanikoArchive, fixture, [], opts),
9090
).resolves.not.toThrow();
9191
});
92-
93-
it("fails to extract the archive when image type is not set", async () => {
94-
await expect(extractImageContent(0, fixture, [], opts)).rejects.toThrow();
95-
});
96-
97-
it("fails to extract the archive when image type is set to docker-archive", async () => {
98-
await expect(
99-
extractImageContent(ImageType.DockerArchive, fixture, [], opts),
100-
).rejects.toThrow();
101-
});
10292
});
10393

10494
describe("Images pulled & saved with Docker Engine >= 25.x", () => {

test/system/plugin.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,70 @@
11
import { DepGraph } from "@snyk/dep-graph";
22
import * as plugin from "../../lib";
3+
import { getFixture } from "../util";
34

45
describe("plugin", () => {
6+
describe("image is scanned when no image type is specified", () => {
7+
it("docker image.tar is scanned successfully when image type is not specified", async () => {
8+
const fixturePath = getFixture([
9+
"../fixtures/docker-archives",
10+
"alpine-arm64.tar",
11+
]);
12+
const imagePath = `${fixturePath}`;
13+
14+
const pluginResult = await plugin.scan({
15+
path: imagePath,
16+
});
17+
const depGraph: DepGraph = pluginResult.scanResults[0].facts.find(
18+
(fact) => fact.type === "depGraph",
19+
)!.data;
20+
});
21+
22+
it("kaniko image.tar is scanned successfully when image type is not specified", async () => {
23+
const fixturePath = getFixture([
24+
"../fixtures/kaniko-archives",
25+
"kaniko-busybox.tar",
26+
]);
27+
const imagePath = `${fixturePath}`;
28+
29+
const pluginResult = await plugin.scan({
30+
path: imagePath,
31+
});
32+
const depGraph: DepGraph = pluginResult.scanResults[0].facts.find(
33+
(fact) => fact.type === "depGraph",
34+
)!.data;
35+
});
36+
it("oci image.tar is scanned successfully when image type is not specified", async () => {
37+
const fixturePath = getFixture([
38+
"../fixtures/docker-oci-archives",
39+
"busybox.amd64.tar",
40+
]);
41+
const imagePath = `${fixturePath}`;
42+
43+
const pluginResult = await plugin.scan({
44+
path: imagePath,
45+
});
46+
const depGraph: DepGraph = pluginResult.scanResults[0].facts.find(
47+
(fact) => fact.type === "depGraph",
48+
)!.data;
49+
});
50+
51+
it("fails to extract the archive when the archive type is not supported", async () => {
52+
const fixturePath = getFixture([
53+
"../fixtures/docker-oci-archives",
54+
"unsupported-image.tar",
55+
]);
56+
const imagePath = `${fixturePath}`;
57+
58+
await expect(
59+
plugin.scan({
60+
path: imagePath,
61+
}),
62+
).rejects.toThrow(
63+
"Unsupported archive type. Please use a Docker archive, OCI image layout, or Kaniko-compatible tarball.",
64+
);
65+
});
66+
});
67+
568
describe("docker-archive image type throws on bad files", () => {
669
test("throws when a file does not exists", async () => {
770
const path = "docker-archive:missing-path";

0 commit comments

Comments
 (0)