@cyclonedx/cdxgen 12.1.2 → 12.1.4
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/README.md +11 -9
- package/bin/cdxgen.js +1 -1
- package/lib/cli/index.js +9 -5
- package/lib/evinser/evinser.js +2 -8
- package/lib/helpers/display.js +1 -1
- package/lib/helpers/envcontext.js +10 -2
- package/lib/helpers/utils.js +462 -86
- package/lib/helpers/utils.poku.js +179 -2
- package/lib/helpers/validator.js +8 -5
- package/lib/managers/docker.getConnection.poku.js +61 -0
- package/lib/managers/docker.js +36 -23
- package/lib/parsers/iri.js +1 -2
- package/lib/server/server.js +164 -34
- package/lib/server/server.poku.js +232 -10
- package/lib/stages/postgen/annotator.js +281 -3
- package/lib/stages/postgen/postgen.js +4 -7
- package/lib/third-party/arborist/lib/diff.js +1 -1
- package/lib/third-party/arborist/lib/node.js +1 -1
- package/lib/third-party/arborist/lib/yarn-lock.js +1 -1
- package/package.json +22 -328
- package/types/bin/dependencies.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +39 -39
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +19 -19
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +14 -14
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +1 -1
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/db.d.ts +2 -2
- package/types/lib/helpers/db.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +2 -2
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +14 -14
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/logger.d.ts +1 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +4 -2
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +103 -88
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/validator.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +2 -2
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +2 -2
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/iri.d.ts +6 -6
- package/types/lib/parsers/iri.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +14 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts +3 -3
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +5 -5
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +6 -6
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts +4 -3
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/can-place-dep.d.ts +5 -5
- package/types/lib/third-party/arborist/lib/can-place-dep.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +4 -4
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/diff.d.ts +3 -3
- package/types/lib/third-party/arborist/lib/diff.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/edge.d.ts +2 -2
- package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +1 -1
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/inventory.d.ts +3 -2
- package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/link.d.ts +10 -7
- package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/node.d.ts +8 -8
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/optional-set.d.ts +1 -1
- package/types/lib/third-party/arborist/lib/optional-set.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/override-set.d.ts +3 -3
- package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/peer-entry-sets.d.ts +1 -1
- package/types/lib/third-party/arborist/lib/peer-entry-sets.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/place-dep.d.ts +3 -3
- package/types/lib/third-party/arborist/lib/place-dep.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +7 -7
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +1 -1
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -1
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +4 -3
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -1
- package/bin/dependencies.js +0 -131
- package/bin/licenses.js +0 -78
- package/lib/helpers/dependencies.poku.js +0 -11
- package/lib/helpers/licenses.poku.js +0 -11
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +0 -34
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +0 -1
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +0 -24
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +0 -1
- package/types/lib/third-party/arborist/lib/tracker.d.ts +0 -13
- package/types/lib/third-party/arborist/lib/tracker.d.ts.map +0 -1
package/lib/helpers/utils.js
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
satisfies,
|
|
49
49
|
valid,
|
|
50
50
|
} from "semver";
|
|
51
|
+
import { v4 as uuidv4 } from "uuid";
|
|
51
52
|
import { xml2js } from "xml-js";
|
|
52
53
|
import { parse as _load } from "yaml";
|
|
53
54
|
|
|
@@ -160,6 +161,20 @@ export function safeSpawnSync(command, args, options) {
|
|
|
160
161
|
if (!options.timeout) {
|
|
161
162
|
options.timeout = TIMEOUT_MS;
|
|
162
163
|
}
|
|
164
|
+
// Check for -S for python invocations in secure mode
|
|
165
|
+
if (command.includes("python") && (!args?.length || args[0] !== "-S")) {
|
|
166
|
+
if (isSecureMode) {
|
|
167
|
+
console.warn(
|
|
168
|
+
"\x1b[1;35mNotice: Running python command without '-S' argument. This is a bug in cdxgen. Please report with an example repo here https://github.com/cdxgen/cdxgen/issues.\x1b[0m",
|
|
169
|
+
);
|
|
170
|
+
} else if (process.env?.CDXGEN_IN_CONTAINER === "true") {
|
|
171
|
+
console.log("Running python command without '-S' argument.");
|
|
172
|
+
} else {
|
|
173
|
+
console.warn(
|
|
174
|
+
"\x1b[1;35mNotice: Running python command without '-S' argument. Only run cdxgen in trusted directories to prevent auto-executing local scripts.\x1b[0m",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
traceLog("spawn", { command, args, ...options });
|
|
164
179
|
commandsExecuted.add(command);
|
|
165
180
|
// Fix for DEP0190 warning
|
|
@@ -1033,18 +1048,10 @@ export function getKnownLicense(licenseUrl, pkg) {
|
|
|
1033
1048
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1034
1049
|
}
|
|
1035
1050
|
}
|
|
1036
|
-
if (
|
|
1037
|
-
akLic.urlIncludes &&
|
|
1038
|
-
licenseUrl &&
|
|
1039
|
-
licenseUrl.includes(akLic.urlIncludes)
|
|
1040
|
-
) {
|
|
1051
|
+
if (akLic.urlIncludes && licenseUrl?.includes(akLic.urlIncludes)) {
|
|
1041
1052
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1042
1053
|
}
|
|
1043
|
-
if (
|
|
1044
|
-
akLic.urlEndswith &&
|
|
1045
|
-
licenseUrl &&
|
|
1046
|
-
licenseUrl.endsWith(akLic.urlEndswith)
|
|
1047
|
-
) {
|
|
1054
|
+
if (akLic.urlEndswith && licenseUrl?.endsWith(akLic.urlEndswith)) {
|
|
1048
1055
|
return { id: akLic.license, name: akLic.licenseName };
|
|
1049
1056
|
}
|
|
1050
1057
|
}
|
|
@@ -1265,7 +1272,7 @@ export async function parsePkgJson(pkgJsonFile, simple = false) {
|
|
|
1265
1272
|
// continue regardless of error
|
|
1266
1273
|
}
|
|
1267
1274
|
}
|
|
1268
|
-
if (!simple && shouldFetchLicense() && pkgList
|
|
1275
|
+
if (!simple && shouldFetchLicense() && pkgList?.length) {
|
|
1269
1276
|
if (DEBUG_MODE) {
|
|
1270
1277
|
console.log(
|
|
1271
1278
|
`About to fetch license information for ${pkgList.length} packages in parsePkgJson`,
|
|
@@ -1455,6 +1462,40 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1455
1462
|
value: "true",
|
|
1456
1463
|
});
|
|
1457
1464
|
}
|
|
1465
|
+
// Detect version spoofing by comparing the version in the lockfile with the version in package.json
|
|
1466
|
+
if (node.path && safeExistsSync(join(node.path, "package.json"))) {
|
|
1467
|
+
try {
|
|
1468
|
+
const diskPkgStr = readFileSync(
|
|
1469
|
+
join(node.path, "package.json"),
|
|
1470
|
+
"utf8",
|
|
1471
|
+
);
|
|
1472
|
+
const diskPkg = JSON.parse(diskPkgStr);
|
|
1473
|
+
if (!diskPkg.name || diskPkg.name !== node.packageName) {
|
|
1474
|
+
console.warn(
|
|
1475
|
+
`\x1b[1;35mWARNING: Package name spoofing detected for ${node.packageName}! Lockfile says ${node.packageName}, but disk says ${diskPkg.name}.\x1b[0m`,
|
|
1476
|
+
);
|
|
1477
|
+
if (diskPkg.name) {
|
|
1478
|
+
pkg.properties.push({
|
|
1479
|
+
name: "cdx:npm:nameMismatchError",
|
|
1480
|
+
value: `${diskPkg.name} used instead of ${node.packageName}`,
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
if (!diskPkg.version || diskPkg.version !== node.version) {
|
|
1485
|
+
console.warn(
|
|
1486
|
+
`\x1b[1;35mWARNING: Package version spoofing detected for ${node.packageName}! Lockfile says ${node.version}, but disk says ${diskPkg.version}.\x1b[0m`,
|
|
1487
|
+
);
|
|
1488
|
+
if (diskPkg.version) {
|
|
1489
|
+
pkg.properties.push({
|
|
1490
|
+
name: "cdx:npm:versionMismatchError",
|
|
1491
|
+
value: `${diskPkg.version} used instead of ${node.version}`,
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
} catch (_err) {
|
|
1496
|
+
// ignore
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1458
1499
|
if (node?.inBundle) {
|
|
1459
1500
|
pkg.properties.push({
|
|
1460
1501
|
name: "cdx:npm:inBundle",
|
|
@@ -1786,7 +1827,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
1786
1827
|
options,
|
|
1787
1828
|
));
|
|
1788
1829
|
|
|
1789
|
-
if (shouldFetchLicense() && pkgList
|
|
1830
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
1790
1831
|
if (DEBUG_MODE) {
|
|
1791
1832
|
console.log(
|
|
1792
1833
|
`About to fetch license information for ${pkgList.length} packages in parsePkgLock`,
|
|
@@ -2419,7 +2460,7 @@ export async function parseYarnLock(
|
|
|
2419
2460
|
}
|
|
2420
2461
|
}
|
|
2421
2462
|
|
|
2422
|
-
if (shouldFetchLicense() && pkgList
|
|
2463
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
2423
2464
|
if (DEBUG_MODE) {
|
|
2424
2465
|
console.log(
|
|
2425
2466
|
`About to fetch license information for ${pkgList.length} packages in parseYarnLock`,
|
|
@@ -2496,7 +2537,7 @@ export async function parseNodeShrinkwrap(swFile) {
|
|
|
2496
2537
|
}
|
|
2497
2538
|
}
|
|
2498
2539
|
}
|
|
2499
|
-
if (shouldFetchLicense() && pkgList
|
|
2540
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
2500
2541
|
if (DEBUG_MODE) {
|
|
2501
2542
|
console.log(
|
|
2502
2543
|
`About to fetch license information for ${pkgList.length} packages in parseNodeShrinkwrap`,
|
|
@@ -3698,7 +3739,7 @@ export async function parsePnpmLock(
|
|
|
3698
3739
|
pkgList = await pnpmMetadata(pkgList, pnpmLock);
|
|
3699
3740
|
}
|
|
3700
3741
|
|
|
3701
|
-
if (shouldFetchLicense() && pkgList
|
|
3742
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3702
3743
|
if (DEBUG_MODE) {
|
|
3703
3744
|
console.log(
|
|
3704
3745
|
`About to fetch license information for ${pkgList.length} packages in parsePnpmLock`,
|
|
@@ -3759,7 +3800,7 @@ export async function parseBowerJson(bowerJsonFile) {
|
|
|
3759
3800
|
// continue regardless of error
|
|
3760
3801
|
}
|
|
3761
3802
|
}
|
|
3762
|
-
if (shouldFetchLicense() && pkgList
|
|
3803
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3763
3804
|
if (DEBUG_MODE) {
|
|
3764
3805
|
console.log(
|
|
3765
3806
|
`About to fetch license information for ${pkgList.length} packages in parseBowerJson`,
|
|
@@ -3857,7 +3898,7 @@ export async function parseMinJs(minJsFile) {
|
|
|
3857
3898
|
// continue regardless of error
|
|
3858
3899
|
}
|
|
3859
3900
|
}
|
|
3860
|
-
if (shouldFetchLicense() && pkgList
|
|
3901
|
+
if (shouldFetchLicense() && pkgList?.length) {
|
|
3861
3902
|
if (DEBUG_MODE) {
|
|
3862
3903
|
console.log(
|
|
3863
3904
|
`About to fetch license information for ${pkgList.length} packages in parseMinJs`,
|
|
@@ -6132,6 +6173,9 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
6132
6173
|
).toString();
|
|
6133
6174
|
pkg.purl = purlString;
|
|
6134
6175
|
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
6176
|
+
if (parentComponent && pkg["bom-ref"] === parentComponent["bom-ref"]) {
|
|
6177
|
+
continue;
|
|
6178
|
+
}
|
|
6135
6179
|
pkg.evidence = {
|
|
6136
6180
|
identity: {
|
|
6137
6181
|
field: "purl",
|
|
@@ -6387,6 +6431,9 @@ export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
|
6387
6431
|
return await parseReqData(reqFile, null, fetchDepsInfo);
|
|
6388
6432
|
}
|
|
6389
6433
|
|
|
6434
|
+
const LICENSE_ID_COMMENTS_PATTERN =
|
|
6435
|
+
/^(Apache-2\.0|MIT|ISC|GPL-|LGPL-|BSD-[23]-Clause)/i;
|
|
6436
|
+
|
|
6390
6437
|
/**
|
|
6391
6438
|
* Method to parse requirements.txt file. Must only be used internally.
|
|
6392
6439
|
*
|
|
@@ -6434,6 +6481,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6434
6481
|
if (!l || l.startsWith("#") || l.startsWith("-")) {
|
|
6435
6482
|
continue;
|
|
6436
6483
|
}
|
|
6484
|
+
let comment = null;
|
|
6485
|
+
const commentMatch = l.match(/\s+#(.*)$/);
|
|
6486
|
+
if (commentMatch) {
|
|
6487
|
+
comment = commentMatch[1].trim();
|
|
6488
|
+
l = l.substring(0, commentMatch.index).trim();
|
|
6489
|
+
}
|
|
6437
6490
|
const properties = reqFile
|
|
6438
6491
|
? [
|
|
6439
6492
|
{
|
|
@@ -6482,6 +6535,21 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6482
6535
|
scope: compScope,
|
|
6483
6536
|
evidence,
|
|
6484
6537
|
};
|
|
6538
|
+
if (comment) {
|
|
6539
|
+
apkg.licenses = comment
|
|
6540
|
+
.split("/")
|
|
6541
|
+
.map((c) => {
|
|
6542
|
+
const licenseObj = {};
|
|
6543
|
+
const cId = c.trim();
|
|
6544
|
+
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
6545
|
+
licenseObj.id = cId;
|
|
6546
|
+
} else {
|
|
6547
|
+
return undefined;
|
|
6548
|
+
}
|
|
6549
|
+
return { license: licenseObj };
|
|
6550
|
+
})
|
|
6551
|
+
.filter((l) => l !== undefined);
|
|
6552
|
+
}
|
|
6485
6553
|
if (extras && extras.length > 0) {
|
|
6486
6554
|
properties.push({
|
|
6487
6555
|
name: "cdx:pypi:extras",
|
|
@@ -6533,6 +6601,21 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
6533
6601
|
scope: compScope,
|
|
6534
6602
|
evidence,
|
|
6535
6603
|
};
|
|
6604
|
+
if (comment) {
|
|
6605
|
+
apkg.licenses = comment
|
|
6606
|
+
.split("/")
|
|
6607
|
+
.map((c) => {
|
|
6608
|
+
const licenseObj = {};
|
|
6609
|
+
const cId = c.trim();
|
|
6610
|
+
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
6611
|
+
licenseObj.id = cId;
|
|
6612
|
+
} else {
|
|
6613
|
+
return undefined;
|
|
6614
|
+
}
|
|
6615
|
+
return { license: licenseObj };
|
|
6616
|
+
})
|
|
6617
|
+
.filter((l) => l !== undefined);
|
|
6618
|
+
}
|
|
6536
6619
|
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
6537
6620
|
properties.push({
|
|
6538
6621
|
name: "cdx:pypi:versionSpecifiers",
|
|
@@ -9877,82 +9960,250 @@ export function parseGitHubWorkflowData(f) {
|
|
|
9877
9960
|
return pkgList;
|
|
9878
9961
|
}
|
|
9879
9962
|
const lines = ghwData.split("\n");
|
|
9963
|
+
// workflow-related values
|
|
9964
|
+
const workflowName = yamlObj.name || path.basename(f, path.extname(f));
|
|
9965
|
+
const workflowTriggers = yamlObj.on || yamlObj.true;
|
|
9966
|
+
const workflowPermissions = yamlObj.permissions || {};
|
|
9967
|
+
let hasWritePermissions = analyzePermissions(workflowPermissions);
|
|
9968
|
+
// GitHub of course supports strings such as "write-all" to make it easy to create supply-chain attacks.
|
|
9969
|
+
if (
|
|
9970
|
+
(typeof workflowPermissions === "string" ||
|
|
9971
|
+
workflowPermissions instanceof String) &&
|
|
9972
|
+
workflowPermissions.includes("write")
|
|
9973
|
+
) {
|
|
9974
|
+
hasWritePermissions = true;
|
|
9975
|
+
}
|
|
9976
|
+
const workflowConcurrency = yamlObj.concurrency || {};
|
|
9977
|
+
const _workflowEnv = yamlObj.env || {};
|
|
9978
|
+
const hasIdTokenWrite = workflowPermissions?.["id-token"] === "write";
|
|
9880
9979
|
for (const jobName of Object.keys(yamlObj.jobs)) {
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9980
|
+
const job = yamlObj.jobs[jobName];
|
|
9981
|
+
if (!job.steps) {
|
|
9982
|
+
continue;
|
|
9983
|
+
}
|
|
9984
|
+
// job-related values
|
|
9985
|
+
const jobRunner = job["runs-on"] || "unknown";
|
|
9986
|
+
const jobEnvironment = job.environment?.name || job.environment || "";
|
|
9987
|
+
const jobTimeout = job["timeout-minutes"] || null;
|
|
9988
|
+
const jobPermissions = job.permissions || {};
|
|
9989
|
+
const jobServices = job.services ? Object.keys(job.services) : [];
|
|
9990
|
+
let jobNeeds = job.needs || [];
|
|
9991
|
+
if (!Array.isArray(jobNeeds)) {
|
|
9992
|
+
jobNeeds = [jobNeeds];
|
|
9993
|
+
}
|
|
9994
|
+
const _jobIf = job.if || "";
|
|
9995
|
+
const _jobStrategy = job.strategy ? JSON.stringify(job.strategy) : "";
|
|
9996
|
+
const jobHasWritePermissions = analyzePermissions(jobPermissions);
|
|
9997
|
+
for (const step of job.steps) {
|
|
9998
|
+
if (step.uses) {
|
|
9999
|
+
const tmpA = step.uses.split("@");
|
|
10000
|
+
if (tmpA.length !== 2) {
|
|
10001
|
+
continue;
|
|
10002
|
+
}
|
|
10003
|
+
const groupName = tmpA[0];
|
|
10004
|
+
let name = groupName;
|
|
10005
|
+
let group = "";
|
|
10006
|
+
const tagOrCommit = tmpA[1];
|
|
10007
|
+
let version = tagOrCommit;
|
|
10008
|
+
const tmpB = groupName.split("/");
|
|
10009
|
+
if (tmpB.length >= 2) {
|
|
10010
|
+
name = tmpB.pop();
|
|
10011
|
+
group = tmpB.join("/");
|
|
10012
|
+
} else if (tmpB.length === 1) {
|
|
10013
|
+
name = tmpB[0];
|
|
10014
|
+
group = "";
|
|
10015
|
+
}
|
|
10016
|
+
const versionPinningType = getVersionPinningType(tagOrCommit);
|
|
10017
|
+
const isShaPinned = versionPinningType === "sha";
|
|
10018
|
+
const _isTagPinned = versionPinningType === "tag";
|
|
10019
|
+
const _isBranchRef = versionPinningType === "branch";
|
|
10020
|
+
let lineNum = -1;
|
|
10021
|
+
const stepLineMatch = ghwData.indexOf(step.uses);
|
|
10022
|
+
if (stepLineMatch >= 0) {
|
|
10023
|
+
lineNum = ghwData.substring(0, stepLineMatch).split("\n").length - 1;
|
|
10024
|
+
}
|
|
10025
|
+
if (lineNum >= 0 && lines[lineNum]) {
|
|
10026
|
+
const line = lines[lineNum];
|
|
10027
|
+
const commentMatch = line.match(/#\s*v?([0-9]+(?:\.[0-9]+)*)/);
|
|
10028
|
+
if (commentMatch?.[1]) {
|
|
10029
|
+
version = commentMatch[1];
|
|
9887
10030
|
}
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
if (
|
|
9895
|
-
|
|
9896
|
-
group = tmpB.join("/");
|
|
9897
|
-
} else if (tmpB.length === 1) {
|
|
9898
|
-
name = tmpB[0];
|
|
9899
|
-
group = "";
|
|
10031
|
+
}
|
|
10032
|
+
const key = `${group}-${name}-${version}`;
|
|
10033
|
+
let confidence = 0.6;
|
|
10034
|
+
if (!keys_cache[key] && name && version) {
|
|
10035
|
+
keys_cache[key] = key;
|
|
10036
|
+
let fullName = name;
|
|
10037
|
+
if (group.length) {
|
|
10038
|
+
fullName = `${group}/${name}`;
|
|
9900
10039
|
}
|
|
9901
|
-
let
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
10040
|
+
let purl = `pkg:github/${fullName}@${version}`;
|
|
10041
|
+
if (tagOrCommit && version !== tagOrCommit) {
|
|
10042
|
+
const qualifierDesc = tagOrCommit.startsWith("v")
|
|
10043
|
+
? "tag"
|
|
10044
|
+
: "commit";
|
|
10045
|
+
purl = `${purl}?${qualifierDesc}=${tagOrCommit}`;
|
|
10046
|
+
confidence = 0.7;
|
|
9906
10047
|
}
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
10048
|
+
const properties = [
|
|
10049
|
+
{ name: "SrcFile", value: f },
|
|
10050
|
+
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10051
|
+
{ name: "cdx:github:job:name", value: jobName },
|
|
10052
|
+
{
|
|
10053
|
+
name: "cdx:github:job:runner",
|
|
10054
|
+
value: Array.isArray(jobRunner) ? jobRunner.join(",") : jobRunner,
|
|
10055
|
+
},
|
|
10056
|
+
{ name: "cdx:github:action:uses", value: step.uses },
|
|
10057
|
+
{
|
|
10058
|
+
name: "cdx:github:action:versionPinningType",
|
|
10059
|
+
value: versionPinningType,
|
|
10060
|
+
},
|
|
10061
|
+
{
|
|
10062
|
+
name: "cdx:github:action:isShaPinned",
|
|
10063
|
+
value: isShaPinned.toString(),
|
|
10064
|
+
},
|
|
10065
|
+
];
|
|
10066
|
+
if (step.name) {
|
|
10067
|
+
properties.push({ name: "cdx:github:step:name", value: step.name });
|
|
9913
10068
|
}
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
10069
|
+
if (step.if) {
|
|
10070
|
+
properties.push({
|
|
10071
|
+
name: "cdx:github:step:condition",
|
|
10072
|
+
value: step.if,
|
|
10073
|
+
});
|
|
10074
|
+
}
|
|
10075
|
+
if (step["continue-on-error"]) {
|
|
10076
|
+
properties.push({
|
|
10077
|
+
name: "cdx:github:step:continueOnError",
|
|
10078
|
+
value: "true",
|
|
10079
|
+
});
|
|
10080
|
+
}
|
|
10081
|
+
if (step.timeout) {
|
|
10082
|
+
properties.push({
|
|
10083
|
+
name: "cdx:github:step:timeout",
|
|
10084
|
+
value: step.timeout.toString(),
|
|
10085
|
+
});
|
|
10086
|
+
}
|
|
10087
|
+
if (jobEnvironment) {
|
|
10088
|
+
properties.push({
|
|
10089
|
+
name: "cdx:github:job:environment",
|
|
10090
|
+
value: jobEnvironment,
|
|
10091
|
+
});
|
|
10092
|
+
}
|
|
10093
|
+
if (jobTimeout) {
|
|
10094
|
+
properties.push({
|
|
10095
|
+
name: "cdx:github:job:timeoutMinutes",
|
|
10096
|
+
value: jobTimeout.toString(),
|
|
10097
|
+
});
|
|
10098
|
+
}
|
|
10099
|
+
if (jobHasWritePermissions) {
|
|
10100
|
+
properties.push({
|
|
10101
|
+
name: "cdx:github:job:hasWritePermissions",
|
|
10102
|
+
value: "true",
|
|
10103
|
+
});
|
|
10104
|
+
}
|
|
10105
|
+
if (jobServices.length > 0) {
|
|
10106
|
+
properties.push({
|
|
10107
|
+
name: "cdx:github:job:services",
|
|
10108
|
+
value: jobServices.join(","),
|
|
10109
|
+
});
|
|
10110
|
+
}
|
|
10111
|
+
if (jobNeeds.length > 0) {
|
|
10112
|
+
properties.push({
|
|
10113
|
+
name: "cdx:github:job:needs",
|
|
10114
|
+
value: jobNeeds.join(","),
|
|
10115
|
+
});
|
|
10116
|
+
}
|
|
10117
|
+
if (hasWritePermissions) {
|
|
10118
|
+
properties.push({
|
|
10119
|
+
name: "cdx:github:workflow:hasWritePermissions",
|
|
10120
|
+
value: "true",
|
|
10121
|
+
});
|
|
10122
|
+
}
|
|
10123
|
+
if (hasIdTokenWrite) {
|
|
10124
|
+
properties.push({
|
|
10125
|
+
name: "cdx:github:workflow:hasIdTokenWrite",
|
|
10126
|
+
value: "true",
|
|
10127
|
+
});
|
|
10128
|
+
}
|
|
10129
|
+
if (workflowConcurrency?.group) {
|
|
10130
|
+
properties.push({
|
|
10131
|
+
name: "cdx:github:workflow:concurrencyGroup",
|
|
10132
|
+
value: workflowConcurrency.group,
|
|
10133
|
+
});
|
|
10134
|
+
}
|
|
10135
|
+
if (group?.startsWith("github/") || group === "actions") {
|
|
10136
|
+
properties.push({ name: "cdx:actions:isOfficial", value: "true" });
|
|
10137
|
+
}
|
|
10138
|
+
if (group?.startsWith("github/")) {
|
|
10139
|
+
properties.push({ name: "cdx:actions:isVerified", value: "true" });
|
|
10140
|
+
}
|
|
10141
|
+
if (workflowTriggers) {
|
|
10142
|
+
const triggers =
|
|
10143
|
+
typeof workflowTriggers === "string"
|
|
10144
|
+
? workflowTriggers
|
|
10145
|
+
: Object.keys(workflowTriggers).join(",");
|
|
10146
|
+
properties.push({
|
|
10147
|
+
name: "cdx:github:workflow:triggers",
|
|
10148
|
+
value: triggers,
|
|
10149
|
+
});
|
|
10150
|
+
}
|
|
10151
|
+
pkgList.push({
|
|
10152
|
+
group,
|
|
10153
|
+
name,
|
|
10154
|
+
version,
|
|
10155
|
+
purl,
|
|
10156
|
+
properties,
|
|
10157
|
+
evidence: {
|
|
10158
|
+
identity: {
|
|
10159
|
+
field: "purl",
|
|
10160
|
+
confidence,
|
|
10161
|
+
methods: [
|
|
10162
|
+
{
|
|
10163
|
+
technique: "source-code-analysis",
|
|
10164
|
+
confidence,
|
|
10165
|
+
value: f,
|
|
10166
|
+
},
|
|
10167
|
+
],
|
|
9934
10168
|
},
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
10169
|
+
},
|
|
10170
|
+
});
|
|
10171
|
+
}
|
|
10172
|
+
}
|
|
10173
|
+
if (step.run) {
|
|
10174
|
+
const runLineNum = ghwData.indexOf(step.run);
|
|
10175
|
+
const runLine =
|
|
10176
|
+
runLineNum >= 0
|
|
10177
|
+
? ghwData.substring(0, runLineNum).split("\n").length
|
|
10178
|
+
: -1;
|
|
10179
|
+
const pkgCommands = extractPackageManagerCommands(step.run);
|
|
10180
|
+
for (const pkgCmd of pkgCommands) {
|
|
10181
|
+
const key = `run-${pkgCmd.name}-${pkgCmd.version || "latest"}`;
|
|
10182
|
+
if (!keys_cache[key]) {
|
|
10183
|
+
keys_cache[key] = key;
|
|
9942
10184
|
pkgList.push({
|
|
9943
|
-
group,
|
|
9944
|
-
name,
|
|
9945
|
-
version,
|
|
9946
|
-
|
|
9947
|
-
|
|
10185
|
+
group: "",
|
|
10186
|
+
name: pkgCmd.name,
|
|
10187
|
+
version: pkgCmd.version || undefined,
|
|
10188
|
+
scope: "excluded",
|
|
10189
|
+
"bom-ref": uuidv4(),
|
|
10190
|
+
description: key,
|
|
10191
|
+
properties: [
|
|
10192
|
+
{ name: "SrcFile", value: f },
|
|
10193
|
+
{ name: "cdx:github:workflow:name", value: workflowName },
|
|
10194
|
+
{ name: "cdx:github:job:name", value: jobName },
|
|
10195
|
+
{ name: "cdx:github:step:type", value: "run" },
|
|
10196
|
+
{ name: "cdx:github:step:command", value: pkgCmd.command },
|
|
10197
|
+
{ name: "cdx:github:run:line", value: runLine.toString() },
|
|
10198
|
+
],
|
|
9948
10199
|
evidence: {
|
|
9949
10200
|
identity: {
|
|
9950
10201
|
field: "purl",
|
|
9951
|
-
confidence,
|
|
10202
|
+
confidence: 0.5,
|
|
9952
10203
|
methods: [
|
|
9953
10204
|
{
|
|
9954
10205
|
technique: "source-code-analysis",
|
|
9955
|
-
confidence,
|
|
10206
|
+
confidence: 0.5,
|
|
9956
10207
|
value: f,
|
|
9957
10208
|
},
|
|
9958
10209
|
],
|
|
@@ -9967,6 +10218,103 @@ export function parseGitHubWorkflowData(f) {
|
|
|
9967
10218
|
return pkgList;
|
|
9968
10219
|
}
|
|
9969
10220
|
|
|
10221
|
+
/**
|
|
10222
|
+
* Analyze permissions for write access.
|
|
10223
|
+
*
|
|
10224
|
+
* Refer to https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions
|
|
10225
|
+
*/
|
|
10226
|
+
function analyzePermissions(permissions) {
|
|
10227
|
+
if (!permissions || typeof permissions !== "object") {
|
|
10228
|
+
return false;
|
|
10229
|
+
}
|
|
10230
|
+
const writePermissions = [
|
|
10231
|
+
"actions",
|
|
10232
|
+
"artifact-metadata",
|
|
10233
|
+
"attestations",
|
|
10234
|
+
"checks",
|
|
10235
|
+
"contents",
|
|
10236
|
+
"deployments",
|
|
10237
|
+
"id-token",
|
|
10238
|
+
"models",
|
|
10239
|
+
"discussions",
|
|
10240
|
+
"packages",
|
|
10241
|
+
"pages",
|
|
10242
|
+
"actions",
|
|
10243
|
+
"deployments",
|
|
10244
|
+
"issues",
|
|
10245
|
+
"pull-requests",
|
|
10246
|
+
"security-events",
|
|
10247
|
+
"statuses",
|
|
10248
|
+
];
|
|
10249
|
+
for (const perm of writePermissions) {
|
|
10250
|
+
if (permissions[perm] === "write") {
|
|
10251
|
+
return true;
|
|
10252
|
+
}
|
|
10253
|
+
}
|
|
10254
|
+
return false;
|
|
10255
|
+
}
|
|
10256
|
+
|
|
10257
|
+
/**
|
|
10258
|
+
* Determine version pinning type for security assessment
|
|
10259
|
+
*/
|
|
10260
|
+
function getVersionPinningType(versionRef) {
|
|
10261
|
+
if (!versionRef) {
|
|
10262
|
+
return "unknown";
|
|
10263
|
+
}
|
|
10264
|
+
if (/^[a-f0-9]{40}$/.test(versionRef)) {
|
|
10265
|
+
return "sha";
|
|
10266
|
+
}
|
|
10267
|
+
if (/^[a-f0-9]{7,}$/.test(versionRef)) {
|
|
10268
|
+
return "sha";
|
|
10269
|
+
}
|
|
10270
|
+
if (
|
|
10271
|
+
versionRef === "main" ||
|
|
10272
|
+
versionRef === "master" ||
|
|
10273
|
+
versionRef.includes("/")
|
|
10274
|
+
) {
|
|
10275
|
+
return "branch";
|
|
10276
|
+
}
|
|
10277
|
+
return "tag";
|
|
10278
|
+
}
|
|
10279
|
+
|
|
10280
|
+
/**
|
|
10281
|
+
* Extract package manager commands from run steps
|
|
10282
|
+
*/
|
|
10283
|
+
function extractPackageManagerCommands(runScript) {
|
|
10284
|
+
const commands = [];
|
|
10285
|
+
if (!runScript) {
|
|
10286
|
+
return commands;
|
|
10287
|
+
}
|
|
10288
|
+
const patterns = [
|
|
10289
|
+
{ regex: /npm\s+(install|i|ci)\s+([^&\n|;]+)/g, name: "npm", type: "npm" },
|
|
10290
|
+
{
|
|
10291
|
+
regex: /yarn\s+(add|install)\s+([^&\n|;]+)/g,
|
|
10292
|
+
name: "yarn",
|
|
10293
|
+
type: "yarn",
|
|
10294
|
+
},
|
|
10295
|
+
{ regex: /pip\s+(install)\s+([^&\n|;]+)/g, name: "pip", type: "pip" },
|
|
10296
|
+
{ regex: /pip3\s+(install)\s+([^&\n|;]+)/g, name: "pip3", type: "pip" },
|
|
10297
|
+
{ regex: /gem\s+(install)\s+([^&\n|;]+)/g, name: "gem", type: "gem" },
|
|
10298
|
+
{ regex: /go\s+(get|install)\s+([^&\n|;]+)/g, name: "go", type: "go" },
|
|
10299
|
+
{
|
|
10300
|
+
regex: /cargo\s+(install|add)\s+([^&\n|;]+)/g,
|
|
10301
|
+
name: "cargo",
|
|
10302
|
+
type: "cargo",
|
|
10303
|
+
},
|
|
10304
|
+
];
|
|
10305
|
+
for (const pattern of patterns) {
|
|
10306
|
+
let match;
|
|
10307
|
+
while ((match = pattern.regex.exec(runScript)) !== null) {
|
|
10308
|
+
commands.push({
|
|
10309
|
+
name: pattern.name,
|
|
10310
|
+
command: match[0],
|
|
10311
|
+
version: null,
|
|
10312
|
+
});
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
return commands;
|
|
10316
|
+
}
|
|
10317
|
+
|
|
9970
10318
|
export function parseCloudBuildData(cbwData) {
|
|
9971
10319
|
const pkgList = [];
|
|
9972
10320
|
const keys_cache = {};
|
|
@@ -10833,6 +11181,9 @@ export function parseCsPkgData(pkgData, pkgFile) {
|
|
|
10833
11181
|
export function getPropertyGroupTextNodes(propsFiles) {
|
|
10834
11182
|
const matches = {};
|
|
10835
11183
|
for (const f of propsFiles) {
|
|
11184
|
+
if (!f) {
|
|
11185
|
+
continue;
|
|
11186
|
+
}
|
|
10836
11187
|
let projects;
|
|
10837
11188
|
try {
|
|
10838
11189
|
const data = readFileSync(f, { encoding: "utf-8" });
|
|
@@ -11623,9 +11974,7 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
11623
11974
|
assetData.dependencies[aversion][adep].resolved;
|
|
11624
11975
|
} else if (
|
|
11625
11976
|
aversion.includes("/") &&
|
|
11626
|
-
assetData.dependencies[aversionNoRuntime]
|
|
11627
|
-
assetData.dependencies[aversionNoRuntime][adep] &&
|
|
11628
|
-
assetData.dependencies[aversionNoRuntime][adep].resolved
|
|
11977
|
+
assetData.dependencies[aversionNoRuntime]?.[adep]?.resolved
|
|
11629
11978
|
) {
|
|
11630
11979
|
adepResolvedVersion =
|
|
11631
11980
|
assetData.dependencies[aversionNoRuntime][adep].resolved;
|
|
@@ -12695,7 +13044,7 @@ export async function collectMvnDependencies(
|
|
|
12695
13044
|
}
|
|
12696
13045
|
|
|
12697
13046
|
// Clean up
|
|
12698
|
-
if (cleanup && tempDir
|
|
13047
|
+
if (cleanup && tempDir?.startsWith(tmpdir()) && rmSync) {
|
|
12699
13048
|
rmSync(tempDir, { recursive: true, force: true });
|
|
12700
13049
|
}
|
|
12701
13050
|
return jarNSMapping;
|
|
@@ -15056,7 +15405,11 @@ export async function getPipFrozenTree(
|
|
|
15056
15405
|
thoughtLog(
|
|
15057
15406
|
"Let me create a new virtual environment for installing the packages with pip.",
|
|
15058
15407
|
);
|
|
15059
|
-
|
|
15408
|
+
const venvCreationArgs = ["-m", "venv", tempVenvDir];
|
|
15409
|
+
if (isSecureMode) {
|
|
15410
|
+
venvCreationArgs.unshift("-S");
|
|
15411
|
+
}
|
|
15412
|
+
result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
|
|
15060
15413
|
shell: isWin,
|
|
15061
15414
|
});
|
|
15062
15415
|
if (result.status !== 0 || result.error) {
|
|
@@ -15107,12 +15460,18 @@ export async function getPipFrozenTree(
|
|
|
15107
15460
|
"true",
|
|
15108
15461
|
"--local",
|
|
15109
15462
|
];
|
|
15463
|
+
if (isSecureMode) {
|
|
15464
|
+
poetryConfigArgs.unshift("-S");
|
|
15465
|
+
}
|
|
15110
15466
|
result = safeSpawnSync(PYTHON_CMD, poetryConfigArgs, {
|
|
15111
15467
|
cwd: basePath,
|
|
15112
15468
|
shell: isWin,
|
|
15113
15469
|
});
|
|
15114
15470
|
thoughtLog("Performing poetry install");
|
|
15115
15471
|
let poetryInstallArgs = ["-m", "poetry", "install", "-n", "--no-root"];
|
|
15472
|
+
if (isSecureMode) {
|
|
15473
|
+
poetryInstallArgs.unshift("-S");
|
|
15474
|
+
}
|
|
15116
15475
|
// Attempt to perform poetry install
|
|
15117
15476
|
result = safeSpawnSync(PYTHON_CMD, poetryInstallArgs, {
|
|
15118
15477
|
cwd: basePath,
|
|
@@ -15189,6 +15548,9 @@ export async function getPipFrozenTree(
|
|
|
15189
15548
|
"install",
|
|
15190
15549
|
"--disable-pip-version-check",
|
|
15191
15550
|
];
|
|
15551
|
+
if (isSecureMode) {
|
|
15552
|
+
pipInstallArgs.unshift("-S");
|
|
15553
|
+
}
|
|
15192
15554
|
// Requirements.txt could be called with any name so best to check for not setup.py and not pyproject.toml
|
|
15193
15555
|
if (
|
|
15194
15556
|
!reqOrSetupFile.endsWith("setup.py") &&
|
|
@@ -15509,7 +15871,11 @@ export function getPipTreeForPackages(
|
|
|
15509
15871
|
};
|
|
15510
15872
|
if (!process.env.VIRTUAL_ENV && !process.env.CONDA_PREFIX) {
|
|
15511
15873
|
// Create a virtual environment
|
|
15512
|
-
|
|
15874
|
+
const venvCreationArgs = ["-m", "venv", tempVenvDir];
|
|
15875
|
+
if (isSecureMode) {
|
|
15876
|
+
venvCreationArgs.unshift("-S");
|
|
15877
|
+
}
|
|
15878
|
+
result = safeSpawnSync(PYTHON_CMD, venvCreationArgs, {
|
|
15513
15879
|
shell: isWin,
|
|
15514
15880
|
});
|
|
15515
15881
|
if (result.status !== 0 || result.error) {
|
|
@@ -15532,6 +15898,9 @@ export function getPipTreeForPackages(
|
|
|
15532
15898
|
}
|
|
15533
15899
|
const python_cmd_for_tree = get_python_command_from_env(env);
|
|
15534
15900
|
let pipInstallArgs = ["-m", "pip", "install", "--disable-pip-version-check"];
|
|
15901
|
+
if (isSecureMode) {
|
|
15902
|
+
pipInstallArgs.unshift("-S");
|
|
15903
|
+
}
|
|
15535
15904
|
// Support for passing additional arguments to pip
|
|
15536
15905
|
// Eg: --python-version 3.10 --ignore-requires-python --no-warn-conflicts
|
|
15537
15906
|
if (process?.env?.PIP_INSTALL_ARGS) {
|
|
@@ -15713,6 +16082,7 @@ export async function addEvidenceForImports(
|
|
|
15713
16082
|
const aliases = group?.length
|
|
15714
16083
|
? [name, `${group}/${name}`, `@${group}/${name}`]
|
|
15715
16084
|
: [name];
|
|
16085
|
+
let isImported = false;
|
|
15716
16086
|
for (const alias of aliases) {
|
|
15717
16087
|
const all_includes = impPkgs.filter(
|
|
15718
16088
|
(find_pkg) =>
|
|
@@ -15761,6 +16131,7 @@ export async function addEvidenceForImports(
|
|
|
15761
16131
|
}
|
|
15762
16132
|
// Identify all the imported modules of a component
|
|
15763
16133
|
if (impPkgs.includes(alias) || all_includes.length) {
|
|
16134
|
+
isImported = true;
|
|
15764
16135
|
let importedModules = new Set();
|
|
15765
16136
|
pkg.scope = "required";
|
|
15766
16137
|
for (const subevidence of all_includes) {
|
|
@@ -15798,6 +16169,11 @@ export async function addEvidenceForImports(
|
|
|
15798
16169
|
}
|
|
15799
16170
|
break;
|
|
15800
16171
|
}
|
|
16172
|
+
if (impPkgs?.length > 0 && !isImported && DEBUG_MODE) {
|
|
16173
|
+
console.debug(
|
|
16174
|
+
`\x1b[1;35mNotice: Package ${pkg.name} has no usage in code. Check if it is needed.\x1b[0m`,
|
|
16175
|
+
);
|
|
16176
|
+
}
|
|
15801
16177
|
// Capture metadata such as description from local node_modules in deep mode
|
|
15802
16178
|
if (deep && !pkg.description && pkg.properties) {
|
|
15803
16179
|
let localNodeModulesPath;
|