@cyclonedx/cdxgen 12.1.1 → 12.1.2

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/lib/cli/index.js CHANGED
@@ -4379,7 +4379,7 @@ export async function createGoBom(path, options) {
4379
4379
  ) {
4380
4380
  for (const f of sortedGomodFiles) {
4381
4381
  const basePath = dirname(f);
4382
- // Ignore vendor packages
4382
+ // Ignore vendor packages and test fixtures
4383
4383
  if (
4384
4384
  basePath.includes("/vendor/") ||
4385
4385
  basePath.includes("/build/") ||
@@ -4391,13 +4391,14 @@ export async function createGoBom(path, options) {
4391
4391
  if (DEBUG_MODE) {
4392
4392
  console.log("Executing go list -deps in", basePath);
4393
4393
  }
4394
+ // TODO: Replacing this with -json gives us more interesting data points such as GoFiles, Imports, and Deps
4394
4395
  let result = safeSpawnSync(
4395
4396
  "go",
4396
4397
  [
4397
4398
  "list",
4398
4399
  "-deps",
4399
4400
  "-f",
4400
- "'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}} {{.Main}}{{end}}'",
4401
+ "'{{with .Module}}{{.Path}}|{{.Version}}|{{.Indirect}}|{{.GoMod}}|{{.GoVersion}}|{{.Main}}|{{.Time}}|{{.Deprecated}}|{{.GoModSum}}|{{.Dir}}{{end}}'",
4401
4402
  "./...",
4402
4403
  ],
4403
4404
  {
@@ -6407,6 +6408,7 @@ export async function createRubyBom(path, options) {
6407
6408
  const gemLockExcludeList = (options.exclude || []).concat([
6408
6409
  "**/vendor/bundle/ruby/**/Gemfile.lock",
6409
6410
  "**/test/data/**/Gemfile*.lock",
6411
+ "**/.rbenv/versions/**/Gemfile.lock",
6410
6412
  ]);
6411
6413
  if (!hasAnyProjectType(["oci"], options, false)) {
6412
6414
  excludeList.push("**/vendor/bundle/**");
@@ -6509,6 +6511,7 @@ export async function createRubyBom(path, options) {
6509
6511
  }
6510
6512
  }
6511
6513
  // Parsing .gemspec files would help us get more metadata such as description, authors, licenses etc
6514
+ let rootGemspecComponent;
6512
6515
  if (gemspecFiles.length) {
6513
6516
  if (!gemLockFiles.length && !hasAnyProjectType(["oci"], options, false)) {
6514
6517
  console.log(
@@ -6522,8 +6525,33 @@ export async function createRubyBom(path, options) {
6522
6525
  if (gpkgList.length) {
6523
6526
  pkgList = pkgList.concat(gpkgList);
6524
6527
  pkgList = trimComponents(pkgList);
6528
+ if (
6529
+ !rootGemspecComponent &&
6530
+ dirname(resolve(f)) === resolve(path) &&
6531
+ gpkgList[0]?.name
6532
+ ) {
6533
+ rootGemspecComponent = gpkgList[0];
6534
+ }
6525
6535
  }
6526
6536
  }
6537
+ if (
6538
+ rootGemspecComponent &&
6539
+ !("project-name" in options) &&
6540
+ options.projectName === undefined
6541
+ ) {
6542
+ parentComponent.name = rootGemspecComponent.name;
6543
+ parentComponent.version = rootGemspecComponent.version || "latest";
6544
+ const parentPurl = new PackageURL(
6545
+ "gem",
6546
+ parentComponent.group,
6547
+ parentComponent.name,
6548
+ parentComponent.version,
6549
+ null,
6550
+ null,
6551
+ ).toString();
6552
+ parentComponent["bom-ref"] = decodeURIComponent(parentPurl);
6553
+ parentComponent["purl"] = parentPurl;
6554
+ }
6527
6555
  }
6528
6556
  if (rootList.length) {
6529
6557
  dependencies = mergeDependencies(
@@ -0,0 +1,11 @@
1
+ import { assert, it } from "poku";
2
+
3
+ import { checkLicenses } from "../../bin/licenses.js";
4
+
5
+ it("checks dependency licenses", async () => {
6
+ assert.equal(
7
+ checkLicenses(),
8
+ 0,
9
+ "There shouldn't have been license discrepancies",
10
+ );
11
+ });
@@ -7257,18 +7257,20 @@ export async function parseGoModData(goModData, gosumMap) {
7257
7257
  isTool = false;
7258
7258
  continue;
7259
7259
  }
7260
- if (l.includes("tool ")) {
7260
+ if (l.includes("tool ") || isTool) {
7261
7261
  continue;
7262
7262
  }
7263
- if (isTool) {
7263
+ if (l.startsWith("toolchain ")) {
7264
+ const toolchainVer = l.split(" ").pop().trim();
7265
+ parentComponent.properties = [
7266
+ { name: "cdx:go:toolchain", value: toolchainVer },
7267
+ ];
7264
7268
  continue;
7265
7269
  }
7266
-
7267
7270
  // Skip go.mod file headers, whitespace, and/or comments
7268
7271
  if (
7269
7272
  l.startsWith("go ") ||
7270
7273
  //TODO: should toolchain be considered as a dependency
7271
- l.startsWith("toolchain ") ||
7272
7274
  l.includes(")") ||
7273
7275
  l.trim() === "" ||
7274
7276
  l.trim().startsWith("//")
@@ -7378,14 +7380,17 @@ export async function parseGoListDep(rawOutput, gosumMap) {
7378
7380
  .split("\n")
7379
7381
  .filter((p) => p.trim().replace(/["']/g, "").length);
7380
7382
  for (const l of pkgs) {
7381
- const verArr = l.trim().replace(/["']/g, "").split(" ");
7383
+ const verArr = l.trim().replace(/["']/g, "").split("|");
7382
7384
  if (verArr && verArr.length >= 5) {
7383
7385
  const key = `${verArr[0]}-${verArr[1]}`;
7384
7386
  // Filter duplicates
7385
7387
  if (!keys_cache[key]) {
7386
7388
  keys_cache[key] = key;
7387
7389
  const version = verArr[1];
7388
- const gosumHash = gosumMap[`${verArr[0]}@${version}`];
7390
+ let gosumHash = gosumMap[`${verArr[0]}@${version}`];
7391
+ if (!gosumHash && verArr.length >= 8 && verArr[8]?.length) {
7392
+ gosumHash = `sha256-${verArr[8].replace("h1:", "")}`;
7393
+ }
7389
7394
  const component = await getGoPkgComponent(
7390
7395
  "",
7391
7396
  verArr[0],
@@ -7412,6 +7417,43 @@ export async function parseGoListDep(rawOutput, gosumMap) {
7412
7417
  value: verArr[2],
7413
7418
  },
7414
7419
  ];
7420
+ if (
7421
+ verArr.length >= 6 &&
7422
+ verArr[6]?.length &&
7423
+ verArr[6] !== "<nil>"
7424
+ ) {
7425
+ component.properties.push({
7426
+ name: "cdx:go:creation_time",
7427
+ value: verArr[6],
7428
+ });
7429
+ }
7430
+ if (verArr.length >= 7 && verArr[7]?.length) {
7431
+ component.properties.push({
7432
+ name: "cdx:go:deprecated",
7433
+ value: verArr[7],
7434
+ });
7435
+ }
7436
+ if (verArr.length >= 9 && verArr[9]?.length) {
7437
+ component.properties.push({
7438
+ name: "cdx:go:local_dir",
7439
+ value: verArr[9],
7440
+ });
7441
+ if (safeExistsSync(join(verArr[9], "LICENSE"))) {
7442
+ const licenseText = readFileSync(join(verArr[9], "LICENSE"), {
7443
+ encoding: "utf-8",
7444
+ });
7445
+ if (licenseText?.length) {
7446
+ component.licenses = [
7447
+ {
7448
+ license: {
7449
+ name: "CUSTOM",
7450
+ text: { contentType: "text/plain", content: licenseText },
7451
+ },
7452
+ },
7453
+ ];
7454
+ }
7455
+ }
7456
+ }
7415
7457
  if (verArr.length > 5 && verArr[5] === "true") {
7416
7458
  parentComponent = component;
7417
7459
  } else {
@@ -16059,29 +16101,24 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
16059
16101
  }
16060
16102
  }
16061
16103
  } else if (l.includes("dependency(")) {
16062
- let tmpA = l.split("dependency(");
16063
- if (tmpA?.length) {
16064
- if (!l.includes("_dependency") && !l.includes(".dependency")) {
16065
- tmpA = tmpA[tmpA.length - 1]
16066
- .split(", ")
16067
- .map((v) => v.replace(/['" )]/g, ""));
16068
- if (tmpA.length) {
16069
- name_list.push(tmpA[0]);
16070
- if (tmpA.length > 2 && tmpA[1].startsWith("version")) {
16071
- const tmpB = tmpA[1].split("version:");
16072
- if (tmpB && tmpB.length === 2) {
16073
- if (tmpB[1].includes(">") || tmpB[1].includes("<")) {
16074
- // We have a version specifier
16075
- versionSpecifiersMap[tmpA[0]] = tmpB[1];
16076
- } else if (
16077
- /^\d+/.test(tmpB[1]) &&
16078
- !tmpB[1].includes("${") &&
16079
- !tmpB[1].startsWith("@")
16080
- ) {
16081
- // We have a valid version
16082
- versionsMap[tmpA[0]] = tmpB[1];
16083
- }
16084
- }
16104
+ if (!l.includes("_dependency") && !l.includes(".dependency")) {
16105
+ const depMatch = l.match(/dependency\(\s*['"]?([^'",)\s]+)['"]?/);
16106
+ const depName = depMatch?.[1]?.trim();
16107
+ if (depName) {
16108
+ name_list.push(depName);
16109
+ const versionMatch = l.match(/version\s*:\s*['"]?([^'",)\s]+)['"]?/);
16110
+ const depVersion = versionMatch?.[1]?.trim();
16111
+ if (depVersion) {
16112
+ if (depVersion.includes(">") || depVersion.includes("<")) {
16113
+ // We have a version specifier
16114
+ versionSpecifiersMap[depName] = depVersion;
16115
+ } else if (
16116
+ /^\d+/.test(depVersion) &&
16117
+ !depVersion.includes("${") &&
16118
+ !depVersion.startsWith("@")
16119
+ ) {
16120
+ // We have a valid version
16121
+ versionsMap[depName] = depVersion;
16085
16122
  }
16086
16123
  }
16087
16124
  }
@@ -1475,7 +1475,7 @@ describe("go data with licenses", () => {
1475
1475
  });
1476
1476
 
1477
1477
  it("parse go list dependencies", async () => {
1478
- const retMap = await parseGoListDep(
1478
+ let retMap = await parseGoListDep(
1479
1479
  readFileSync("./test/data/golist-dep.txt", { encoding: "utf-8" }),
1480
1480
  {},
1481
1481
  );
@@ -1502,6 +1502,11 @@ it("parse go list dependencies", async () => {
1502
1502
  },
1503
1503
  ],
1504
1504
  });
1505
+ retMap = await parseGoListDep(
1506
+ readFileSync("./test/data/golist-dep3.txt", { encoding: "utf-8" }),
1507
+ {},
1508
+ );
1509
+ assert.deepStrictEqual(retMap.pkgList.length, 291);
1505
1510
  });
1506
1511
 
1507
1512
  it("parse go mod graph", async () => {
@@ -7645,6 +7650,21 @@ it("parseCmakeLikeFile tests", () => {
7645
7650
  version: "20230125.1",
7646
7651
  });
7647
7652
  assert.deepStrictEqual(retMap.pkgList.length, 2);
7653
+
7654
+ retMap = parseCmakeLikeFile(
7655
+ "./test/data/meson-empty-dependency.build",
7656
+ "conan",
7657
+ );
7658
+ assert.deepStrictEqual(retMap.parentComponent, {
7659
+ "bom-ref": "pkg:conan/empty-dep-test",
7660
+ group: "",
7661
+ name: "empty-dep-test",
7662
+ purl: "pkg:conan/empty-dep-test",
7663
+ type: "application",
7664
+ version: "",
7665
+ });
7666
+ assert.deepStrictEqual(retMap.pkgList.length, 1);
7667
+ assert.deepStrictEqual(retMap.pkgList[0].name, "threads");
7648
7668
  });
7649
7669
 
7650
7670
  it("parseMakeDFile tests", () => {
@@ -81,6 +81,8 @@ export const validateBom = (bomJson) => {
81
81
  *
82
82
  * @param {object} bomJson Bom json object
83
83
  */
84
+ const PLACEHOLDER_COMPONENT_NAMES = new Set(["app", "application", "project"]);
85
+
84
86
  export const validateMetadata = (bomJson) => {
85
87
  const errorList = [];
86
88
  const warningsList = [];
@@ -107,6 +109,14 @@ export const validateMetadata = (bomJson) => {
107
109
  "Version is missing for metadata.component. Pass the version using --project-version argument.",
108
110
  );
109
111
  }
112
+ const metadataName = bomJson.metadata.component.name
113
+ ?.trim()
114
+ .toLowerCase();
115
+ if (metadataName && PLACEHOLDER_COMPONENT_NAMES.has(metadataName)) {
116
+ warningsList.push(
117
+ `metadata.component.name appears to be a placeholder ('${bomJson.metadata.component.name}'). Pass --project-name to set the correct parent component name.`,
118
+ );
119
+ }
110
120
  // Is the same component getting repeated inside the components block
111
121
  if (bomJson.metadata.component.components?.length) {
112
122
  for (const comp of bomJson.metadata.component.components) {
@@ -198,6 +208,27 @@ export const validatePurls = (bomJson) => {
198
208
  }
199
209
  // Catch the trivy version hack that removes the epoch from version
200
210
  const qualifiers = purlObj.qualifiers || {};
211
+ if (
212
+ Object.keys(qualifiers).length &&
213
+ [
214
+ "cargo",
215
+ "cocoapods",
216
+ "composer",
217
+ "cran",
218
+ "github",
219
+ "golang",
220
+ "hackage",
221
+ "nuget",
222
+ "opam",
223
+ "pub",
224
+ "qpkg",
225
+ "swift",
226
+ ].includes(purlObj.type)
227
+ ) {
228
+ warningsList.push(
229
+ `Qualifiers are usually not expected for the PURL type: ${purlObj.type}. Purl: ${comp.purl}, Qualifiers: ${Object.keys(qualifiers).join(", ")}.`,
230
+ );
231
+ }
201
232
  if (
202
233
  qualifiers.epoch &&
203
234
  !comp.version.startsWith(`${qualifiers.epoch}:`)
@@ -7,7 +7,14 @@ import {
7
7
  statSync,
8
8
  } from "node:fs";
9
9
  import { arch as _arch, platform as _platform, homedir } from "node:os";
10
- import { basename, delimiter, dirname, join, resolve } from "node:path";
10
+ import {
11
+ basename,
12
+ delimiter,
13
+ dirname,
14
+ join,
15
+ relative,
16
+ resolve,
17
+ } from "node:path";
11
18
  import process from "node:process";
12
19
 
13
20
  import { PackageURL } from "packageurl-js";
@@ -27,9 +34,11 @@ import {
27
34
  safeMkdirSync,
28
35
  safeSpawnSync,
29
36
  } from "../helpers/utils.js";
37
+ import { getDirs } from "./containerutils.js";
30
38
 
31
39
  const dirName = dirNameStr;
32
40
  const isWin = _platform() === "win32";
41
+ const OS_PURL_TYPES = ["deb", "apk", "rpm", "alpm", "qpkg"];
33
42
 
34
43
  function isMusl() {
35
44
  const result = safeSpawnSync("ldd", ["--version"]);
@@ -444,7 +453,20 @@ export async function getOSPackages(src, imageConfig) {
444
453
  const allTypes = new Set();
445
454
  const bundledSdks = new Set();
446
455
  const bundledRuntimes = new Set();
447
- const binPaths = extractPathEnv(imageConfig?.Env);
456
+ let binPaths = extractPathEnv(imageConfig?.Env);
457
+ if (!binPaths?.length && DEBUG_MODE) {
458
+ const rootBinPaths = getDirs(src, "{sbin,bin}", true, false);
459
+ const usrBinPaths = getDirs(
460
+ src,
461
+ "/{app,opt,usr,home}/**/{sbin,bin}",
462
+ true,
463
+ true,
464
+ );
465
+ binPaths = binPaths
466
+ .concat(rootBinPaths)
467
+ .concat(usrBinPaths)
468
+ .map((f) => relative(src, f));
469
+ }
448
470
  if (TRIVY_BIN) {
449
471
  let imageType = "image";
450
472
  const trivyCacheDir = join(homedir(), ".cache", "trivy");
@@ -607,7 +629,7 @@ export async function getOSPackages(src, imageConfig) {
607
629
  } catch (_err) {
608
630
  // continue regardless of error
609
631
  }
610
- if (group === "") {
632
+ if (group === "" && OS_PURL_TYPES.includes(purlObj["type"])) {
611
633
  try {
612
634
  if (purlObj?.namespace && purlObj.namespace !== "") {
613
635
  group = purlObj.namespace;
@@ -955,16 +977,16 @@ const retrieveDependencies = (tmpDependencies, origBomRef, comp) => {
955
977
  const tmpPurl = PackageURL.fromString(d.replace("none", compPurl.type));
956
978
  tmpPurl.type = compPurl.type;
957
979
  // FIXME: Check if this hack is still needed with the latest trivy
958
- if (["deb", "apk", "rpm", "alpm", "qpkg"].includes(compPurl.type)) {
980
+ if (OS_PURL_TYPES.includes(compPurl.type)) {
959
981
  tmpPurl.namespace = compPurl.namespace;
960
- }
961
- tmpPurl.qualifiers = tmpPurl.qualifiers || {};
962
- if (compPurl.qualifiers) {
963
- if (compPurl.qualifiers.distro_name) {
964
- tmpPurl.qualifiers.distro_name = compPurl.qualifiers.distro_name;
965
- }
966
- if (compPurl.qualifiers.distro) {
967
- tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
982
+ tmpPurl.qualifiers = tmpPurl.qualifiers || {};
983
+ if (compPurl.qualifiers) {
984
+ if (compPurl.qualifiers.distro_name) {
985
+ tmpPurl.qualifiers.distro_name = compPurl.qualifiers.distro_name;
986
+ }
987
+ if (compPurl.qualifiers.distro) {
988
+ tmpPurl.qualifiers.distro = compPurl.qualifiers.distro;
989
+ }
968
990
  }
969
991
  }
970
992
  if (tmpPurl.qualifiers) {
@@ -0,0 +1,68 @@
1
+ import { lstatSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { globSync } from "glob";
5
+
6
+ import { safeExistsSync } from "../helpers/utils.js";
7
+
8
+ /**
9
+ * Method to get all dirs matching a name
10
+ *
11
+ * @param {string} dirPath Root directory for search
12
+ * @param {string} dirName Directory name
13
+ * @param {boolean} hidden Include hidden directories and files. Default: false
14
+ * @param {boolean} recurse Recurse. Default: false
15
+ */
16
+ export const getDirs = (dirPath, dirName, hidden = false, recurse = true) => {
17
+ try {
18
+ return globSync(`${recurse ? "**" : ""}${dirName}`, {
19
+ cwd: dirPath,
20
+ absolute: true,
21
+ nocase: true,
22
+ nodir: false,
23
+ follow: false,
24
+ dot: hidden,
25
+ });
26
+ } catch (_err) {
27
+ return [];
28
+ }
29
+ };
30
+
31
+ function flatten(lists) {
32
+ return lists.reduce((a, b) => a.concat(b), []);
33
+ }
34
+
35
+ function getDirectories(srcpath) {
36
+ if (safeExistsSync(srcpath)) {
37
+ return readdirSync(srcpath)
38
+ .map((file) => join(srcpath, file))
39
+ .filter((path) => {
40
+ try {
41
+ return statSync(path).isDirectory();
42
+ } catch (_e) {
43
+ return false;
44
+ }
45
+ });
46
+ }
47
+ return [];
48
+ }
49
+
50
+ export const getOnlyDirs = (srcpath, dirName) => {
51
+ return [
52
+ srcpath,
53
+ ...flatten(
54
+ getDirectories(srcpath)
55
+ .map((p) => {
56
+ try {
57
+ if (safeExistsSync(p) && lstatSync(p).isDirectory()) {
58
+ return getOnlyDirs(p, dirName);
59
+ }
60
+ return [];
61
+ } catch (_err) {
62
+ return [];
63
+ }
64
+ })
65
+ .filter((p) => p !== undefined),
66
+ ),
67
+ ].filter((d) => d.endsWith(dirName));
68
+ };
@@ -6,7 +6,6 @@ import {
6
6
  readdirSync,
7
7
  readFileSync,
8
8
  rmSync,
9
- statSync,
10
9
  } from "node:fs";
11
10
  import { platform as _platform, userInfo as _userInfo, homedir } from "node:os";
12
11
  import { basename, join, resolve, win32 } from "node:path";
@@ -14,7 +13,6 @@ import process from "node:process";
14
13
  import stream from "node:stream/promises";
15
14
  import { URL } from "node:url";
16
15
 
17
- import { globSync } from "glob";
18
16
  import got from "got";
19
17
  import { x } from "tar";
20
18
 
@@ -27,6 +25,7 @@ import {
27
25
  safeMkdirSync,
28
26
  safeSpawnSync,
29
27
  } from "../helpers/utils.js";
28
+ import { getDirs, getOnlyDirs } from "./containerutils.js";
30
29
 
31
30
  export const isWin = _platform() === "win32";
32
31
  export const DOCKER_HUB_REGISTRY = "docker.io";
@@ -156,67 +155,6 @@ export function detectRancherDesktop() {
156
155
 
157
156
  // Cache the registry auth keys
158
157
  const registry_auth_keys = {};
159
-
160
- /**
161
- * Method to get all dirs matching a name
162
- *
163
- * @param {string} dirPath Root directory for search
164
- * @param {string} dirName Directory name
165
- */
166
- export const getDirs = (dirPath, dirName, hidden = false, recurse = true) => {
167
- try {
168
- return globSync(recurse ? "**/" : `${dirName}`, {
169
- cwd: dirPath,
170
- absolute: true,
171
- nocase: true,
172
- nodir: false,
173
- follow: false,
174
- dot: hidden,
175
- });
176
- } catch (_err) {
177
- return [];
178
- }
179
- };
180
-
181
- function flatten(lists) {
182
- return lists.reduce((a, b) => a.concat(b), []);
183
- }
184
-
185
- function getDirectories(srcpath) {
186
- if (safeExistsSync(srcpath)) {
187
- return readdirSync(srcpath)
188
- .map((file) => join(srcpath, file))
189
- .filter((path) => {
190
- try {
191
- return statSync(path).isDirectory();
192
- } catch (_e) {
193
- return false;
194
- }
195
- });
196
- }
197
- return [];
198
- }
199
-
200
- export const getOnlyDirs = (srcpath, dirName) => {
201
- return [
202
- srcpath,
203
- ...flatten(
204
- getDirectories(srcpath)
205
- .map((p) => {
206
- try {
207
- if (safeExistsSync(p) && lstatSync(p).isDirectory()) {
208
- return getOnlyDirs(p, dirName);
209
- }
210
- return [];
211
- } catch (_err) {
212
- return [];
213
- }
214
- })
215
- .filter((p) => p !== undefined),
216
- ),
217
- ].filter((d) => d.endsWith(dirName));
218
- };
219
-
220
158
  const REQUEST_TIMEOUT_SECS = 60000;
221
159
  const getDefaultOptions = (forRegistry) => {
222
160
  let authTokenSet = false;
@@ -844,6 +782,33 @@ function handleAbsolutePath(entry) {
844
782
  }
845
783
  }
846
784
 
785
+ /**
786
+ * Filter out problematic files, paths, and devices during tar extraction.
787
+ */
788
+ function tarFilter(path, entry) {
789
+ const name = basename(path);
790
+ if (name.startsWith(".wh.")) {
791
+ return false;
792
+ }
793
+ const ext = win32.extname(name).toLowerCase();
794
+ if (MEDIA_EXTENSIONS.has(ext)) {
795
+ return false;
796
+ }
797
+ return !(
798
+ EXTRACT_EXCLUDE_PATHS.some((p) => path.includes(p)) ||
799
+ EXTRACT_EXCLUDE_TYPES.has(entry.type)
800
+ );
801
+ }
802
+
803
+ function handleTarWarning(code, message) {
804
+ if (code === "TAR_ENTRY_INFO" || code === "TAR_LONGLINK") {
805
+ return;
806
+ }
807
+ if (DEBUG_MODE) {
808
+ console.log(code, message);
809
+ }
810
+ }
811
+
847
812
  // These paths are known to cause extract errors
848
813
  const EXTRACT_EXCLUDE_PATHS = [
849
814
  "etc/machine-id",
@@ -918,29 +883,9 @@ export const extractTar = async (fullImageName, dir, options) => {
918
883
  C: dir,
919
884
  portable: true,
920
885
  unlink: true,
921
- onwarn: (code, message) => {
922
- if (code === "TAR_ENTRY_INFO" || code === "TAR_LONGLINK") {
923
- return;
924
- }
925
- if (DEBUG_MODE) {
926
- console.log(code, message);
927
- }
928
- },
886
+ onwarn: handleTarWarning,
929
887
  onReadEntry: handleAbsolutePath,
930
- filter: (path, entry) => {
931
- const name = basename(path);
932
- if (name.startsWith(".wh.")) {
933
- return false;
934
- }
935
- const ext = win32.extname(name).toLowerCase();
936
- if (MEDIA_EXTENSIONS.has(ext)) {
937
- return false;
938
- }
939
- return !(
940
- EXTRACT_EXCLUDE_PATHS.some((p) => path.includes(p)) ||
941
- EXTRACT_EXCLUDE_TYPES.has(entry.type)
942
- );
943
- },
888
+ filter: tarFilter,
944
889
  }),
945
890
  );
946
891
  return true;
@@ -1238,19 +1183,17 @@ export const exportImage = async (fullImageName, options) => {
1238
1183
  await stream.pipeline(
1239
1184
  client.stream(`images/${fullImageName}/get`),
1240
1185
  x({
1241
- sync: true,
1186
+ sync: false,
1242
1187
  preserveOwner: false,
1243
1188
  noMtime: true,
1244
1189
  noChmod: true,
1245
1190
  strict: !NON_STRICT_TAR_EXTRACT,
1246
1191
  C: tempDir,
1247
1192
  portable: true,
1248
- onwarn: (code, message) => {
1249
- if (DEBUG_MODE) {
1250
- console.log(code, message);
1251
- }
1252
- },
1193
+ unlink: true,
1194
+ onwarn: handleTarWarning,
1253
1195
  onReadEntry: handleAbsolutePath,
1196
+ filter: tarFilter,
1254
1197
  }),
1255
1198
  );
1256
1199
  } catch (_err) {
@@ -1267,12 +1210,9 @@ export const exportImage = async (fullImageName, options) => {
1267
1210
  strict: !NON_STRICT_TAR_EXTRACT,
1268
1211
  C: tempDir,
1269
1212
  portable: true,
1270
- onwarn: (code, message) => {
1271
- if (DEBUG_MODE) {
1272
- console.log(code, message);
1273
- }
1274
- },
1213
+ onwarn: handleTarWarning,
1275
1214
  onReadEntry: handleAbsolutePath,
1215
+ filter: tarFilter,
1276
1216
  }),
1277
1217
  );
1278
1218
  } catch (_err) {