@cyclonedx/cdxgen 12.3.2 → 12.3.3
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 +6 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +141 -39
- package/lib/cli/index.poku.js +579 -1
- package/lib/helpers/agentFormulationParser.js +6 -2
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/analyzer.js +38 -9
- package/lib/helpers/analyzer.poku.js +67 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +45 -22
- package/lib/helpers/display.poku.js +47 -60
- package/lib/helpers/mcpConfigParser.js +21 -5
- package/lib/helpers/mcpConfigParser.poku.js +39 -2
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +951 -40
- package/lib/helpers/utils.poku.js +882 -0
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/auditBom.poku.js +644 -2
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +29 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
package/lib/helpers/utils.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
mkdirSync,
|
|
12
12
|
mkdtempSync,
|
|
13
13
|
readFileSync,
|
|
14
|
+
realpathSync,
|
|
14
15
|
rmSync,
|
|
15
16
|
unlinkSync,
|
|
16
17
|
writeFileSync,
|
|
@@ -981,7 +982,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
981
982
|
dart: ["dart", "flutter", "pub"],
|
|
982
983
|
haskell: ["haskell", "hackage", "cabal"],
|
|
983
984
|
elixir: ["elixir", "hex", "mix"],
|
|
984
|
-
c: ["c", "cpp", "c++", "conan"],
|
|
985
|
+
c: ["c", "cpp", "c++", "conan", "collider"],
|
|
985
986
|
clojure: ["clojure", "edn", "clj", "leiningen"],
|
|
986
987
|
github: ["github", "actions"],
|
|
987
988
|
os: ["os", "osquery", "windows", "linux", "mac", "macos", "darwin"],
|
|
@@ -1014,7 +1015,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
1014
1015
|
"visionos",
|
|
1015
1016
|
],
|
|
1016
1017
|
binary: ["binary", "blint"],
|
|
1017
|
-
oci: ["docker", "oci", "container", "podman"],
|
|
1018
|
+
oci: ["docker", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
1018
1019
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
1019
1020
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
1020
1021
|
nix: ["nix", "nixos", "flake"],
|
|
@@ -2585,6 +2586,23 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
2585
2586
|
value: "true",
|
|
2586
2587
|
});
|
|
2587
2588
|
}
|
|
2589
|
+
const npmManifestSources = collectNpmManifestSources(node);
|
|
2590
|
+
if (npmManifestSources.length) {
|
|
2591
|
+
addComponentProperty(
|
|
2592
|
+
pkg,
|
|
2593
|
+
"cdx:npm:manifestSourceType",
|
|
2594
|
+
npmManifestSources
|
|
2595
|
+
.map((manifestSource) => manifestSource.type)
|
|
2596
|
+
.join(","),
|
|
2597
|
+
);
|
|
2598
|
+
addComponentProperty(
|
|
2599
|
+
pkg,
|
|
2600
|
+
"cdx:npm:manifestSource",
|
|
2601
|
+
npmManifestSources
|
|
2602
|
+
.map((manifestSource) => manifestSource.value)
|
|
2603
|
+
.join(","),
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2588
2606
|
// This getter method could fail with errors at times.
|
|
2589
2607
|
// Example Error: Invalid tag name "^>=6.0.0" of package "^>=6.0.0": Tags may not have any characters that encodeURIComponent encodes.
|
|
2590
2608
|
try {
|
|
@@ -6811,8 +6829,17 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
6811
6829
|
value: origName,
|
|
6812
6830
|
});
|
|
6813
6831
|
}
|
|
6814
|
-
|
|
6815
|
-
|
|
6832
|
+
const releaseEntries = body.releases?.[p.version]?.length
|
|
6833
|
+
? body.releases[p.version]
|
|
6834
|
+
: Array.isArray(body.urls)
|
|
6835
|
+
? body.urls
|
|
6836
|
+
: [];
|
|
6837
|
+
mergeExternalReferences(
|
|
6838
|
+
p,
|
|
6839
|
+
collectPypiReleaseExternalReferences(releaseEntries),
|
|
6840
|
+
);
|
|
6841
|
+
if (releaseEntries.length) {
|
|
6842
|
+
const digest = releaseEntries[0].digests;
|
|
6816
6843
|
if (digest["sha256"]) {
|
|
6817
6844
|
p._integrity = `sha256-${digest["sha256"]}`;
|
|
6818
6845
|
} else if (digest["md5"]) {
|
|
@@ -7043,6 +7070,287 @@ export async function parsePiplockData(lockData) {
|
|
|
7043
7070
|
return await getPyMetadata(pkgList, false);
|
|
7044
7071
|
}
|
|
7045
7072
|
|
|
7073
|
+
function addComponentProperty(component, name, value) {
|
|
7074
|
+
if (value === undefined || value === null || value === "" || !component) {
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
component.properties = component.properties || [];
|
|
7078
|
+
if (
|
|
7079
|
+
component.properties.some(
|
|
7080
|
+
(property) => property.name === name && property.value === value,
|
|
7081
|
+
)
|
|
7082
|
+
) {
|
|
7083
|
+
return;
|
|
7084
|
+
}
|
|
7085
|
+
component.properties.push({
|
|
7086
|
+
name,
|
|
7087
|
+
value,
|
|
7088
|
+
});
|
|
7089
|
+
}
|
|
7090
|
+
|
|
7091
|
+
const PYTHON_DIRECT_REFERENCE_PATTERN =
|
|
7092
|
+
/^([A-Za-z0-9_.-]+)(?:\[[^\]]+\])?\s*@\s*(\S+)$/;
|
|
7093
|
+
|
|
7094
|
+
function isWindowsAbsolutePath(value) {
|
|
7095
|
+
return /^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
7096
|
+
}
|
|
7097
|
+
|
|
7098
|
+
function createExternalReferenceKey(reference) {
|
|
7099
|
+
return JSON.stringify([
|
|
7100
|
+
reference.type,
|
|
7101
|
+
reference.url,
|
|
7102
|
+
reference.comment || "",
|
|
7103
|
+
]);
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
function classifyNpmManifestSource(spec) {
|
|
7107
|
+
if (typeof spec !== "string" || !spec.trim()) {
|
|
7108
|
+
return undefined;
|
|
7109
|
+
}
|
|
7110
|
+
const normalizedSpec = spec.trim();
|
|
7111
|
+
const lowerSpec = normalizedSpec.toLowerCase();
|
|
7112
|
+
if (
|
|
7113
|
+
lowerSpec.startsWith("git+") ||
|
|
7114
|
+
lowerSpec.startsWith("git://") ||
|
|
7115
|
+
lowerSpec.startsWith("github:") ||
|
|
7116
|
+
lowerSpec.startsWith("gitlab:") ||
|
|
7117
|
+
lowerSpec.startsWith("bitbucket:") ||
|
|
7118
|
+
lowerSpec.startsWith("gist:")
|
|
7119
|
+
) {
|
|
7120
|
+
return {
|
|
7121
|
+
type: "git",
|
|
7122
|
+
value: normalizedSpec,
|
|
7123
|
+
};
|
|
7124
|
+
}
|
|
7125
|
+
if (lowerSpec.startsWith("http://") || lowerSpec.startsWith("https://")) {
|
|
7126
|
+
return {
|
|
7127
|
+
type: "url",
|
|
7128
|
+
value: normalizedSpec,
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
if (
|
|
7132
|
+
lowerSpec.startsWith("file:") ||
|
|
7133
|
+
lowerSpec.startsWith("link:") ||
|
|
7134
|
+
lowerSpec.startsWith("workspace:") ||
|
|
7135
|
+
normalizedSpec.startsWith("./") ||
|
|
7136
|
+
normalizedSpec.startsWith("../") ||
|
|
7137
|
+
normalizedSpec.startsWith("/") ||
|
|
7138
|
+
isWindowsAbsolutePath(normalizedSpec)
|
|
7139
|
+
) {
|
|
7140
|
+
return {
|
|
7141
|
+
type: "path",
|
|
7142
|
+
value: normalizedSpec,
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
return undefined;
|
|
7146
|
+
}
|
|
7147
|
+
|
|
7148
|
+
function collectNpmManifestSources(node) {
|
|
7149
|
+
const manifestSources = [];
|
|
7150
|
+
const seen = new Set();
|
|
7151
|
+
if (!node?.edgesIn) {
|
|
7152
|
+
return manifestSources;
|
|
7153
|
+
}
|
|
7154
|
+
for (const edge of node.edgesIn) {
|
|
7155
|
+
const manifestSource = classifyNpmManifestSource(edge?.spec);
|
|
7156
|
+
if (!manifestSource) {
|
|
7157
|
+
continue;
|
|
7158
|
+
}
|
|
7159
|
+
const dedupeKey = `${manifestSource.type}|${manifestSource.value}`;
|
|
7160
|
+
if (seen.has(dedupeKey)) {
|
|
7161
|
+
continue;
|
|
7162
|
+
}
|
|
7163
|
+
seen.add(dedupeKey);
|
|
7164
|
+
manifestSources.push(manifestSource);
|
|
7165
|
+
}
|
|
7166
|
+
return manifestSources;
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7169
|
+
function normalizePythonDependencyKey(value) {
|
|
7170
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7171
|
+
return undefined;
|
|
7172
|
+
}
|
|
7173
|
+
return value.trim().toLowerCase().replaceAll("_", "-");
|
|
7174
|
+
}
|
|
7175
|
+
|
|
7176
|
+
function extractPythonDependencyKey(value) {
|
|
7177
|
+
const manifestSource = parsePyProjectDependencySourceString(value);
|
|
7178
|
+
if (manifestSource?.name) {
|
|
7179
|
+
return normalizePythonDependencyKey(manifestSource.name);
|
|
7180
|
+
}
|
|
7181
|
+
const packageMatch =
|
|
7182
|
+
typeof value === "string"
|
|
7183
|
+
? value.trim().match(/^([A-Za-z0-9_.-]+)(?:\[[^\]]+\])?/)
|
|
7184
|
+
: undefined;
|
|
7185
|
+
return normalizePythonDependencyKey(packageMatch?.[1]);
|
|
7186
|
+
}
|
|
7187
|
+
function classifyPythonManifestSourceValue(value) {
|
|
7188
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7189
|
+
return undefined;
|
|
7190
|
+
}
|
|
7191
|
+
const normalizedValue = value.trim();
|
|
7192
|
+
const lowerValue = normalizedValue.toLowerCase();
|
|
7193
|
+
if (
|
|
7194
|
+
lowerValue.startsWith("git+") ||
|
|
7195
|
+
lowerValue.startsWith("git://") ||
|
|
7196
|
+
lowerValue.startsWith("git@") ||
|
|
7197
|
+
lowerValue.startsWith("ssh://git@")
|
|
7198
|
+
) {
|
|
7199
|
+
return {
|
|
7200
|
+
type: "git",
|
|
7201
|
+
value: normalizedValue,
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
if (
|
|
7205
|
+
lowerValue.startsWith("http://") ||
|
|
7206
|
+
lowerValue.startsWith("https://") ||
|
|
7207
|
+
lowerValue.startsWith("ftp://")
|
|
7208
|
+
) {
|
|
7209
|
+
return {
|
|
7210
|
+
type: "url",
|
|
7211
|
+
value: normalizedValue,
|
|
7212
|
+
};
|
|
7213
|
+
}
|
|
7214
|
+
if (
|
|
7215
|
+
lowerValue.startsWith("file:") ||
|
|
7216
|
+
normalizedValue.startsWith("./") ||
|
|
7217
|
+
normalizedValue.startsWith("../") ||
|
|
7218
|
+
normalizedValue.startsWith("/") ||
|
|
7219
|
+
isWindowsAbsolutePath(normalizedValue)
|
|
7220
|
+
) {
|
|
7221
|
+
return {
|
|
7222
|
+
type: "path",
|
|
7223
|
+
value: normalizedValue,
|
|
7224
|
+
};
|
|
7225
|
+
}
|
|
7226
|
+
return undefined;
|
|
7227
|
+
}
|
|
7228
|
+
|
|
7229
|
+
function applyManifestSourceProperties(
|
|
7230
|
+
component,
|
|
7231
|
+
propertyPrefix,
|
|
7232
|
+
manifestSource,
|
|
7233
|
+
) {
|
|
7234
|
+
if (!manifestSource?.type || !manifestSource?.value) {
|
|
7235
|
+
return;
|
|
7236
|
+
}
|
|
7237
|
+
addComponentProperty(
|
|
7238
|
+
component,
|
|
7239
|
+
`${propertyPrefix}:manifestSourceType`,
|
|
7240
|
+
manifestSource.type,
|
|
7241
|
+
);
|
|
7242
|
+
addComponentProperty(
|
|
7243
|
+
component,
|
|
7244
|
+
`${propertyPrefix}:manifestSource`,
|
|
7245
|
+
manifestSource.value,
|
|
7246
|
+
);
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
function recordPythonDependencySource(
|
|
7250
|
+
dependencySourceMap,
|
|
7251
|
+
dependencyName,
|
|
7252
|
+
sourceType,
|
|
7253
|
+
sourceValue,
|
|
7254
|
+
) {
|
|
7255
|
+
const normalizedKey = normalizePythonDependencyKey(dependencyName);
|
|
7256
|
+
if (!normalizedKey || !sourceType || !sourceValue) {
|
|
7257
|
+
return;
|
|
7258
|
+
}
|
|
7259
|
+
dependencySourceMap[normalizedKey] = {
|
|
7260
|
+
type: sourceType,
|
|
7261
|
+
value: sourceValue,
|
|
7262
|
+
};
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
function parsePyProjectDependencySourceString(value) {
|
|
7266
|
+
if (typeof value !== "string" || !value.includes("@")) {
|
|
7267
|
+
return undefined;
|
|
7268
|
+
}
|
|
7269
|
+
const directReferenceMatch = value
|
|
7270
|
+
.trim()
|
|
7271
|
+
.match(PYTHON_DIRECT_REFERENCE_PATTERN);
|
|
7272
|
+
if (!directReferenceMatch) {
|
|
7273
|
+
return undefined;
|
|
7274
|
+
}
|
|
7275
|
+
const manifestSource = classifyPythonManifestSourceValue(
|
|
7276
|
+
directReferenceMatch[2],
|
|
7277
|
+
);
|
|
7278
|
+
if (!manifestSource) {
|
|
7279
|
+
return undefined;
|
|
7280
|
+
}
|
|
7281
|
+
return {
|
|
7282
|
+
name: directReferenceMatch[1],
|
|
7283
|
+
...manifestSource,
|
|
7284
|
+
};
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
function collectPythonManifestSource(pkg) {
|
|
7288
|
+
const sourceCandidates = [
|
|
7289
|
+
{ kind: "git", value: pkg?.source?.git },
|
|
7290
|
+
{ kind: "git", value: pkg?.vcs?.git },
|
|
7291
|
+
{ kind: "url", value: pkg?.vcs?.url },
|
|
7292
|
+
{ kind: "url", value: pkg?.source?.url },
|
|
7293
|
+
{ kind: "path", value: pkg?.source?.path },
|
|
7294
|
+
{ kind: "path", value: pkg?.source?.editable },
|
|
7295
|
+
{ kind: "path", value: pkg?.source?.virtual },
|
|
7296
|
+
];
|
|
7297
|
+
for (const candidate of sourceCandidates) {
|
|
7298
|
+
if (typeof candidate.value !== "string" || !candidate.value.trim()) {
|
|
7299
|
+
continue;
|
|
7300
|
+
}
|
|
7301
|
+
const normalizedValue = candidate.value.trim();
|
|
7302
|
+
if (candidate.kind === "git") {
|
|
7303
|
+
return {
|
|
7304
|
+
type: "git",
|
|
7305
|
+
value: normalizedValue.startsWith("git+")
|
|
7306
|
+
? normalizedValue
|
|
7307
|
+
: `git+${normalizedValue}`,
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
const manifestSource = classifyPythonManifestSourceValue(normalizedValue);
|
|
7311
|
+
if (manifestSource) {
|
|
7312
|
+
return manifestSource;
|
|
7313
|
+
}
|
|
7314
|
+
return {
|
|
7315
|
+
type: candidate.kind,
|
|
7316
|
+
value: normalizedValue,
|
|
7317
|
+
};
|
|
7318
|
+
}
|
|
7319
|
+
return undefined;
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7322
|
+
function parsePythonRequirementManifestSource(value) {
|
|
7323
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
7324
|
+
return undefined;
|
|
7325
|
+
}
|
|
7326
|
+
const normalizedValue = value.trim();
|
|
7327
|
+
const directReferenceMatch = normalizedValue.match(
|
|
7328
|
+
PYTHON_DIRECT_REFERENCE_PATTERN,
|
|
7329
|
+
);
|
|
7330
|
+
if (directReferenceMatch) {
|
|
7331
|
+
const manifestSource = classifyPythonManifestSourceValue(
|
|
7332
|
+
directReferenceMatch[2],
|
|
7333
|
+
);
|
|
7334
|
+
if (manifestSource) {
|
|
7335
|
+
return {
|
|
7336
|
+
name: directReferenceMatch[1],
|
|
7337
|
+
...manifestSource,
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
const vcsRequirementMatch = normalizedValue.match(
|
|
7342
|
+
/^(git\+\S+?)(?:#.*egg=([A-Za-z0-9_.-]+))?$/,
|
|
7343
|
+
);
|
|
7344
|
+
if (vcsRequirementMatch?.[2]) {
|
|
7345
|
+
return {
|
|
7346
|
+
name: vcsRequirementMatch[2],
|
|
7347
|
+
type: "git",
|
|
7348
|
+
value: vcsRequirementMatch[1],
|
|
7349
|
+
};
|
|
7350
|
+
}
|
|
7351
|
+
return undefined;
|
|
7352
|
+
}
|
|
7353
|
+
|
|
7046
7354
|
/**
|
|
7047
7355
|
* Method to parse python pyproject.toml file
|
|
7048
7356
|
*
|
|
@@ -7102,6 +7410,7 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7102
7410
|
let tomlData;
|
|
7103
7411
|
const directDepsKeys = {};
|
|
7104
7412
|
const groupDepsKeys = {};
|
|
7413
|
+
const dependencySourceMap = {};
|
|
7105
7414
|
try {
|
|
7106
7415
|
tomlData = toml.parse(readFileSync(tomlFile, { encoding: "utf-8" }));
|
|
7107
7416
|
} catch (err) {
|
|
@@ -7173,8 +7482,19 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7173
7482
|
}
|
|
7174
7483
|
if (tomlData?.project?.dependencies) {
|
|
7175
7484
|
for (const adep of tomlData.project.dependencies) {
|
|
7176
|
-
|
|
7177
|
-
|
|
7485
|
+
const dependencyKey = extractPythonDependencyKey(adep);
|
|
7486
|
+
if (dependencyKey) {
|
|
7487
|
+
directDepsKeys[dependencyKey] = true;
|
|
7488
|
+
}
|
|
7489
|
+
const manifestSource = parsePyProjectDependencySourceString(adep);
|
|
7490
|
+
if (manifestSource) {
|
|
7491
|
+
recordPythonDependencySource(
|
|
7492
|
+
dependencySourceMap,
|
|
7493
|
+
manifestSource.name,
|
|
7494
|
+
manifestSource.type,
|
|
7495
|
+
manifestSource.value,
|
|
7496
|
+
);
|
|
7497
|
+
}
|
|
7178
7498
|
}
|
|
7179
7499
|
}
|
|
7180
7500
|
if (tomlData["dependency-groups"]) {
|
|
@@ -7186,6 +7506,15 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7186
7506
|
groupDepsKeys[pname] = [];
|
|
7187
7507
|
}
|
|
7188
7508
|
groupDepsKeys[pname].push(agroup);
|
|
7509
|
+
const manifestSource = parsePyProjectDependencySourceString(p);
|
|
7510
|
+
if (manifestSource) {
|
|
7511
|
+
recordPythonDependencySource(
|
|
7512
|
+
dependencySourceMap,
|
|
7513
|
+
manifestSource.name,
|
|
7514
|
+
manifestSource.type,
|
|
7515
|
+
manifestSource.value,
|
|
7516
|
+
);
|
|
7517
|
+
}
|
|
7189
7518
|
} else {
|
|
7190
7519
|
return;
|
|
7191
7520
|
}
|
|
@@ -7206,6 +7535,29 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7206
7535
|
].includes(adep)
|
|
7207
7536
|
) {
|
|
7208
7537
|
directDepsKeys[adep] = true;
|
|
7538
|
+
const poetryDependency = tomlData.tool.poetry.dependencies[adep];
|
|
7539
|
+
if (poetryDependency?.git) {
|
|
7540
|
+
recordPythonDependencySource(
|
|
7541
|
+
dependencySourceMap,
|
|
7542
|
+
adep,
|
|
7543
|
+
"git",
|
|
7544
|
+
poetryDependency.git,
|
|
7545
|
+
);
|
|
7546
|
+
} else if (poetryDependency?.url) {
|
|
7547
|
+
recordPythonDependencySource(
|
|
7548
|
+
dependencySourceMap,
|
|
7549
|
+
adep,
|
|
7550
|
+
"url",
|
|
7551
|
+
poetryDependency.url,
|
|
7552
|
+
);
|
|
7553
|
+
} else if (poetryDependency?.path) {
|
|
7554
|
+
recordPythonDependencySource(
|
|
7555
|
+
dependencySourceMap,
|
|
7556
|
+
adep,
|
|
7557
|
+
"path",
|
|
7558
|
+
poetryDependency.path,
|
|
7559
|
+
);
|
|
7560
|
+
}
|
|
7209
7561
|
}
|
|
7210
7562
|
} // for
|
|
7211
7563
|
if (tomlData?.tool?.poetry?.group) {
|
|
@@ -7217,10 +7569,63 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7217
7569
|
groupDepsKeys[adep] = [];
|
|
7218
7570
|
}
|
|
7219
7571
|
groupDepsKeys[adep].push(agroup);
|
|
7572
|
+
const poetryDependency =
|
|
7573
|
+
tomlData.tool.poetry.group[agroup]?.dependencies?.[adep];
|
|
7574
|
+
if (poetryDependency?.git) {
|
|
7575
|
+
recordPythonDependencySource(
|
|
7576
|
+
dependencySourceMap,
|
|
7577
|
+
adep,
|
|
7578
|
+
"git",
|
|
7579
|
+
poetryDependency.git,
|
|
7580
|
+
);
|
|
7581
|
+
} else if (poetryDependency?.url) {
|
|
7582
|
+
recordPythonDependencySource(
|
|
7583
|
+
dependencySourceMap,
|
|
7584
|
+
adep,
|
|
7585
|
+
"url",
|
|
7586
|
+
poetryDependency.url,
|
|
7587
|
+
);
|
|
7588
|
+
} else if (poetryDependency?.path) {
|
|
7589
|
+
recordPythonDependencySource(
|
|
7590
|
+
dependencySourceMap,
|
|
7591
|
+
adep,
|
|
7592
|
+
"path",
|
|
7593
|
+
poetryDependency.path,
|
|
7594
|
+
);
|
|
7595
|
+
}
|
|
7220
7596
|
}
|
|
7221
7597
|
} // for
|
|
7222
7598
|
}
|
|
7223
7599
|
}
|
|
7600
|
+
if (tomlData?.tool?.uv?.sources) {
|
|
7601
|
+
for (const adep of Object.keys(tomlData.tool.uv.sources)) {
|
|
7602
|
+
const uvSource = Array.isArray(tomlData.tool.uv.sources[adep])
|
|
7603
|
+
? tomlData.tool.uv.sources[adep][0]
|
|
7604
|
+
: tomlData.tool.uv.sources[adep];
|
|
7605
|
+
if (uvSource?.git) {
|
|
7606
|
+
recordPythonDependencySource(
|
|
7607
|
+
dependencySourceMap,
|
|
7608
|
+
adep,
|
|
7609
|
+
"git",
|
|
7610
|
+
uvSource.git,
|
|
7611
|
+
);
|
|
7612
|
+
} else if (uvSource?.url) {
|
|
7613
|
+
recordPythonDependencySource(
|
|
7614
|
+
dependencySourceMap,
|
|
7615
|
+
adep,
|
|
7616
|
+
"url",
|
|
7617
|
+
uvSource.url,
|
|
7618
|
+
);
|
|
7619
|
+
} else if (uvSource?.path) {
|
|
7620
|
+
recordPythonDependencySource(
|
|
7621
|
+
dependencySourceMap,
|
|
7622
|
+
adep,
|
|
7623
|
+
"path",
|
|
7624
|
+
uvSource.path,
|
|
7625
|
+
);
|
|
7626
|
+
}
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7224
7629
|
return {
|
|
7225
7630
|
parentComponent: pkg,
|
|
7226
7631
|
poetryMode,
|
|
@@ -7229,9 +7634,146 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7229
7634
|
workspacePaths,
|
|
7230
7635
|
directDepsKeys,
|
|
7231
7636
|
groupDepsKeys,
|
|
7637
|
+
dependencySourceMap,
|
|
7232
7638
|
};
|
|
7233
7639
|
}
|
|
7234
7640
|
|
|
7641
|
+
function collectPythonLockDistributionReferences(pkg) {
|
|
7642
|
+
const externalReferences = [];
|
|
7643
|
+
const seen = new Set();
|
|
7644
|
+
|
|
7645
|
+
function addExternalReference(type, url, comment) {
|
|
7646
|
+
if (typeof url !== "string" || !url.trim()) {
|
|
7647
|
+
return;
|
|
7648
|
+
}
|
|
7649
|
+
const normalizedUrl = url.trim();
|
|
7650
|
+
const reference = {
|
|
7651
|
+
type,
|
|
7652
|
+
url: normalizedUrl,
|
|
7653
|
+
comment,
|
|
7654
|
+
};
|
|
7655
|
+
const referenceKey = createExternalReferenceKey(reference);
|
|
7656
|
+
if (seen.has(referenceKey)) {
|
|
7657
|
+
return;
|
|
7658
|
+
}
|
|
7659
|
+
seen.add(referenceKey);
|
|
7660
|
+
externalReferences.push(reference);
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
addExternalReference("distribution", pkg?.archive?.url, "archive");
|
|
7664
|
+
addExternalReference("distribution", pkg?.sdist?.url, "sdist");
|
|
7665
|
+
if (Array.isArray(pkg?.wheels)) {
|
|
7666
|
+
for (const wheel of pkg.wheels) {
|
|
7667
|
+
addExternalReference(
|
|
7668
|
+
"distribution",
|
|
7669
|
+
wheel?.url,
|
|
7670
|
+
wheel?.file || wheel?.name || wheel?.filename || "wheel",
|
|
7671
|
+
);
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
const vcsSource = [
|
|
7675
|
+
{ kind: "url", value: pkg?.vcs?.url },
|
|
7676
|
+
{ kind: "git", value: pkg?.vcs?.git },
|
|
7677
|
+
{ kind: "git", value: pkg?.source?.git },
|
|
7678
|
+
].find(
|
|
7679
|
+
(entry) => typeof entry.value === "string" && entry.value.trim().length > 0,
|
|
7680
|
+
);
|
|
7681
|
+
if (vcsSource) {
|
|
7682
|
+
const vcsUrl = vcsSource.value.trim();
|
|
7683
|
+
const normalizedVcsUrl =
|
|
7684
|
+
vcsSource.kind === "git" && !vcsUrl.startsWith("git+")
|
|
7685
|
+
? `git+${vcsUrl}`
|
|
7686
|
+
: vcsUrl;
|
|
7687
|
+
addExternalReference("vcs", normalizedVcsUrl, "vcs");
|
|
7688
|
+
}
|
|
7689
|
+
if (pkg?.source?.url) {
|
|
7690
|
+
const manifestSource = classifyPythonManifestSourceValue(pkg.source.url);
|
|
7691
|
+
addExternalReference(
|
|
7692
|
+
manifestSource?.type === "git" ? "vcs" : "distribution",
|
|
7693
|
+
pkg.source.url,
|
|
7694
|
+
"source",
|
|
7695
|
+
);
|
|
7696
|
+
}
|
|
7697
|
+
return externalReferences;
|
|
7698
|
+
}
|
|
7699
|
+
|
|
7700
|
+
function collectPythonLockMetadataFileEntries(lockTomlObj, pkg) {
|
|
7701
|
+
if (!lockTomlObj?.metadata?.files || !pkg?.name) {
|
|
7702
|
+
return [];
|
|
7703
|
+
}
|
|
7704
|
+
const expectedKeys = new Set([normalizePythonDependencyKey(pkg.name)]);
|
|
7705
|
+
if (pkg.version) {
|
|
7706
|
+
expectedKeys.add(
|
|
7707
|
+
`${normalizePythonDependencyKey(pkg.name)} ${`${pkg.version}`.trim().toLowerCase()}`,
|
|
7708
|
+
);
|
|
7709
|
+
}
|
|
7710
|
+
const matchingEntries = [];
|
|
7711
|
+
for (const [entryKey, entryValues] of Object.entries(
|
|
7712
|
+
lockTomlObj.metadata.files,
|
|
7713
|
+
)) {
|
|
7714
|
+
if (!Array.isArray(entryValues)) {
|
|
7715
|
+
continue;
|
|
7716
|
+
}
|
|
7717
|
+
if (expectedKeys.has(normalizePythonDependencyKey(entryKey))) {
|
|
7718
|
+
matchingEntries.push(...entryValues);
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
return matchingEntries;
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
function mergeExternalReferences(component, references) {
|
|
7725
|
+
if (!references?.length) {
|
|
7726
|
+
return;
|
|
7727
|
+
}
|
|
7728
|
+
const existingReferences = component.externalReferences || [];
|
|
7729
|
+
const seen = new Set(
|
|
7730
|
+
existingReferences.map((reference) =>
|
|
7731
|
+
createExternalReferenceKey(reference),
|
|
7732
|
+
),
|
|
7733
|
+
);
|
|
7734
|
+
for (const reference of references) {
|
|
7735
|
+
const dedupeKey = createExternalReferenceKey(reference);
|
|
7736
|
+
if (seen.has(dedupeKey)) {
|
|
7737
|
+
continue;
|
|
7738
|
+
}
|
|
7739
|
+
seen.add(dedupeKey);
|
|
7740
|
+
existingReferences.push(reference);
|
|
7741
|
+
}
|
|
7742
|
+
if (existingReferences.length) {
|
|
7743
|
+
component.externalReferences = existingReferences;
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
|
|
7747
|
+
function collectPythonLockMetadataDistributionReferences(fileEntries) {
|
|
7748
|
+
const distributionReferences = [];
|
|
7749
|
+
for (const fileEntry of fileEntries || []) {
|
|
7750
|
+
if (typeof fileEntry?.url !== "string" || !fileEntry.url.trim()) {
|
|
7751
|
+
continue;
|
|
7752
|
+
}
|
|
7753
|
+
distributionReferences.push({
|
|
7754
|
+
type: "distribution",
|
|
7755
|
+
url: fileEntry.url.trim(),
|
|
7756
|
+
comment: fileEntry.file,
|
|
7757
|
+
});
|
|
7758
|
+
}
|
|
7759
|
+
return distributionReferences;
|
|
7760
|
+
}
|
|
7761
|
+
|
|
7762
|
+
function collectPypiReleaseExternalReferences(releaseEntries) {
|
|
7763
|
+
const externalReferences = [];
|
|
7764
|
+
for (const releaseEntry of releaseEntries || []) {
|
|
7765
|
+
if (typeof releaseEntry?.url !== "string" || !releaseEntry.url.trim()) {
|
|
7766
|
+
continue;
|
|
7767
|
+
}
|
|
7768
|
+
externalReferences.push({
|
|
7769
|
+
type: "distribution",
|
|
7770
|
+
url: releaseEntry.url.trim(),
|
|
7771
|
+
comment: releaseEntry.filename || releaseEntry.packagetype,
|
|
7772
|
+
});
|
|
7773
|
+
}
|
|
7774
|
+
return externalReferences;
|
|
7775
|
+
}
|
|
7776
|
+
|
|
7235
7777
|
/**
|
|
7236
7778
|
* Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
|
|
7237
7779
|
*
|
|
@@ -7248,6 +7790,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7248
7790
|
const pkgBomRefMap = {};
|
|
7249
7791
|
let directDepsKeys = {};
|
|
7250
7792
|
let groupDepsKeys = {};
|
|
7793
|
+
let dependencySourceMap = {};
|
|
7251
7794
|
let parentComponent;
|
|
7252
7795
|
let workspacePaths;
|
|
7253
7796
|
let workspaceWarningShown = false;
|
|
@@ -7255,6 +7798,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7255
7798
|
let pyLockProperties = [];
|
|
7256
7799
|
// Keep track of any workspace components to be added to the parent component
|
|
7257
7800
|
const workspaceComponentMap = {};
|
|
7801
|
+
const workspaceDependencySourceMap = {};
|
|
7258
7802
|
const workspacePyProjMap = {};
|
|
7259
7803
|
const workspaceRefPyProjMap = {};
|
|
7260
7804
|
const pkgParentMap = {};
|
|
@@ -7274,6 +7818,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7274
7818
|
const pyProjMap = parsePyProjectTomlFile(pyProjectFile);
|
|
7275
7819
|
directDepsKeys = pyProjMap.directDepsKeys || {};
|
|
7276
7820
|
groupDepsKeys = pyProjMap.groupDepsKeys || {};
|
|
7821
|
+
dependencySourceMap = pyProjMap.dependencySourceMap || {};
|
|
7277
7822
|
parentComponent = pyProjMap.parentComponent;
|
|
7278
7823
|
workspacePaths = pyProjMap.workspacePaths;
|
|
7279
7824
|
if (workspacePaths?.length) {
|
|
@@ -7337,6 +7882,12 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7337
7882
|
}
|
|
7338
7883
|
}
|
|
7339
7884
|
const wparentComponentRef = wcompMap.parentComponent["bom-ref"];
|
|
7885
|
+
if (wcompMap?.dependencySourceMap) {
|
|
7886
|
+
Object.assign(
|
|
7887
|
+
workspaceDependencySourceMap,
|
|
7888
|
+
wcompMap.dependencySourceMap,
|
|
7889
|
+
);
|
|
7890
|
+
}
|
|
7340
7891
|
// Track the parents of workspace direct dependencies
|
|
7341
7892
|
if (wcompMap?.directDepsKeys) {
|
|
7342
7893
|
for (const wdd of Object.keys(wcompMap?.directDepsKeys)) {
|
|
@@ -7408,6 +7959,11 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7408
7959
|
value: workspacePyProjMap[apkg.name] || pyProjectFile,
|
|
7409
7960
|
});
|
|
7410
7961
|
}
|
|
7962
|
+
const manifestSource =
|
|
7963
|
+
dependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
7964
|
+
workspaceDependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
7965
|
+
collectPythonManifestSource(apkg);
|
|
7966
|
+
applyManifestSourceProperties(pkg, "cdx:pypi", manifestSource);
|
|
7411
7967
|
if (apkg.optional) {
|
|
7412
7968
|
pkg.scope = "optional";
|
|
7413
7969
|
}
|
|
@@ -7449,6 +8005,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7449
8005
|
});
|
|
7450
8006
|
}
|
|
7451
8007
|
}
|
|
8008
|
+
mergeExternalReferences(pkg, collectPythonLockDistributionReferences(apkg));
|
|
7452
8009
|
if (pyLockMode) {
|
|
7453
8010
|
pkg.properties = pkg.properties.concat(
|
|
7454
8011
|
collectPyLockPackageProperties(apkg),
|
|
@@ -7511,9 +8068,17 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7511
8068
|
}
|
|
7512
8069
|
}
|
|
7513
8070
|
}
|
|
7514
|
-
|
|
8071
|
+
const metadataFileEntries = collectPythonLockMetadataFileEntries(
|
|
8072
|
+
lockTomlObj,
|
|
8073
|
+
pkg,
|
|
8074
|
+
);
|
|
8075
|
+
mergeExternalReferences(
|
|
8076
|
+
pkg,
|
|
8077
|
+
collectPythonLockMetadataDistributionReferences(metadataFileEntries),
|
|
8078
|
+
);
|
|
8079
|
+
if (metadataFileEntries.length) {
|
|
7515
8080
|
pkg.components = [];
|
|
7516
|
-
for (const afileObj of
|
|
8081
|
+
for (const afileObj of metadataFileEntries) {
|
|
7517
8082
|
const hashParts = afileObj?.hash?.split(":");
|
|
7518
8083
|
let hashes;
|
|
7519
8084
|
if (hashParts?.length === 2) {
|
|
@@ -7547,8 +8112,9 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7547
8112
|
pkg.components = (pkg.components || []).concat(pylockFileComponents);
|
|
7548
8113
|
}
|
|
7549
8114
|
}
|
|
8115
|
+
const normalizedPkgName = normalizePythonDependencyKey(pkg.name);
|
|
7550
8116
|
if (
|
|
7551
|
-
directDepsKeys[
|
|
8117
|
+
directDepsKeys[normalizedPkgName] ||
|
|
7552
8118
|
(hasWorkspaces && !Object.keys(workspaceComponentMap).length)
|
|
7553
8119
|
) {
|
|
7554
8120
|
rootList.push(pkg);
|
|
@@ -7743,6 +8309,23 @@ export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
|
7743
8309
|
const LICENSE_ID_COMMENTS_PATTERN =
|
|
7744
8310
|
/^(Apache-2\.0|MIT|ISC|GPL-|LGPL-|BSD-[23]-Clause)/i;
|
|
7745
8311
|
|
|
8312
|
+
function parseLicenseComment(comment) {
|
|
8313
|
+
if (!comment) {
|
|
8314
|
+
return undefined;
|
|
8315
|
+
}
|
|
8316
|
+
const licenses = comment
|
|
8317
|
+
.split("/")
|
|
8318
|
+
.map((value) => {
|
|
8319
|
+
const licenseId = value.trim();
|
|
8320
|
+
if (!licenseId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
8321
|
+
return undefined;
|
|
8322
|
+
}
|
|
8323
|
+
return { license: { id: licenseId } };
|
|
8324
|
+
})
|
|
8325
|
+
.filter((value) => value !== undefined);
|
|
8326
|
+
return licenses.length ? licenses : undefined;
|
|
8327
|
+
}
|
|
8328
|
+
|
|
7746
8329
|
/**
|
|
7747
8330
|
* Method to parse requirements.txt file. Must only be used internally.
|
|
7748
8331
|
*
|
|
@@ -7780,11 +8363,16 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7780
8363
|
const lines = normalizedData.split("\n");
|
|
7781
8364
|
for (const line of lines) {
|
|
7782
8365
|
let l = line.trim();
|
|
8366
|
+
let editableRequirement = false;
|
|
7783
8367
|
if (l.includes("# Basic requirements")) {
|
|
7784
8368
|
compScope = "required";
|
|
7785
8369
|
} else if (l.includes("added by pip freeze")) {
|
|
7786
8370
|
compScope = undefined;
|
|
7787
8371
|
}
|
|
8372
|
+
if (l.startsWith("-e ") || l.startsWith("--editable ")) {
|
|
8373
|
+
editableRequirement = true;
|
|
8374
|
+
l = l.replace(/^--editable\s+|^-e\s+/, "").trim();
|
|
8375
|
+
}
|
|
7788
8376
|
if (l.startsWith("Skipping line") || l.startsWith("(add")) {
|
|
7789
8377
|
continue;
|
|
7790
8378
|
}
|
|
@@ -7833,6 +8421,45 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7833
8421
|
markers = parts.slice(1).join(";").trim();
|
|
7834
8422
|
structuredMarkers = parseReqEnvMarkers(markers);
|
|
7835
8423
|
}
|
|
8424
|
+
const requirementManifestSource = parsePythonRequirementManifestSource(l);
|
|
8425
|
+
if (requirementManifestSource?.name) {
|
|
8426
|
+
const apkg = {
|
|
8427
|
+
name: requirementManifestSource.name,
|
|
8428
|
+
version: null,
|
|
8429
|
+
scope: compScope,
|
|
8430
|
+
evidence,
|
|
8431
|
+
};
|
|
8432
|
+
if (hashes.length > 0) {
|
|
8433
|
+
apkg.hashes = hashes;
|
|
8434
|
+
}
|
|
8435
|
+
const licenses = parseLicenseComment(comment);
|
|
8436
|
+
if (licenses) {
|
|
8437
|
+
apkg.licenses = licenses;
|
|
8438
|
+
}
|
|
8439
|
+
applyManifestSourceProperties(
|
|
8440
|
+
apkg,
|
|
8441
|
+
"cdx:pypi",
|
|
8442
|
+
requirementManifestSource,
|
|
8443
|
+
);
|
|
8444
|
+
if (editableRequirement) {
|
|
8445
|
+
addComponentProperty(apkg, "cdx:pypi:editable", "true");
|
|
8446
|
+
}
|
|
8447
|
+
if (markers) {
|
|
8448
|
+
addComponentProperty(apkg, "cdx:pip:markers", markers);
|
|
8449
|
+
if (structuredMarkers?.length > 0) {
|
|
8450
|
+
addComponentProperty(
|
|
8451
|
+
apkg,
|
|
8452
|
+
"cdx:pip:structuredMarkers",
|
|
8453
|
+
JSON.stringify(structuredMarkers),
|
|
8454
|
+
);
|
|
8455
|
+
}
|
|
8456
|
+
}
|
|
8457
|
+
if (reqFile) {
|
|
8458
|
+
addComponentProperty(apkg, "SrcFile", reqFile);
|
|
8459
|
+
}
|
|
8460
|
+
pkgList.push(apkg);
|
|
8461
|
+
continue;
|
|
8462
|
+
}
|
|
7836
8463
|
|
|
7837
8464
|
// Handle extras (e.g., package[extra1,extra2])
|
|
7838
8465
|
let extras = null;
|
|
@@ -7866,20 +8493,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7866
8493
|
if (hashes.length > 0) {
|
|
7867
8494
|
apkg.hashes = hashes;
|
|
7868
8495
|
}
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
.map((c) => {
|
|
7873
|
-
const licenseObj = {};
|
|
7874
|
-
const cId = c.trim();
|
|
7875
|
-
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
7876
|
-
licenseObj.id = cId;
|
|
7877
|
-
} else {
|
|
7878
|
-
return undefined;
|
|
7879
|
-
}
|
|
7880
|
-
return { license: licenseObj };
|
|
7881
|
-
})
|
|
7882
|
-
.filter((l) => l !== undefined);
|
|
8496
|
+
const licenses = parseLicenseComment(comment);
|
|
8497
|
+
if (licenses) {
|
|
8498
|
+
apkg.licenses = licenses;
|
|
7883
8499
|
}
|
|
7884
8500
|
if (extras && extras.length > 0) {
|
|
7885
8501
|
properties.push({
|
|
@@ -7905,6 +8521,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7905
8521
|
});
|
|
7906
8522
|
}
|
|
7907
8523
|
}
|
|
8524
|
+
if (editableRequirement) {
|
|
8525
|
+
properties.push({
|
|
8526
|
+
name: "cdx:pypi:editable",
|
|
8527
|
+
value: "true",
|
|
8528
|
+
});
|
|
8529
|
+
}
|
|
7908
8530
|
if (properties.length) {
|
|
7909
8531
|
apkg.properties = properties;
|
|
7910
8532
|
}
|
|
@@ -7932,20 +8554,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7932
8554
|
scope: compScope,
|
|
7933
8555
|
evidence,
|
|
7934
8556
|
};
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
.map((c) => {
|
|
7939
|
-
const licenseObj = {};
|
|
7940
|
-
const cId = c.trim();
|
|
7941
|
-
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
7942
|
-
licenseObj.id = cId;
|
|
7943
|
-
} else {
|
|
7944
|
-
return undefined;
|
|
7945
|
-
}
|
|
7946
|
-
return { license: licenseObj };
|
|
7947
|
-
})
|
|
7948
|
-
.filter((l) => l !== undefined);
|
|
8557
|
+
const licenses = parseLicenseComment(comment);
|
|
8558
|
+
if (licenses) {
|
|
8559
|
+
apkg.licenses = licenses;
|
|
7949
8560
|
}
|
|
7950
8561
|
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
7951
8562
|
properties.push({
|
|
@@ -7965,6 +8576,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7965
8576
|
});
|
|
7966
8577
|
}
|
|
7967
8578
|
}
|
|
8579
|
+
if (editableRequirement) {
|
|
8580
|
+
properties.push({
|
|
8581
|
+
name: "cdx:pypi:editable",
|
|
8582
|
+
value: "true",
|
|
8583
|
+
});
|
|
8584
|
+
}
|
|
7968
8585
|
if (properties.length) {
|
|
7969
8586
|
apkg.properties = properties;
|
|
7970
8587
|
}
|
|
@@ -12665,6 +13282,188 @@ export function parseConanData(conanData) {
|
|
|
12665
13282
|
return pkgList;
|
|
12666
13283
|
}
|
|
12667
13284
|
|
|
13285
|
+
/**
|
|
13286
|
+
* Construct a generic package component object for collider-managed packages.
|
|
13287
|
+
*
|
|
13288
|
+
* @param {string} name Package name
|
|
13289
|
+
* @param {Object} pkgData Locked package entry from collider.lock
|
|
13290
|
+
* @param {string} lockFile Source lock file path
|
|
13291
|
+
* @param {string} dependencyKind Whether the package is direct or transitive
|
|
13292
|
+
* @returns {Object|undefined} Package component
|
|
13293
|
+
*/
|
|
13294
|
+
function buildColliderComponent(name, pkgData, lockFile, dependencyKind) {
|
|
13295
|
+
if (!name) {
|
|
13296
|
+
return undefined;
|
|
13297
|
+
}
|
|
13298
|
+
pkgData = pkgData || {};
|
|
13299
|
+
dependencyKind = dependencyKind || "transitive";
|
|
13300
|
+
const version = pkgData?.version || "";
|
|
13301
|
+
const purl = new PackageURL(
|
|
13302
|
+
"generic",
|
|
13303
|
+
"",
|
|
13304
|
+
name,
|
|
13305
|
+
version || undefined,
|
|
13306
|
+
null,
|
|
13307
|
+
null,
|
|
13308
|
+
).toString();
|
|
13309
|
+
const properties = [
|
|
13310
|
+
{
|
|
13311
|
+
name: "cdx:collider:dependencyKind",
|
|
13312
|
+
value: dependencyKind,
|
|
13313
|
+
},
|
|
13314
|
+
];
|
|
13315
|
+
if (lockFile) {
|
|
13316
|
+
properties.unshift({
|
|
13317
|
+
name: "SrcFile",
|
|
13318
|
+
value: lockFile,
|
|
13319
|
+
});
|
|
13320
|
+
}
|
|
13321
|
+
const wrapHash =
|
|
13322
|
+
typeof pkgData?.wrap_hash === "string" ? pkgData.wrap_hash.trim() : "";
|
|
13323
|
+
const wrapHashMatch = wrapHash.match(/^sha256:([0-9A-Fa-f]{64})$/);
|
|
13324
|
+
if (wrapHash) {
|
|
13325
|
+
properties.push({
|
|
13326
|
+
name: "cdx:collider:wrapHash",
|
|
13327
|
+
value: wrapHash,
|
|
13328
|
+
});
|
|
13329
|
+
}
|
|
13330
|
+
properties.push({
|
|
13331
|
+
name: "cdx:collider:hasWrapHash",
|
|
13332
|
+
value: wrapHashMatch ? "true" : "false",
|
|
13333
|
+
});
|
|
13334
|
+
if (wrapHash && !wrapHashMatch) {
|
|
13335
|
+
properties.push({
|
|
13336
|
+
name: "cdx:collider:wrapHashInvalid",
|
|
13337
|
+
value: "true",
|
|
13338
|
+
});
|
|
13339
|
+
}
|
|
13340
|
+
let originReference;
|
|
13341
|
+
if (typeof pkgData?.origin === "string" && pkgData.origin.trim()) {
|
|
13342
|
+
try {
|
|
13343
|
+
const originUrl = new URL(pkgData.origin.trim());
|
|
13344
|
+
const originHadSensitiveParts = Boolean(
|
|
13345
|
+
originUrl.username ||
|
|
13346
|
+
originUrl.password ||
|
|
13347
|
+
originUrl.search ||
|
|
13348
|
+
originUrl.hash,
|
|
13349
|
+
);
|
|
13350
|
+
originUrl.username = "";
|
|
13351
|
+
originUrl.password = "";
|
|
13352
|
+
originUrl.search = "";
|
|
13353
|
+
originUrl.hash = "";
|
|
13354
|
+
originReference = originUrl.toString();
|
|
13355
|
+
properties.push({
|
|
13356
|
+
name: "cdx:collider:origin",
|
|
13357
|
+
value: originReference,
|
|
13358
|
+
});
|
|
13359
|
+
properties.push({
|
|
13360
|
+
name: "cdx:collider:originScheme",
|
|
13361
|
+
value: originUrl.protocol.replace(":", ""),
|
|
13362
|
+
});
|
|
13363
|
+
if (originUrl.host) {
|
|
13364
|
+
properties.push({
|
|
13365
|
+
name: "cdx:collider:originHost",
|
|
13366
|
+
value: originUrl.host,
|
|
13367
|
+
});
|
|
13368
|
+
}
|
|
13369
|
+
if (originHadSensitiveParts) {
|
|
13370
|
+
properties.push({
|
|
13371
|
+
name: "cdx:collider:originSanitized",
|
|
13372
|
+
value: "true",
|
|
13373
|
+
});
|
|
13374
|
+
}
|
|
13375
|
+
} catch {
|
|
13376
|
+
thoughtLog("Ignoring invalid Collider origin URL");
|
|
13377
|
+
}
|
|
13378
|
+
}
|
|
13379
|
+
const component = {
|
|
13380
|
+
name,
|
|
13381
|
+
version,
|
|
13382
|
+
purl,
|
|
13383
|
+
"bom-ref": decodeURIComponent(purl),
|
|
13384
|
+
properties,
|
|
13385
|
+
};
|
|
13386
|
+
if (dependencyKind === "direct") {
|
|
13387
|
+
component.scope = "required";
|
|
13388
|
+
}
|
|
13389
|
+
if (wrapHashMatch) {
|
|
13390
|
+
component.hashes = [
|
|
13391
|
+
{
|
|
13392
|
+
alg: "SHA-256",
|
|
13393
|
+
content: wrapHashMatch[1].toLowerCase(),
|
|
13394
|
+
},
|
|
13395
|
+
];
|
|
13396
|
+
}
|
|
13397
|
+
if (originReference) {
|
|
13398
|
+
component.externalReferences = [
|
|
13399
|
+
{
|
|
13400
|
+
type: "distribution",
|
|
13401
|
+
url: originReference,
|
|
13402
|
+
},
|
|
13403
|
+
];
|
|
13404
|
+
}
|
|
13405
|
+
return component;
|
|
13406
|
+
}
|
|
13407
|
+
|
|
13408
|
+
/**
|
|
13409
|
+
* Parse Collider lock file data (collider.lock) and return the package list and
|
|
13410
|
+
* parent component dependencies.
|
|
13411
|
+
*
|
|
13412
|
+
* @param {string} colliderLockData Raw JSON string of the Collider lock file
|
|
13413
|
+
* @param {string} lockFile Source lock file path
|
|
13414
|
+
* @returns {{ pkgList: Object[], dependencies: Object, parentComponentDependencies: string[] }}
|
|
13415
|
+
*/
|
|
13416
|
+
export function parseColliderLockData(colliderLockData, lockFile) {
|
|
13417
|
+
const pkgList = [];
|
|
13418
|
+
const dependencies = {};
|
|
13419
|
+
const parentComponentDependencies = [];
|
|
13420
|
+
if (!colliderLockData) {
|
|
13421
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
13422
|
+
}
|
|
13423
|
+
let parsedLockFile;
|
|
13424
|
+
try {
|
|
13425
|
+
parsedLockFile = JSON.parse(colliderLockData);
|
|
13426
|
+
} catch {
|
|
13427
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
13428
|
+
}
|
|
13429
|
+
const addedBomRefs = new Set();
|
|
13430
|
+
const directDependencies = parsedLockFile.dependencies || {};
|
|
13431
|
+
const packages = parsedLockFile.packages || {};
|
|
13432
|
+
for (const [name, pkgData] of Object.entries(directDependencies)) {
|
|
13433
|
+
const component = buildColliderComponent(name, pkgData, lockFile, "direct");
|
|
13434
|
+
if (!component) {
|
|
13435
|
+
continue;
|
|
13436
|
+
}
|
|
13437
|
+
if (!addedBomRefs.has(component["bom-ref"])) {
|
|
13438
|
+
pkgList.push(component);
|
|
13439
|
+
addedBomRefs.add(component["bom-ref"]);
|
|
13440
|
+
}
|
|
13441
|
+
if (!parentComponentDependencies.includes(component["bom-ref"])) {
|
|
13442
|
+
parentComponentDependencies.push(component["bom-ref"]);
|
|
13443
|
+
}
|
|
13444
|
+
if (!(component["bom-ref"] in dependencies)) {
|
|
13445
|
+
dependencies[component["bom-ref"]] = [];
|
|
13446
|
+
}
|
|
13447
|
+
}
|
|
13448
|
+
for (const [name, pkgData] of Object.entries(packages)) {
|
|
13449
|
+
const component = buildColliderComponent(
|
|
13450
|
+
name,
|
|
13451
|
+
pkgData,
|
|
13452
|
+
lockFile,
|
|
13453
|
+
"transitive",
|
|
13454
|
+
);
|
|
13455
|
+
if (!component || addedBomRefs.has(component["bom-ref"])) {
|
|
13456
|
+
continue;
|
|
13457
|
+
}
|
|
13458
|
+
pkgList.push(component);
|
|
13459
|
+
addedBomRefs.add(component["bom-ref"]);
|
|
13460
|
+
if (!(component["bom-ref"] in dependencies)) {
|
|
13461
|
+
dependencies[component["bom-ref"]] = [];
|
|
13462
|
+
}
|
|
13463
|
+
}
|
|
13464
|
+
return { pkgList, dependencies, parentComponentDependencies };
|
|
13465
|
+
}
|
|
13466
|
+
|
|
12668
13467
|
/**
|
|
12669
13468
|
* Parse Leiningen project.clj data and extract dependency packages.
|
|
12670
13469
|
*
|
|
@@ -18363,6 +19162,92 @@ export function parsePackageJsonName(name) {
|
|
|
18363
19162
|
return returnObject;
|
|
18364
19163
|
}
|
|
18365
19164
|
|
|
19165
|
+
/**
|
|
19166
|
+
* Collect bom-refs from metadata.tools entries.
|
|
19167
|
+
*
|
|
19168
|
+
* @param {Object[]|Object} tools CycloneDX metadata.tools section
|
|
19169
|
+
* @param {Function} predicate Optional filter function
|
|
19170
|
+
* @returns {string[]} Unique tool bom-refs
|
|
19171
|
+
*/
|
|
19172
|
+
export function extractToolRefs(tools, predicate) {
|
|
19173
|
+
if (!tools) {
|
|
19174
|
+
return [];
|
|
19175
|
+
}
|
|
19176
|
+
const toolRefs = new Set();
|
|
19177
|
+
const toolList = Array.isArray(tools)
|
|
19178
|
+
? tools
|
|
19179
|
+
: [...(tools.components || []), ...(tools.services || [])];
|
|
19180
|
+
for (const tool of toolList) {
|
|
19181
|
+
let toolRef = tool?.["bom-ref"];
|
|
19182
|
+
if (!toolRef && tool?.purl) {
|
|
19183
|
+
toolRef = decodeURIComponent(tool.purl);
|
|
19184
|
+
}
|
|
19185
|
+
if (!toolRef && tool?.name) {
|
|
19186
|
+
try {
|
|
19187
|
+
toolRef = new PackageURL(
|
|
19188
|
+
"generic",
|
|
19189
|
+
tool.group || tool.publisher || tool.manufacturer?.name || undefined,
|
|
19190
|
+
tool.name,
|
|
19191
|
+
tool.version || undefined,
|
|
19192
|
+
null,
|
|
19193
|
+
null,
|
|
19194
|
+
).toString();
|
|
19195
|
+
} catch (_err) {
|
|
19196
|
+
thoughtLog("Unable to derive bom-ref for external tool", {
|
|
19197
|
+
group: tool.group,
|
|
19198
|
+
manufacturer: tool.manufacturer?.name,
|
|
19199
|
+
name: tool.name,
|
|
19200
|
+
publisher: tool.publisher,
|
|
19201
|
+
version: tool.version,
|
|
19202
|
+
});
|
|
19203
|
+
toolRef = undefined;
|
|
19204
|
+
}
|
|
19205
|
+
}
|
|
19206
|
+
if (!toolRef) {
|
|
19207
|
+
continue;
|
|
19208
|
+
}
|
|
19209
|
+
if (!tool["bom-ref"]) {
|
|
19210
|
+
tool["bom-ref"] = toolRef;
|
|
19211
|
+
}
|
|
19212
|
+
if (predicate && !predicate(tool)) {
|
|
19213
|
+
continue;
|
|
19214
|
+
}
|
|
19215
|
+
toolRefs.add(toolRef);
|
|
19216
|
+
}
|
|
19217
|
+
return Array.from(toolRefs);
|
|
19218
|
+
}
|
|
19219
|
+
|
|
19220
|
+
/**
|
|
19221
|
+
* Attach evidence.identity.tools references to the supplied subjects.
|
|
19222
|
+
*
|
|
19223
|
+
* @param {Object|Object[]} subjects Component or service objects
|
|
19224
|
+
* @param {string[]} toolRefs Tool bom-refs
|
|
19225
|
+
* @returns {Object|Object[]} The same mutated subject(s)
|
|
19226
|
+
*/
|
|
19227
|
+
export function attachIdentityTools(subjects, toolRefs) {
|
|
19228
|
+
if (!subjects || !toolRefs?.length) {
|
|
19229
|
+
return subjects;
|
|
19230
|
+
}
|
|
19231
|
+
const uniqueToolRefs = Array.from(new Set(toolRefs.filter(Boolean)));
|
|
19232
|
+
if (!uniqueToolRefs.length) {
|
|
19233
|
+
return subjects;
|
|
19234
|
+
}
|
|
19235
|
+
const subjectList = Array.isArray(subjects) ? subjects : [subjects];
|
|
19236
|
+
for (const subject of subjectList) {
|
|
19237
|
+
const identities = Array.isArray(subject?.evidence?.identity)
|
|
19238
|
+
? subject.evidence.identity
|
|
19239
|
+
: subject?.evidence?.identity
|
|
19240
|
+
? [subject.evidence.identity]
|
|
19241
|
+
: [];
|
|
19242
|
+
for (const identity of identities) {
|
|
19243
|
+
identity.tools = Array.from(
|
|
19244
|
+
new Set([...(identity.tools || []), ...uniqueToolRefs]),
|
|
19245
|
+
);
|
|
19246
|
+
}
|
|
19247
|
+
}
|
|
19248
|
+
return subjects;
|
|
19249
|
+
}
|
|
19250
|
+
|
|
18366
19251
|
/**
|
|
18367
19252
|
* Method to add occurrence evidence for components based on import statements. Currently useful for js
|
|
18368
19253
|
*
|
|
@@ -19960,7 +20845,7 @@ export function collectExecutables(basePath, binPaths) {
|
|
|
19960
20845
|
if (!binPaths) {
|
|
19961
20846
|
return [];
|
|
19962
20847
|
}
|
|
19963
|
-
|
|
20848
|
+
const executablesByResolvedPath = new Map();
|
|
19964
20849
|
const ignoreList = [
|
|
19965
20850
|
"**/*.{h,c,cpp,hpp,man,txt,md,htm,html,jar,ear,war,zip,tar,egg,keepme,gitignore,json,js,py,pyc}",
|
|
19966
20851
|
"[",
|
|
@@ -19976,12 +20861,38 @@ export function collectExecutables(basePath, binPaths) {
|
|
|
19976
20861
|
follow: true,
|
|
19977
20862
|
ignore: ignoreList,
|
|
19978
20863
|
});
|
|
19979
|
-
|
|
20864
|
+
for (const file of files) {
|
|
20865
|
+
let resolvedFile = file;
|
|
20866
|
+
try {
|
|
20867
|
+
resolvedFile = relative(basePath, realpathSync(join(basePath, file)));
|
|
20868
|
+
} catch (_err) {
|
|
20869
|
+
// Broken symlinks or permission errors can prevent realpath resolution.
|
|
20870
|
+
if (DEBUG_MODE) {
|
|
20871
|
+
console.log(`Unable to resolve executable path alias for ${file}`);
|
|
20872
|
+
}
|
|
20873
|
+
}
|
|
20874
|
+
const existingFile = executablesByResolvedPath.get(resolvedFile);
|
|
20875
|
+
if (shouldPreferUsrMergedExecutablePath(file, existingFile)) {
|
|
20876
|
+
executablesByResolvedPath.set(resolvedFile, file);
|
|
20877
|
+
}
|
|
20878
|
+
}
|
|
19980
20879
|
} catch (_err) {
|
|
19981
20880
|
// ignore
|
|
19982
20881
|
}
|
|
19983
20882
|
}
|
|
19984
|
-
return Array.from(
|
|
20883
|
+
return Array.from(executablesByResolvedPath.values()).sort();
|
|
20884
|
+
}
|
|
20885
|
+
|
|
20886
|
+
function shouldPreferUsrMergedExecutablePath(file, existingFile) {
|
|
20887
|
+
if (!existingFile) {
|
|
20888
|
+
return true;
|
|
20889
|
+
}
|
|
20890
|
+
const fileUsesUsrPrefix = file.startsWith("usr/");
|
|
20891
|
+
const existingFileUsesUsrPrefix = existingFile.startsWith("usr/");
|
|
20892
|
+
if (fileUsesUsrPrefix !== existingFileUsesUsrPrefix) {
|
|
20893
|
+
return fileUsesUsrPrefix;
|
|
20894
|
+
}
|
|
20895
|
+
return file < existingFile;
|
|
19985
20896
|
}
|
|
19986
20897
|
|
|
19987
20898
|
/**
|