Skip to content

Commit

Permalink
Merge branch 'CycloneDX:master' into snapshot_test
Browse files Browse the repository at this point in the history
  • Loading branch information
AnsahMohammad authored Jan 22, 2025
2 parents 0df1831 + 459dfe1 commit 145c1d6
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 43 deletions.
2 changes: 1 addition & 1 deletion bin/cdxgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const args = yargs(hideBin(process.argv))
})
.option("install-deps", {
type: "boolean",
default: true,
description:
"Install dependencies automatically for some projects. Defaults to true but disabled for containers and oci scans. Use --no-install-deps to disable this feature.",
})
Expand Down Expand Up @@ -506,7 +507,6 @@ const applyAdvancedOptions = (options) => {
options.installDeps = true;
break;
default:
options.installDeps = true;
break;
}
// When the user specifies source-code-analysis as a technique, then enable deep and evidence mode.
Expand Down
10 changes: 7 additions & 3 deletions lib/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2533,9 +2533,9 @@ export async function createNodejsBom(path, options) {
const apkgRef = `pkg:npm/${adep}`;
workspaceDirectDeps[workspaceRef].add(apkgRef);
if (!depsWorkspaceRefs[apkgRef]) {
depsWorkspaceRefs[apkgRef] = new Set();
depsWorkspaceRefs[apkgRef] = [];
}
depsWorkspaceRefs[apkgRef].add(workspaceRef);
depsWorkspaceRefs[apkgRef].push(workspaceRef);
}
}
}
Expand Down Expand Up @@ -5986,7 +5986,11 @@ export function trimComponents(components) {
const keyCache = {};
const filteredComponents = [];
for (const comp of components) {
const key = comp.purl || comp["bom-ref"] || comp.name + comp.version;
const key = (
comp.purl ||
comp["bom-ref"] ||
comp.name + comp.version
).toLowerCase();
if (!keyCache[key]) {
keyCache[key] = comp;
} else {
Expand Down
239 changes: 206 additions & 33 deletions lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1968,6 +1968,59 @@ function _markTreeOptional(
}
}

function _setTreeWorkspaceRef(
dependenciesMap,
depref,
pkgRefMap,
wref,
wsrcFile,
depsWorkspaceRefs,
) {
for (const dref of dependenciesMap[depref] || []) {
const addedMap = {};
const depPkg = pkgRefMap[dref];
if (!depPkg) {
continue;
}
const wsprops = depPkg.properties.filter(
(p) => p.name === "internal:workspaceRef",
);
if (wsprops.length) {
continue;
}
depPkg.properties = depPkg.properties || [];
for (const prop of depPkg.properties) {
addedMap[prop.value] = true;
}
if (!addedMap[wref]) {
depPkg.properties.push({
name: "internal:workspaceRef",
value: wref,
});
addedMap[wref] = true;
}
if (wsrcFile && !addedMap[wsrcFile]) {
depPkg.properties.push({
name: "internal:workspaceSrcFile",
value: wsrcFile,
});
addedMap[wsrcFile] = true;
}
depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref] || [];
depsWorkspaceRefs[dref] = depsWorkspaceRefs[dref].concat(
dependenciesMap[depref] || [],
);
_setTreeWorkspaceRef(
dependenciesMap,
dref,
pkgRefMap,
wref,
wsrcFile,
depsWorkspaceRefs,
);
}
}

async function getVersionNumPnpm(depPkg, relativePath) {
let version = depPkg;
if (typeof version === "object" && depPkg.version) {
Expand Down Expand Up @@ -2059,6 +2112,7 @@ export async function parsePnpmLock(
const parentSubComponents = [];
const srcFilesMap = {};
const workspacePackageNames = {};
const pkgRefMap = {};
// Track references to packages that are directly installed from github.com
const gitPkgRefs = {};
// pnpm could refer to packages from git sources
Expand Down Expand Up @@ -2569,6 +2623,7 @@ export async function parsePnpmLock(
}
}
const purlNoVersion = new PackageURL("npm", group, name).toString();
let packageType = "library";
const theBomRef = decodeURIComponent(purlString);
if (
workspacePackageNames[decodeURIComponent(purlNoVersion)] ||
Expand All @@ -2578,6 +2633,7 @@ export async function parsePnpmLock(
name: "internal:is_workspace",
value: "true",
});
packageType = "application";
const wsSrcFile =
workspaceSrcFiles[decodeURIComponent(purlNoVersion)] ||
workspaceSrcFiles[theBomRef];
Expand Down Expand Up @@ -2634,6 +2690,7 @@ export async function parsePnpmLock(
version: version,
purl: purlString,
"bom-ref": theBomRef,
type: packageType,
scope,
_integrity: integrity,
properties,
Expand All @@ -2651,7 +2708,11 @@ export async function parsePnpmLock(
},
},
};
pkgList.push(thePkg);
// Don't add internal workspace packages to the components list
if (thePkg.type !== "application") {
pkgList.push(thePkg);
}
pkgRefMap[thePkg["bom-ref"]] = thePkg;
}
}
}
Expand Down Expand Up @@ -2703,35 +2764,53 @@ export async function parsePnpmLock(
if (requiredDependencies[apkg["bom-ref"]]) {
apkg.scope = undefined;
}
if (depsWorkspaceRefs[apkg["bom-ref"]]?.length) {
// There are no workspaces so exit early
if (!Object.keys(workspacePackageNames).length) {
continue;
}
const purlNoVersion = decodeURIComponent(
new PackageURL("npm", apkg.group, apkg.name).toString(),
);
const wsRefs =
depsWorkspaceRefs[apkg["bom-ref"]] || depsWorkspaceRefs[purlNoVersion];
// There is a workspace reference
if (wsRefs?.length) {
const wsprops = apkg.properties.filter(
(p) => p.name === "internal:workspaceRef",
);
if (!wsprops.length) {
for (const wref of depsWorkspaceRefs[apkg["bom-ref"]]) {
// Such a cycle should never happen, but we can't sure
if (wref === apkg["bom-ref"]) {
continue;
}
// workspace properties are already set.
if (wsprops.length) {
continue;
}
for (const wref of wsRefs) {
// Such a cycle should never happen, but we can't sure
if (wref === apkg["bom-ref"]) {
continue;
}
apkg.properties.push({
name: "internal:workspaceRef",
value: wref,
});
const purlObj = PackageURL.fromString(apkg.purl);
purlObj.version = undefined;
const wrefNoVersion = decodeURIComponent(purlObj.toString());
const wsrcFile =
workspaceSrcFiles[wref] || workspaceSrcFiles[wrefNoVersion];
if (wsrcFile) {
apkg.properties.push({
name: "internal:workspaceRef",
value: wref,
name: "internal:workspaceSrcFile",
value: wsrcFile,
});
const purlObj = PackageURL.fromString(apkg.purl);
purlObj.version = undefined;
const wrefNoVersion = decodeURIComponent(purlObj.toString());
if (workspaceSrcFiles[wref]) {
apkg.properties.push({
name: "internal:workspaceSrcFile",
value: workspaceSrcFiles[wref],
});
} else if (workspaceSrcFiles[wrefNoVersion]) {
apkg.properties.push({
name: "internal:workspaceSrcFile",
value: workspaceSrcFiles[wrefNoVersion],
});
}
}
// Repeat for the children
_setTreeWorkspaceRef(
dependenciesMap,
apkg["bom-ref"],
pkgRefMap,
wref,
wsrcFile,
depsWorkspaceRefs,
);
}
}
}
Expand Down Expand Up @@ -4915,7 +4994,11 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
// This would help the lookup
existingPkgMap[pkg.name.toLowerCase()] = pkg["bom-ref"];
pkgBomRefMap[pkg["bom-ref"]] = pkg;
pkgList.push(pkg);
// Do not repeat workspace components again under components
// This will reduce false positives, when a downstream tool attempts to analyze all components
if (pkg.type !== "application") {
pkgList.push(pkg);
}
if (!depsMap[pkg["bom-ref"]]) {
depsMap[pkg["bom-ref"]] = new Set();
}
Expand All @@ -4928,10 +5011,33 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
depsMap[pkgParentRef].add(pkg["bom-ref"]);
}
}
if (apkg.dependencies) {
let optionalDependencies = [];
let devDependencies = [];
if (apkg["dev-dependencies"]) {
for (const agroup of Object.keys(apkg["dev-dependencies"])) {
devDependencies = devDependencies.concat(
apkg["dev-dependencies"][agroup],
);
}
}
if (apkg["optional-dependencies"]) {
for (const agroup of Object.keys(apkg["optional-dependencies"])) {
optionalDependencies = optionalDependencies.concat(
apkg["optional-dependencies"][agroup],
);
}
}
if (
apkg.dependencies ||
devDependencies.length ||
optionalDependencies.length
) {
if (Array.isArray(apkg.dependencies)) {
// pdm.lock files
for (const apkgDep of apkg.dependencies) {
let allDeps = apkg.dependencies;
allDeps = allDeps.concat(devDependencies);
allDeps = allDeps.concat(optionalDependencies);
for (const apkgDep of allDeps) {
// Example: "msgpack>=0.5.2"
const nameStr =
apkgDep.name || apkgDep.split(/(==|<=|~=|>=)/)[0].split(" ")[0];
Expand All @@ -4944,6 +5050,18 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
const dependentPkg = pkgBomRefMap[existingPkgMap[nameStr]];
dependentPkg.properties = dependentPkg.properties || [];
const addedValue = {};
// Is the parent a workspace
if (workspaceComponentMap[pkg.name]) {
dependentPkg.properties.push({
name: "internal:workspaceRef",
value: pkg["bom-ref"],
});
dependentPkg.properties.push({
name: "internal:workspaceSrcFile",
value: workspaceRefPyProjMap[pkg["bom-ref"]],
});
addedValue[pkg["bom-ref"]] = true;
}
for (const pprop of pkg.properties) {
if (
pprop.name.startsWith("internal:workspace") &&
Expand All @@ -4955,14 +5073,13 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
}
}
}
} else if (Object.keys(apkg.dependencies).length) {
} else if (pkg.dependencies && Object.keys(apkg.dependencies).length) {
for (const apkgDep of Object.keys(apkg.dependencies)) {
depsMap[pkg["bom-ref"]].add(existingPkgMap[apkgDep] || apkgDep);
}
}
}
}
pkgList = await getPyMetadata(pkgList, false);
for (const key of Object.keys(depsMap)) {
const dependsOnList = new Set();
const parentPkg = pkgBomRefMap[key];
Expand Down Expand Up @@ -4995,6 +5112,35 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
) {
dependentPkg.properties.push(pprop);
addedValue[pprop.value] = true;
} else if (pprop.name === "internal:is_workspace") {
dependentPkg.properties.push({
name: "internal:workspaceRef",
value: parentPkg["bom-ref"],
});
dependentPkg.properties.push({
name: "internal:workspaceSrcFile",
value: workspaceRefPyProjMap[parentPkg["bom-ref"]],
});
addedValue[parentPkg["bom-ref"]] = true;
addedValue[workspaceRefPyProjMap[parentPkg["bom-ref"]]] = true;
const childDeps = depsMap[dependentPkg["bom-ref"]];
for (const childRef of childDeps) {
if (!childRef.startsWith("pkg:")) {
continue;
}
const childPkg = pkgBomRefMap[childRef];
if (childPkg) {
childPkg.properties = childPkg.properties || [];
childPkg.properties.push({
name: "internal:workspaceRef",
value: parentPkg["bom-ref"],
});
childPkg.properties.push({
name: "internal:workspaceSrcFile",
value: workspaceRefPyProjMap[parentPkg["bom-ref"]],
});
}
}
}
}
}
Expand All @@ -5004,6 +5150,7 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
dependsOn: [...dependsOnList].sort(),
});
}
pkgList = await getPyMetadata(pkgList, false);
return {
parentComponent,
pkgList,
Expand Down Expand Up @@ -9367,17 +9514,43 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
continue;
}
const targetNameVersion = `${nameOperatorVersion.name}/${nameOperatorVersion.version}`;

// skip if the dep is not in the targets for whatever reason
let nameToUse = nameOperatorVersion.name;
// Due to the difference in casing, we might arrive this case where a simple lookup doesn't succeed.
// Instead of skipping, let's work harder to find a match.
if (!csProjData.targets[frameworkTarget][targetNameVersion]) {
continue;
let matchFound = false;
for (const fkeys of Object.keys(
csProjData.targets[frameworkTarget],
)) {
const tmpParts = fkeys.split("/");
const tname = tmpParts[0];
const tversion = tmpParts[1];
if (
tname.toLowerCase() === nameOperatorVersion.name.toLowerCase() &&
tversion === nameOperatorVersion.version
) {
nameToUse = tname;
matchFound = true;
break;
}
}
if (!matchFound) {
if (DEBUG_MODE) {
console.log(
"Unable to match",
dependencyName,
"with a target name. The dependency tree will be imprecise.",
);
}
continue;
}
}

const dpurl = decodeURIComponent(
new PackageURL(
"nuget",
"",
nameOperatorVersion.name,
nameToUse,
nameOperatorVersion.version,
null,
null,
Expand Down
Loading

0 comments on commit 145c1d6

Please sign in to comment.