@cyclonedx/cdxgen 12.3.1 → 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/bin/cdxgen.js +1 -2
- package/data/rules/ai-agent-governance.yaml +43 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/mcp-servers.yaml +36 -2
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +436 -56
- package/lib/cli/index.poku.js +875 -2
- package/lib/helpers/agentFormulationParser.js +10 -3
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +413 -54
- package/lib/helpers/analyzer.poku.js +117 -0
- package/lib/helpers/auditCategories.js +76 -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 +50 -24
- package/lib/helpers/display.poku.js +70 -58
- package/lib/helpers/formulationParsers.js +26 -6
- package/lib/helpers/jsonLike.js +21 -20
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcpConfigParser.js +32 -16
- package/lib/helpers/mcpConfigParser.poku.js +104 -0
- package/lib/helpers/mcpDiscovery.js +13 -23
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +953 -41
- package/lib/helpers/utils.poku.js +901 -1
- 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/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +12 -6
- package/lib/stages/postgen/auditBom.poku.js +755 -6
- package/lib/stages/postgen/postgen.js +229 -6
- package/lib/stages/postgen/postgen.poku.js +180 -0
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts +1 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +5 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- 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 +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- 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/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- 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 +31 -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/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.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,
|
|
@@ -918,7 +919,6 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
918
919
|
"node20",
|
|
919
920
|
"node22",
|
|
920
921
|
"node23",
|
|
921
|
-
"mcp",
|
|
922
922
|
"js",
|
|
923
923
|
"javascript",
|
|
924
924
|
"typescript",
|
|
@@ -927,6 +927,8 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
927
927
|
"yarn",
|
|
928
928
|
"rush",
|
|
929
929
|
],
|
|
930
|
+
mcp: ["mcp"],
|
|
931
|
+
"ai-skill": ["ai-skill", "skill", "skills"],
|
|
930
932
|
py: [
|
|
931
933
|
"py",
|
|
932
934
|
"python",
|
|
@@ -980,7 +982,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
980
982
|
dart: ["dart", "flutter", "pub"],
|
|
981
983
|
haskell: ["haskell", "hackage", "cabal"],
|
|
982
984
|
elixir: ["elixir", "hex", "mix"],
|
|
983
|
-
c: ["c", "cpp", "c++", "conan"],
|
|
985
|
+
c: ["c", "cpp", "c++", "conan", "collider"],
|
|
984
986
|
clojure: ["clojure", "edn", "clj", "leiningen"],
|
|
985
987
|
github: ["github", "actions"],
|
|
986
988
|
os: ["os", "osquery", "windows", "linux", "mac", "macos", "darwin"],
|
|
@@ -1013,7 +1015,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
1013
1015
|
"visionos",
|
|
1014
1016
|
],
|
|
1015
1017
|
binary: ["binary", "blint"],
|
|
1016
|
-
oci: ["docker", "oci", "container", "podman"],
|
|
1018
|
+
oci: ["docker", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
1017
1019
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
1018
1020
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
1019
1021
|
nix: ["nix", "nixos", "flake"],
|
|
@@ -2584,6 +2586,23 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
|
|
|
2584
2586
|
value: "true",
|
|
2585
2587
|
});
|
|
2586
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
|
+
}
|
|
2587
2606
|
// This getter method could fail with errors at times.
|
|
2588
2607
|
// Example Error: Invalid tag name "^>=6.0.0" of package "^>=6.0.0": Tags may not have any characters that encodeURIComponent encodes.
|
|
2589
2608
|
try {
|
|
@@ -6810,8 +6829,17 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
6810
6829
|
value: origName,
|
|
6811
6830
|
});
|
|
6812
6831
|
}
|
|
6813
|
-
|
|
6814
|
-
|
|
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;
|
|
6815
6843
|
if (digest["sha256"]) {
|
|
6816
6844
|
p._integrity = `sha256-${digest["sha256"]}`;
|
|
6817
6845
|
} else if (digest["md5"]) {
|
|
@@ -7042,6 +7070,287 @@ export async function parsePiplockData(lockData) {
|
|
|
7042
7070
|
return await getPyMetadata(pkgList, false);
|
|
7043
7071
|
}
|
|
7044
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
|
+
|
|
7045
7354
|
/**
|
|
7046
7355
|
* Method to parse python pyproject.toml file
|
|
7047
7356
|
*
|
|
@@ -7101,6 +7410,7 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7101
7410
|
let tomlData;
|
|
7102
7411
|
const directDepsKeys = {};
|
|
7103
7412
|
const groupDepsKeys = {};
|
|
7413
|
+
const dependencySourceMap = {};
|
|
7104
7414
|
try {
|
|
7105
7415
|
tomlData = toml.parse(readFileSync(tomlFile, { encoding: "utf-8" }));
|
|
7106
7416
|
} catch (err) {
|
|
@@ -7172,8 +7482,19 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7172
7482
|
}
|
|
7173
7483
|
if (tomlData?.project?.dependencies) {
|
|
7174
7484
|
for (const adep of tomlData.project.dependencies) {
|
|
7175
|
-
|
|
7176
|
-
|
|
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
|
+
}
|
|
7177
7498
|
}
|
|
7178
7499
|
}
|
|
7179
7500
|
if (tomlData["dependency-groups"]) {
|
|
@@ -7185,6 +7506,15 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7185
7506
|
groupDepsKeys[pname] = [];
|
|
7186
7507
|
}
|
|
7187
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
|
+
}
|
|
7188
7518
|
} else {
|
|
7189
7519
|
return;
|
|
7190
7520
|
}
|
|
@@ -7205,6 +7535,29 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7205
7535
|
].includes(adep)
|
|
7206
7536
|
) {
|
|
7207
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
|
+
}
|
|
7208
7561
|
}
|
|
7209
7562
|
} // for
|
|
7210
7563
|
if (tomlData?.tool?.poetry?.group) {
|
|
@@ -7216,10 +7569,63 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7216
7569
|
groupDepsKeys[adep] = [];
|
|
7217
7570
|
}
|
|
7218
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
|
+
}
|
|
7219
7596
|
}
|
|
7220
7597
|
} // for
|
|
7221
7598
|
}
|
|
7222
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
|
+
}
|
|
7223
7629
|
return {
|
|
7224
7630
|
parentComponent: pkg,
|
|
7225
7631
|
poetryMode,
|
|
@@ -7228,9 +7634,146 @@ export function parsePyProjectTomlFile(tomlFile) {
|
|
|
7228
7634
|
workspacePaths,
|
|
7229
7635
|
directDepsKeys,
|
|
7230
7636
|
groupDepsKeys,
|
|
7637
|
+
dependencySourceMap,
|
|
7231
7638
|
};
|
|
7232
7639
|
}
|
|
7233
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
|
+
|
|
7234
7777
|
/**
|
|
7235
7778
|
* Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
|
|
7236
7779
|
*
|
|
@@ -7247,6 +7790,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7247
7790
|
const pkgBomRefMap = {};
|
|
7248
7791
|
let directDepsKeys = {};
|
|
7249
7792
|
let groupDepsKeys = {};
|
|
7793
|
+
let dependencySourceMap = {};
|
|
7250
7794
|
let parentComponent;
|
|
7251
7795
|
let workspacePaths;
|
|
7252
7796
|
let workspaceWarningShown = false;
|
|
@@ -7254,6 +7798,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7254
7798
|
let pyLockProperties = [];
|
|
7255
7799
|
// Keep track of any workspace components to be added to the parent component
|
|
7256
7800
|
const workspaceComponentMap = {};
|
|
7801
|
+
const workspaceDependencySourceMap = {};
|
|
7257
7802
|
const workspacePyProjMap = {};
|
|
7258
7803
|
const workspaceRefPyProjMap = {};
|
|
7259
7804
|
const pkgParentMap = {};
|
|
@@ -7273,6 +7818,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7273
7818
|
const pyProjMap = parsePyProjectTomlFile(pyProjectFile);
|
|
7274
7819
|
directDepsKeys = pyProjMap.directDepsKeys || {};
|
|
7275
7820
|
groupDepsKeys = pyProjMap.groupDepsKeys || {};
|
|
7821
|
+
dependencySourceMap = pyProjMap.dependencySourceMap || {};
|
|
7276
7822
|
parentComponent = pyProjMap.parentComponent;
|
|
7277
7823
|
workspacePaths = pyProjMap.workspacePaths;
|
|
7278
7824
|
if (workspacePaths?.length) {
|
|
@@ -7336,6 +7882,12 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7336
7882
|
}
|
|
7337
7883
|
}
|
|
7338
7884
|
const wparentComponentRef = wcompMap.parentComponent["bom-ref"];
|
|
7885
|
+
if (wcompMap?.dependencySourceMap) {
|
|
7886
|
+
Object.assign(
|
|
7887
|
+
workspaceDependencySourceMap,
|
|
7888
|
+
wcompMap.dependencySourceMap,
|
|
7889
|
+
);
|
|
7890
|
+
}
|
|
7339
7891
|
// Track the parents of workspace direct dependencies
|
|
7340
7892
|
if (wcompMap?.directDepsKeys) {
|
|
7341
7893
|
for (const wdd of Object.keys(wcompMap?.directDepsKeys)) {
|
|
@@ -7407,6 +7959,11 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7407
7959
|
value: workspacePyProjMap[apkg.name] || pyProjectFile,
|
|
7408
7960
|
});
|
|
7409
7961
|
}
|
|
7962
|
+
const manifestSource =
|
|
7963
|
+
dependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
7964
|
+
workspaceDependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
7965
|
+
collectPythonManifestSource(apkg);
|
|
7966
|
+
applyManifestSourceProperties(pkg, "cdx:pypi", manifestSource);
|
|
7410
7967
|
if (apkg.optional) {
|
|
7411
7968
|
pkg.scope = "optional";
|
|
7412
7969
|
}
|
|
@@ -7448,6 +8005,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7448
8005
|
});
|
|
7449
8006
|
}
|
|
7450
8007
|
}
|
|
8008
|
+
mergeExternalReferences(pkg, collectPythonLockDistributionReferences(apkg));
|
|
7451
8009
|
if (pyLockMode) {
|
|
7452
8010
|
pkg.properties = pkg.properties.concat(
|
|
7453
8011
|
collectPyLockPackageProperties(apkg),
|
|
@@ -7510,9 +8068,17 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7510
8068
|
}
|
|
7511
8069
|
}
|
|
7512
8070
|
}
|
|
7513
|
-
|
|
8071
|
+
const metadataFileEntries = collectPythonLockMetadataFileEntries(
|
|
8072
|
+
lockTomlObj,
|
|
8073
|
+
pkg,
|
|
8074
|
+
);
|
|
8075
|
+
mergeExternalReferences(
|
|
8076
|
+
pkg,
|
|
8077
|
+
collectPythonLockMetadataDistributionReferences(metadataFileEntries),
|
|
8078
|
+
);
|
|
8079
|
+
if (metadataFileEntries.length) {
|
|
7514
8080
|
pkg.components = [];
|
|
7515
|
-
for (const afileObj of
|
|
8081
|
+
for (const afileObj of metadataFileEntries) {
|
|
7516
8082
|
const hashParts = afileObj?.hash?.split(":");
|
|
7517
8083
|
let hashes;
|
|
7518
8084
|
if (hashParts?.length === 2) {
|
|
@@ -7546,8 +8112,9 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
7546
8112
|
pkg.components = (pkg.components || []).concat(pylockFileComponents);
|
|
7547
8113
|
}
|
|
7548
8114
|
}
|
|
8115
|
+
const normalizedPkgName = normalizePythonDependencyKey(pkg.name);
|
|
7549
8116
|
if (
|
|
7550
|
-
directDepsKeys[
|
|
8117
|
+
directDepsKeys[normalizedPkgName] ||
|
|
7551
8118
|
(hasWorkspaces && !Object.keys(workspaceComponentMap).length)
|
|
7552
8119
|
) {
|
|
7553
8120
|
rootList.push(pkg);
|
|
@@ -7742,6 +8309,23 @@ export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
|
7742
8309
|
const LICENSE_ID_COMMENTS_PATTERN =
|
|
7743
8310
|
/^(Apache-2\.0|MIT|ISC|GPL-|LGPL-|BSD-[23]-Clause)/i;
|
|
7744
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
|
+
|
|
7745
8329
|
/**
|
|
7746
8330
|
* Method to parse requirements.txt file. Must only be used internally.
|
|
7747
8331
|
*
|
|
@@ -7779,11 +8363,16 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7779
8363
|
const lines = normalizedData.split("\n");
|
|
7780
8364
|
for (const line of lines) {
|
|
7781
8365
|
let l = line.trim();
|
|
8366
|
+
let editableRequirement = false;
|
|
7782
8367
|
if (l.includes("# Basic requirements")) {
|
|
7783
8368
|
compScope = "required";
|
|
7784
8369
|
} else if (l.includes("added by pip freeze")) {
|
|
7785
8370
|
compScope = undefined;
|
|
7786
8371
|
}
|
|
8372
|
+
if (l.startsWith("-e ") || l.startsWith("--editable ")) {
|
|
8373
|
+
editableRequirement = true;
|
|
8374
|
+
l = l.replace(/^--editable\s+|^-e\s+/, "").trim();
|
|
8375
|
+
}
|
|
7787
8376
|
if (l.startsWith("Skipping line") || l.startsWith("(add")) {
|
|
7788
8377
|
continue;
|
|
7789
8378
|
}
|
|
@@ -7832,6 +8421,45 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7832
8421
|
markers = parts.slice(1).join(";").trim();
|
|
7833
8422
|
structuredMarkers = parseReqEnvMarkers(markers);
|
|
7834
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
|
+
}
|
|
7835
8463
|
|
|
7836
8464
|
// Handle extras (e.g., package[extra1,extra2])
|
|
7837
8465
|
let extras = null;
|
|
@@ -7865,20 +8493,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7865
8493
|
if (hashes.length > 0) {
|
|
7866
8494
|
apkg.hashes = hashes;
|
|
7867
8495
|
}
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
.map((c) => {
|
|
7872
|
-
const licenseObj = {};
|
|
7873
|
-
const cId = c.trim();
|
|
7874
|
-
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
7875
|
-
licenseObj.id = cId;
|
|
7876
|
-
} else {
|
|
7877
|
-
return undefined;
|
|
7878
|
-
}
|
|
7879
|
-
return { license: licenseObj };
|
|
7880
|
-
})
|
|
7881
|
-
.filter((l) => l !== undefined);
|
|
8496
|
+
const licenses = parseLicenseComment(comment);
|
|
8497
|
+
if (licenses) {
|
|
8498
|
+
apkg.licenses = licenses;
|
|
7882
8499
|
}
|
|
7883
8500
|
if (extras && extras.length > 0) {
|
|
7884
8501
|
properties.push({
|
|
@@ -7904,6 +8521,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7904
8521
|
});
|
|
7905
8522
|
}
|
|
7906
8523
|
}
|
|
8524
|
+
if (editableRequirement) {
|
|
8525
|
+
properties.push({
|
|
8526
|
+
name: "cdx:pypi:editable",
|
|
8527
|
+
value: "true",
|
|
8528
|
+
});
|
|
8529
|
+
}
|
|
7907
8530
|
if (properties.length) {
|
|
7908
8531
|
apkg.properties = properties;
|
|
7909
8532
|
}
|
|
@@ -7931,20 +8554,9 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7931
8554
|
scope: compScope,
|
|
7932
8555
|
evidence,
|
|
7933
8556
|
};
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
.map((c) => {
|
|
7938
|
-
const licenseObj = {};
|
|
7939
|
-
const cId = c.trim();
|
|
7940
|
-
if (cId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
7941
|
-
licenseObj.id = cId;
|
|
7942
|
-
} else {
|
|
7943
|
-
return undefined;
|
|
7944
|
-
}
|
|
7945
|
-
return { license: licenseObj };
|
|
7946
|
-
})
|
|
7947
|
-
.filter((l) => l !== undefined);
|
|
8557
|
+
const licenses = parseLicenseComment(comment);
|
|
8558
|
+
if (licenses) {
|
|
8559
|
+
apkg.licenses = licenses;
|
|
7948
8560
|
}
|
|
7949
8561
|
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
7950
8562
|
properties.push({
|
|
@@ -7964,6 +8576,12 @@ async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
|
7964
8576
|
});
|
|
7965
8577
|
}
|
|
7966
8578
|
}
|
|
8579
|
+
if (editableRequirement) {
|
|
8580
|
+
properties.push({
|
|
8581
|
+
name: "cdx:pypi:editable",
|
|
8582
|
+
value: "true",
|
|
8583
|
+
});
|
|
8584
|
+
}
|
|
7967
8585
|
if (properties.length) {
|
|
7968
8586
|
apkg.properties = properties;
|
|
7969
8587
|
}
|
|
@@ -12664,6 +13282,188 @@ export function parseConanData(conanData) {
|
|
|
12664
13282
|
return pkgList;
|
|
12665
13283
|
}
|
|
12666
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
|
+
|
|
12667
13467
|
/**
|
|
12668
13468
|
* Parse Leiningen project.clj data and extract dependency packages.
|
|
12669
13469
|
*
|
|
@@ -18362,6 +19162,92 @@ export function parsePackageJsonName(name) {
|
|
|
18362
19162
|
return returnObject;
|
|
18363
19163
|
}
|
|
18364
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
|
+
|
|
18365
19251
|
/**
|
|
18366
19252
|
* Method to add occurrence evidence for components based on import statements. Currently useful for js
|
|
18367
19253
|
*
|
|
@@ -19959,7 +20845,7 @@ export function collectExecutables(basePath, binPaths) {
|
|
|
19959
20845
|
if (!binPaths) {
|
|
19960
20846
|
return [];
|
|
19961
20847
|
}
|
|
19962
|
-
|
|
20848
|
+
const executablesByResolvedPath = new Map();
|
|
19963
20849
|
const ignoreList = [
|
|
19964
20850
|
"**/*.{h,c,cpp,hpp,man,txt,md,htm,html,jar,ear,war,zip,tar,egg,keepme,gitignore,json,js,py,pyc}",
|
|
19965
20851
|
"[",
|
|
@@ -19975,12 +20861,38 @@ export function collectExecutables(basePath, binPaths) {
|
|
|
19975
20861
|
follow: true,
|
|
19976
20862
|
ignore: ignoreList,
|
|
19977
20863
|
});
|
|
19978
|
-
|
|
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
|
+
}
|
|
19979
20879
|
} catch (_err) {
|
|
19980
20880
|
// ignore
|
|
19981
20881
|
}
|
|
19982
20882
|
}
|
|
19983
|
-
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;
|
|
19984
20896
|
}
|
|
19985
20897
|
|
|
19986
20898
|
/**
|