@cyclonedx/cdxgen
Version:
Creates CycloneDX Software Bill of Materials (SBOM) from source or container image
1,552 lines (1,505 loc) • 371 kB
JavaScript
import { Buffer } from "node:buffer";
import {
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { platform, tmpdir } from "node:os";
import path from "node:path";
import process from "node:process";
import esmock from "esmock";
import { PackageURL } from "packageurl-js";
import { assert, describe, it, test } from "poku";
import sinon from "sinon";
import { parse } from "ssri";
import { parse as loadYaml } from "yaml";
import { validateRefs } from "../validator/bomValidator.js";
import {
addEvidenceForDotnet,
addEvidenceForImports,
attachIdentityTools,
buildObjectForCocoaPod,
buildObjectForGradleModule,
cdxgenAgent,
collectExecutables,
collectSharedLibs,
convertOSQueryResults,
encodeForPurl,
extractToolRefs,
findLicenseId,
findPnpmPackagePath,
getAllFiles,
getCratesMetadata,
getDartMetadata,
getDefaultBomAuditCategories,
getLicenses,
getMvnMetadata,
getPropertyGroupTextNodes,
getPyMetadata,
getRecordedActivities,
guessPypiMatchingVersion,
hasAnyProjectType,
inferJarGroupFromManifest,
isAllowedHttpHost,
isDryRunError,
isPackageManagerAllowed,
isPartialTree,
isValidIriReference,
mapConanPkgRefToPurlStringAndNameAndVersion,
PROJECT_TYPE_ALIASES,
parseBazelActionGraph,
parseBazelBuild,
parseBazelSkyframe,
parseBdistMetadata,
parseBitbucketPipelinesFile,
parseBowerJson,
parseCabalData,
parseCargoAuditableData,
parseCargoData,
parseCargoDependencyData,
parseCargoManifestDependencyData,
parseCargoTomlData,
parseCljDep,
parseCloudBuildData,
parseCmakeDotFile,
parseCmakeLikeFile,
parseCocoaDependency,
parseColliderLockData,
parseComposerJson,
parseComposerLock,
parseConanData,
parseConanLockData,
parseContainerFile,
parseContainerSpecData,
parseCsPkgData,
parseCsPkgLockData,
parseCsProjAssetsData,
parseCsProjData,
parseEdnData,
parseFlakeLock,
parseFlakeNix,
parseGemfileLockData,
parseGemspecData,
parseGitHubWorkflowData,
parseGoListDep,
parseGoModData,
parseGoModGraph,
parseGoModulesTxt,
parseGoModWhy,
parseGopkgData,
parseGosumData,
parseGoVersionData,
parseGradleDep,
parseGradleProjects,
parseGradleProperties,
parseHelmYamlData,
parseJarManifest,
parseKVDep,
parseLeinDep,
parseLeiningenData,
parseMakeDFile,
parseMavenTree,
parseMillDependency,
parseMinJs,
parseMixLockData,
parseNodeShrinkwrap,
parseNupkg,
parseNuspecData,
parseOpenapiSpecData,
parsePackageJsonName,
parsePaketLockData,
parsePiplockData,
parsePkgJson,
parsePkgLock,
parsePnpmLock,
parsePnpmWorkspace,
parsePodfileLock,
parsePodfileTargets,
parsePom,
parsePomProperties,
parsePrivadoFile,
parsePubLockData,
parsePubYamlData,
parsePyLockData,
parsePyProjectTomlFile,
parsePyRequiresDist,
parseReqEnvMarkers,
parseReqFile,
parseSbtLock,
parseSbtTree,
parseSetupPyFile,
parseSwiftJsonTree,
parseSwiftResolved,
parseYarnLock,
pnpmMetadata,
purlFromUrlString,
readEnvironmentVariable,
readZipEntry,
recordSensitiveFileRead,
recordSymlinkResolution,
resetRecordedActivities,
safeExistsSync,
safeMkdtempSync,
safeRmSync,
safeSpawnSync,
safeUnlinkSync,
safeWriteSync,
setDryRunMode,
shouldRunPredictiveBomAudit,
splitOutputByGradleProjects,
toGemModuleNames,
trimJarGroupSuffix,
yarnLockToIdentMap,
} from "./utils.js";
const jarMetadataFixturesDir = path.resolve("test", "data", "jar-metadata");
function createMockedProcess(envOverrides = {}) {
const env = {
...process.env,
};
for (const [key, value] of Object.entries(envOverrides)) {
if (value === undefined) {
delete env[key];
} else {
env[key] = value;
}
}
const mockedProcess = Object.create(process);
mockedProcess.argv = [...process.argv];
mockedProcess.env = env;
return mockedProcess;
}
function readJarMetadataFixture(...segments) {
return readFileSync(path.join(jarMetadataFixturesDir, ...segments), {
encoding: "utf-8",
});
}
it("SSRI test", () => {
// gopkg.lock hash
let ss = parse(
"2ca532a6bc655663344004ba102436d29031018eab236247678db1d8978627bf",
);
assert.deepStrictEqual(ss, null);
ss = parse(
"sha256-2ca532a6bc655663344004ba102436d29031018eab236247678db1d8978627bf",
);
assert.deepStrictEqual(
ss.sha256[0].digest,
"2ca532a6bc655663344004ba102436d29031018eab236247678db1d8978627bf",
);
ss = parse(
`sha256-${Buffer.from(
"2ca532a6bc655663344004ba102436d29031018eab236247678db1d8978627bf",
"hex",
).toString("base64")}`,
);
assert.deepStrictEqual(
ss.sha256[0].digest,
"LKUyprxlVmM0QAS6ECQ20pAxAY6rI2JHZ42x2JeGJ78=",
);
ss = parse(
"sha512-Vn0lE2mprXEFPcRoI89xjw1fk1VJiyVbwfaPnVnvCXxEieByioO8Mj6sMwa6ON9PRuqbAjIxaQpkzccu41sYlw==",
);
assert.deepStrictEqual(
ss.sha512[0].digest,
"Vn0lE2mprXEFPcRoI89xjw1fk1VJiyVbwfaPnVnvCXxEieByioO8Mj6sMwa6ON9PRuqbAjIxaQpkzccu41sYlw==",
);
});
it("Parse requires dist string", () => {
assert.deepStrictEqual(
parsePyRequiresDist("lazy-object-proxy (>=1.4.0)"),
{
name: "lazy-object-proxy",
version: "1.4.0",
},
);
assert.deepStrictEqual(parsePyRequiresDist("wrapt (<1.13,>=1.11)"), {
name: "wrapt",
version: "1.13",
});
assert.deepStrictEqual(
parsePyRequiresDist(
'typed-ast (<1.5,>=1.4.0) ; implementation_name == "cpython" and python_version < "3.8"',
),
{ name: "typed-ast", version: "1.5" },
);
assert.deepStrictEqual(parsePyRequiresDist("asgiref (<4,>=3.2.10)"), {
name: "asgiref",
version: "4",
});
assert.deepStrictEqual(parsePyRequiresDist("pytz"), {
name: "pytz",
version: "",
});
assert.deepStrictEqual(parsePyRequiresDist("sqlparse (>=0.2.2)"), {
name: "sqlparse",
version: "0.2.2",
});
assert.deepStrictEqual(
parsePyRequiresDist("argon2-cffi (>=16.1.0) ; extra == 'argon2'"),
{ name: "argon2-cffi", version: "16.1.0" },
);
assert.deepStrictEqual(parsePyRequiresDist("bcrypt ; extra == 'bcrypt'"), {
name: "bcrypt",
version: "",
});
});
it("finds license id from name", () => {
assert.deepStrictEqual(
findLicenseId("Apache License Version 2.0"),
"Apache-2.0",
);
assert.deepStrictEqual(
findLicenseId("GNU General Public License (GPL) version 2.0"),
"GPL-2.0-only",
);
});
it("safeSpawnSync() resets ANSI color state for host pip warnings", () => {
const originalConsoleWarn = console.warn;
const originalContainer = process.env.CDXGEN_IN_CONTAINER;
const originalNoticeCache = globalThis.__cdxgenNoticeCache;
const warnings = [];
delete process.env.CDXGEN_IN_CONTAINER;
delete globalThis.__cdxgenNoticeCache;
console.warn = (message) => {
warnings.push(message);
};
try {
safeSpawnSync("pip-cdxgen-test", ["install"], {});
assert.strictEqual(warnings.length, 1);
assert.ok(
warnings[0].startsWith(
"\x1b[1;35mNotice: pip/uv install invoked without '--only-binary'.",
),
);
assert.ok(warnings[0].endsWith("\x1b[0m"));
assert.ok(!warnings[0].endsWith("\x1b"));
} finally {
console.warn = originalConsoleWarn;
if (originalContainer === undefined) {
delete process.env.CDXGEN_IN_CONTAINER;
} else {
process.env.CDXGEN_IN_CONTAINER = originalContainer;
}
if (originalNoticeCache === undefined) {
delete globalThis.__cdxgenNoticeCache;
} else {
globalThis.__cdxgenNoticeCache = originalNoticeCache;
}
}
});
it("safeSpawnSync() returns a dry-run sentinel result when dry run mode is enabled", () => {
setDryRunMode(true);
resetRecordedActivities();
try {
const result = safeSpawnSync("node", ["--version"], {});
assert.strictEqual(result.status, 1);
assert.ok(isDryRunError(result.error));
const executeActivity = getRecordedActivities().find(
(activity) => activity.kind === "execute",
);
assert.ok(executeActivity);
assert.strictEqual(executeActivity.status, "blocked");
} finally {
setDryRunMode(false);
resetRecordedActivities();
}
});
it("safeSpawnSync() does not classify non-probe -v commands as version checks", () => {
setDryRunMode(true);
resetRecordedActivities();
try {
safeSpawnSync("swift", ["package", "-v", "resolve"], {});
const executeActivity = getRecordedActivities().find(
(activity) => activity.kind === "execute",
);
assert.ok(executeActivity);
assert.strictEqual(executeActivity.probeType, undefined);
assert.ok(!/version check/i.test(executeActivity.reason));
} finally {
setDryRunMode(false);
resetRecordedActivities();
}
});
it("safeSpawnSync() blocks shell metacharacters with shell execution", () => {
const markerFile = path.join(tmpdir(), "cdxgen-safe-spawn-shell-marker");
const originalConsoleWarn = console.warn;
const warnings = [];
rmSync(markerFile, { force: true });
resetRecordedActivities();
console.warn = (message) => {
warnings.push(message);
};
try {
const result = safeSpawnSync("printf", ["blocked", ">", markerFile], {
shell: true,
});
assert.strictEqual(result.status, 1);
assert.ok(result.error);
assert.match(result.error.message, /shell metacharacters/);
assert.strictEqual(existsSync(markerFile), false);
assert.ok(warnings.some((warning) => /Security Alert/.test(warning)));
} finally {
console.warn = originalConsoleWarn;
resetRecordedActivities();
rmSync(markerFile, { force: true });
}
});
it("safeSpawnSync() reads CDXGEN_ALLOWED_COMMANDS once per invocation", () => {
const originalAllowedCommands = process.env.CDXGEN_ALLOWED_COMMANDS;
process.env.CDXGEN_ALLOWED_COMMANDS = "echo-cdxgen-test";
setDryRunMode(true);
resetRecordedActivities();
try {
safeSpawnSync("echo-cdxgen-test", ["value"], {});
const envActivities = getRecordedActivities().filter(
(activity) => activity.target === "process.env:CDXGEN_ALLOWED_COMMANDS",
);
assert.strictEqual(envActivities.length, 1);
assert.strictEqual(envActivities[0].count, 1);
} finally {
if (originalAllowedCommands === undefined) {
delete process.env.CDXGEN_ALLOWED_COMMANDS;
} else {
process.env.CDXGEN_ALLOWED_COMMANDS = originalAllowedCommands;
}
setDryRunMode(false);
resetRecordedActivities();
}
});
it("safeSpawnSync() records stdout and stderr byte sizes in debug mode", async () => {
const mockedProcess = createMockedProcess({
CDXGEN_ALLOWED_COMMANDS: "echo-cdxgen-test",
CDXGEN_DEBUG_MODE: "debug",
CDXGEN_SECURE_MODE: undefined,
NODE_OPTIONS: undefined,
});
const utilsModule = await esmock("./utils.js", {
"node:child_process": {
spawnSync: sinon.stub().returns({
status: 0,
stdout: "hello",
stderr: "warn",
}),
},
"node:process": {
default: mockedProcess,
},
});
utilsModule.resetRecordedActivities();
utilsModule.safeSpawnSync("echo-cdxgen-test", ["value"], {});
const executeActivity = utilsModule
.getRecordedActivities()
.find(
(activity) =>
activity.kind === "execute" &&
activity.target === "echo-cdxgen-test value",
);
assert.ok(executeActivity);
assert.strictEqual(executeActivity.stdoutBytes, 5);
assert.strictEqual(executeActivity.stderrBytes, 4);
utilsModule.resetRecordedActivities();
});
it("safeExtractArchive() records source byte size in debug mode", async () => {
const mockedProcess = createMockedProcess({
CDXGEN_ALLOWED_COMMANDS: undefined,
CDXGEN_DEBUG_MODE: "debug",
CDXGEN_SECURE_MODE: undefined,
NODE_OPTIONS: undefined,
});
const utilsModule = await esmock("./utils.js", {
"node:process": {
default: mockedProcess,
},
});
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-archive-trace-"));
const sourcePath = path.join(tempDir, "archive.zip");
const targetPath = path.join(tempDir, "extracted");
mkdirSync(targetPath, { recursive: true });
writeFileSync(sourcePath, "abc");
utilsModule.resetRecordedActivities();
await utilsModule.safeExtractArchive(
sourcePath,
targetPath,
async () => {
writeFileSync(path.join(targetPath, "a.txt"), "hello");
mkdirSync(path.join(targetPath, "nested"), { recursive: true });
writeFileSync(path.join(targetPath, "nested", "b.txt"), "xy");
},
"unzip",
);
const archiveActivity = utilsModule
.getRecordedActivities()
.find(
(activity) =>
activity.kind === "unzip" &&
activity.target === `${sourcePath} -> ${targetPath}`,
);
assert.ok(archiveActivity);
assert.strictEqual(archiveActivity.status, "completed");
assert.strictEqual(archiveActivity.sourceBytes, 3);
rmSync(tempDir, { recursive: true, force: true });
});
it("safeExtractArchive() records failed extraction activity in debug mode", async () => {
const mockedProcess = createMockedProcess({
CDXGEN_ALLOWED_COMMANDS: undefined,
CDXGEN_DEBUG_MODE: "debug",
CDXGEN_SECURE_MODE: undefined,
NODE_OPTIONS: undefined,
});
const utilsModule = await esmock("./utils.js", {
"node:process": {
default: mockedProcess,
},
});
const tempDir = mkdtempSync(path.join(tmpdir(), "cdxgen-archive-trace-"));
const sourcePath = path.join(tempDir, "archive.tar");
const targetPath = path.join(tempDir, "extracted");
mkdirSync(targetPath, { recursive: true });
writeFileSync(sourcePath, "abcd");
utilsModule.resetRecordedActivities();
const extractionError = new Error("permission denied");
extractionError.code = "EACCES";
await assert.rejects(
utilsModule.safeExtractArchive(
sourcePath,
targetPath,
async () => {
throw extractionError;
},
"untar",
),
extractionError,
);
const archiveActivity = utilsModule
.getRecordedActivities()
.find(
(activity) =>
activity.kind === "untar" &&
activity.target === `${sourcePath} -> ${targetPath}`,
);
assert.ok(archiveActivity);
assert.strictEqual(archiveActivity.status, "failed");
assert.strictEqual(archiveActivity.errorCode, "EACCES");
assert.strictEqual(archiveActivity.sourceBytes, 4);
rmSync(tempDir, { recursive: true, force: true });
});
it("records dry-run environment variable reads via helper access", () => {
const originalEnvValue = process.env.CDXGEN_TEST_ENV_READ;
process.env.CDXGEN_TEST_ENV_READ = "trace-me";
setDryRunMode(true);
resetRecordedActivities();
try {
readEnvironmentVariable("CDXGEN_TEST_ENV_READ");
readEnvironmentVariable("CDXGEN_TEST_ENV_READ");
const activities = getRecordedActivities().filter(
(activity) => activity.target === "process.env:CDXGEN_TEST_ENV_READ",
);
assert.strictEqual(activities.length, 1);
assert.strictEqual(activities[0].kind, "env");
assert.match(activities[0].reason, /2 times/);
} finally {
if (originalEnvValue === undefined) {
delete process.env.CDXGEN_TEST_ENV_READ;
} else {
process.env.CDXGEN_TEST_ENV_READ = originalEnvValue;
}
setDryRunMode(false);
resetRecordedActivities();
}
});
it("isAllowedHttpHost() honors exact and wildcard host allowlists", () => {
const originalAllowedHosts = process.env.CDXGEN_ALLOWED_HOSTS;
try {
process.env.CDXGEN_ALLOWED_HOSTS = "example.com,*.trusted.test";
assert.strictEqual(isAllowedHttpHost("example.com"), true);
assert.strictEqual(isAllowedHttpHost("api.trusted.test"), true);
assert.strictEqual(isAllowedHttpHost("trusted.test"), false);
assert.strictEqual(isAllowedHttpHost("evil.com"), false);
} finally {
if (originalAllowedHosts === undefined) {
delete process.env.CDXGEN_ALLOWED_HOSTS;
} else {
process.env.CDXGEN_ALLOWED_HOSTS = originalAllowedHosts;
}
}
});
it("deduplicates sensitive file read activity entries in dry-run mode", () => {
setDryRunMode(true);
resetRecordedActivities();
try {
recordSensitiveFileRead("/tmp/docker/config.json", {
label: "Docker credential file",
});
recordSensitiveFileRead("/tmp/docker/config.json", {
label: "Docker credential file",
});
const activities = getRecordedActivities().filter(
(activity) => activity.target === "/tmp/docker/config.json",
);
assert.strictEqual(activities.length, 1);
assert.strictEqual(activities[0].kind, "read");
assert.match(activities[0].reason, /2 times/);
} finally {
setDryRunMode(false);
resetRecordedActivities();
}
});
it("records classified manifest and config inspections in dry-run mode", () => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "cdxgen-dry-run-inspect-"));
const packageJsonFile = path.join(tmpRoot, "package.json");
const settingsXmlFile = path.join(tmpRoot, "settings.xml");
writeFileSync(packageJsonFile, "{}");
writeFileSync(settingsXmlFile, "<settings />");
setDryRunMode(true);
resetRecordedActivities();
try {
assert.ok(safeExistsSync(packageJsonFile));
assert.ok(safeExistsSync(settingsXmlFile));
const activities = getRecordedActivities().filter((activity) =>
[packageJsonFile, settingsXmlFile].includes(activity.target),
);
assert.strictEqual(activities.length, 2);
assert.deepStrictEqual(
activities.map((activity) => activity.kind),
["inspect", "inspect"],
);
assert.deepStrictEqual(
activities.map((activity) => activity.classification),
["manifest", "config"],
);
} finally {
setDryRunMode(false);
resetRecordedActivities();
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("records recursive file discovery activity in dry-run mode", () => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "cdxgen-dry-run-glob-"));
const packageJsonFile = path.join(tmpRoot, "package.json");
writeFileSync(packageJsonFile, "{}");
setDryRunMode(true);
resetRecordedActivities();
try {
const files = getAllFiles(tmpRoot, "**/package.json");
assert.deepStrictEqual(files, [packageJsonFile]);
const activities = getRecordedActivities().filter((activity) =>
activity.target.includes("**/package.json"),
);
assert.strictEqual(activities.length, 1);
assert.strictEqual(activities[0].kind, "discover");
assert.strictEqual(activities[0].discoveryType, "manifest-discovery");
} finally {
setDryRunMode(false);
resetRecordedActivities();
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("records suspicious discovered paths with shell metacharacters in dry-run mode", () => {
const tmpRoot = mkdtempSync(
path.join(tmpdir(), "cdxgen-dry-run-shell-path-"),
);
const shellIfs = "$" + "{IFS}";
const maliciousDirName =
platform() === "win32"
? "evil&echo%CDXGEN_GITURL_E2E_MARKER%&rem"
: `evil;cd${shellIfs}..;printf${shellIfs}marker>CDXGEN_GITURL_E2E_MARKER;#`;
const maliciousDir = path.join(tmpRoot, maliciousDirName);
const pomFile = path.join(maliciousDir, "pom.xml");
mkdirSync(maliciousDir, { recursive: true });
writeFileSync(pomFile, "<project />");
setDryRunMode(true);
resetRecordedActivities();
try {
assert.deepStrictEqual(getAllFiles(tmpRoot, "**/pom.xml"), [pomFile]);
const suspiciousActivity = getRecordedActivities().find(
(activity) => activity.classification === "suspicious-path",
);
assert.ok(suspiciousActivity);
assert.strictEqual(suspiciousActivity.risk, "shell-metacharacters");
assert.strictEqual(suspiciousActivity.target, pomFile);
assert.match(suspiciousActivity.reason, /shell metacharacters/);
} finally {
setDryRunMode(false);
resetRecordedActivities();
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("records updated discovery activity when a repeated glob match count changes", () => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "cdxgen-dry-run-glob-"));
const packageJsonFile = path.join(tmpRoot, "package.json");
const nestedDir = path.join(tmpRoot, "nested");
const nestedPackageJsonFile = path.join(nestedDir, "package.json");
writeFileSync(packageJsonFile, "{}");
setDryRunMode(true);
resetRecordedActivities();
try {
getAllFiles(tmpRoot, "**/package.json");
mkdirSync(nestedDir, { recursive: true });
writeFileSync(nestedPackageJsonFile, "{}");
getAllFiles(tmpRoot, "**/package.json");
const activities = getRecordedActivities().filter((activity) =>
activity.target.includes("**/package.json"),
);
assert.strictEqual(activities.length, 2);
assert.deepStrictEqual(
activities.map((activity) => activity.matchedCount),
[1, 2],
);
} finally {
setDryRunMode(false);
resetRecordedActivities();
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("dry-run filesystem wrappers do not mutate the filesystem", () => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "cdxgen-dry-run-"));
const fileToKeep = path.join(tmpRoot, "keep.txt");
const fileToSkip = path.join(tmpRoot, "skip.txt");
const dirToKeep = path.join(tmpRoot, "keep-dir");
writeFileSync(fileToKeep, "hello");
mkdirSync(dirToKeep, { recursive: true });
setDryRunMode(true);
resetRecordedActivities();
try {
const tempPath = safeMkdtempSync(path.join(tmpRoot, "temp-"));
safeWriteSync(fileToSkip, "world");
safeUnlinkSync(fileToKeep);
safeRmSync(dirToKeep, { recursive: true, force: true });
assert.ok(!existsSync(fileToSkip));
assert.ok(existsSync(fileToKeep));
assert.ok(existsSync(dirToKeep));
assert.ok(!existsSync(tempPath));
const activities = getRecordedActivities();
assert.deepStrictEqual(
activities.map((activity) => activity.kind),
["temp-dir", "write", "cleanup", "cleanup"],
);
assert.ok(activities.every((activity) => activity.status === "blocked"));
} finally {
setDryRunMode(false);
resetRecordedActivities();
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("safeWriteSync() honors explicit fs.write permission for output files", async () => {
const tmpRoot = mkdtempSync(path.join(tmpdir(), "cdxgen-secure-write-"));
const outputFile = path.join(tmpRoot, "bom.json");
const originalDebugMode = process.env.CDXGEN_DEBUG_MODE;
const originalSecureMode = process.env.CDXGEN_SECURE_MODE;
const originalPermissionDescriptor = Object.getOwnPropertyDescriptor(
process,
"permission",
);
const hasPermissionStub = sinon.stub().callsFake((scope, filePath) => {
return (
scope === "fs.read" || (scope === "fs.write" && filePath === outputFile)
);
});
process.env.CDXGEN_DEBUG_MODE = "debug";
process.env.CDXGEN_SECURE_MODE = "true";
Object.defineProperty(process, "permission", {
configurable: true,
value: {
has: hasPermissionStub,
},
});
try {
const {
getRecordedActivities: getRecordedActivitiesMocked,
resetRecordedActivities: resetRecordedActivitiesMocked,
safeWriteSync: safeWriteSyncMocked,
} = await import(
new URL(`./utils.js?secure-write-test=${Date.now()}`, import.meta.url)
);
resetRecordedActivitiesMocked();
safeWriteSyncMocked(outputFile, "{}");
assert.strictEqual(readFileSync(outputFile, "utf-8"), "{}");
assert.strictEqual(getRecordedActivitiesMocked()[0].status, "completed");
} finally {
if (originalDebugMode === undefined) {
delete process.env.CDXGEN_DEBUG_MODE;
} else {
process.env.CDXGEN_DEBUG_MODE = originalDebugMode;
}
if (originalSecureMode === undefined) {
delete process.env.CDXGEN_SECURE_MODE;
} else {
process.env.CDXGEN_SECURE_MODE = originalSecureMode;
}
if (originalPermissionDescriptor) {
Object.defineProperty(
process,
"permission",
originalPermissionDescriptor,
);
} else {
delete process.permission;
}
rmSync(tmpRoot, { force: true, recursive: true });
}
});
it("cdxgenAgent records completed and failed network activity outcomes", async () => {
let setDryRunModeMocked;
try {
const {
cdxgenAgent,
getRecordedActivities: getRecordedActivitiesMocked,
resetRecordedActivities: resetRecordedActivitiesMocked,
setDryRunMode: mockedSetDryRunMode,
} = await esmock("./utils.js", {
got: {
default: {
extend: sinon.stub().callsFake((options) => {
return {
hooks: options.hooks,
};
}),
},
},
});
setDryRunModeMocked = mockedSetDryRunMode;
const afterResponseHook = cdxgenAgent.hooks.afterResponse[0];
const beforeErrorHook = cdxgenAgent.hooks.beforeError[0];
setDryRunModeMocked(true);
resetRecordedActivitiesMocked();
const successUrl = "https://example.com/success";
const successOptions = {
context: {
activityTarget: successUrl,
},
url: new URL(successUrl),
};
afterResponseHook({
request: {
options: successOptions,
},
statusCode: 200,
url: successUrl,
});
assert.strictEqual(getRecordedActivitiesMocked()[0].status, "completed");
assert.strictEqual(getRecordedActivitiesMocked()[0].target, successUrl);
resetRecordedActivitiesMocked();
const failureUrl = "https://example.com/failure";
const failureOptions = {
context: {
activityTarget: failureUrl,
},
url: new URL(failureUrl),
};
const returnedError = beforeErrorHook({
message: "Request failed with status code 500",
options: failureOptions,
});
assert.match(returnedError.message, /status code 500/);
const failureActivities = getRecordedActivitiesMocked();
assert.strictEqual(failureActivities.length, 1);
assert.strictEqual(failureActivities[0].status, "failed");
assert.strictEqual(failureActivities[0].target, failureUrl);
} finally {
if (setDryRunModeMocked) {
setDryRunModeMocked(false);
}
}
});
it("cdxgenAgent reads CDXGEN_ALLOWED_HOSTS once per request", () => {
const originalAllowedHosts = process.env.CDXGEN_ALLOWED_HOSTS;
try {
process.env.CDXGEN_ALLOWED_HOSTS = "example.com";
const beforeRequestHook =
cdxgenAgent.defaults.options.hooks.beforeRequest[0];
setDryRunMode(true);
resetRecordedActivities();
assert.throws(() =>
beforeRequestHook({
context: {},
url: new URL("https://example.com/resource"),
}),
);
const envActivities = getRecordedActivities().filter(
(activity) => activity.target === "process.env:CDXGEN_ALLOWED_HOSTS",
);
assert.strictEqual(envActivities.length, 1);
assert.strictEqual(envActivities[0].count, 1);
} finally {
if (originalAllowedHosts === undefined) {
delete process.env.CDXGEN_ALLOWED_HOSTS;
} else {
process.env.CDXGEN_ALLOWED_HOSTS = originalAllowedHosts;
}
setDryRunMode(false);
resetRecordedActivities();
}
});
it("safeSpawnSync() logs container python notices to stdout", () => {
const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;
const originalContainer = process.env.CDXGEN_IN_CONTAINER;
const originalNoticeCache = globalThis.__cdxgenNoticeCache;
const logs = [];
const warnings = [];
process.env.CDXGEN_IN_CONTAINER = "true";
delete globalThis.__cdxgenNoticeCache;
console.log = (message) => {
logs.push(message);
};
console.warn = (message) => {
warnings.push(message);
};
try {
safeSpawnSync("python-cdxgen-test", ["-c", "pass"], {});
safeSpawnSync("python-cdxgen-test", ["-c", "pass"], {});
assert.strictEqual(logs.length + warnings.length, 1);
assert.ok(
[...logs, ...warnings].some((message) =>
message.includes("Running python command without '-S' argument."),
),
);
} finally {
console.log = originalConsoleLog;
console.warn = originalConsoleWarn;
if (originalContainer === undefined) {
delete process.env.CDXGEN_IN_CONTAINER;
} else {
process.env.CDXGEN_IN_CONTAINER = originalContainer;
}
if (originalNoticeCache === undefined) {
delete globalThis.__cdxgenNoticeCache;
} else {
globalThis.__cdxgenNoticeCache = originalNoticeCache;
}
}
});
it("splits parallel gradle properties output correctly", () => {
const parallelGradlePropertiesOutput = readFileSync(
"./test/gradle-prop-parallel.out",
{ encoding: "utf-8" },
);
const relevantTasks = ["properties"];
const propOutputSplitBySubProject = splitOutputByGradleProjects(
parallelGradlePropertiesOutput,
relevantTasks,
);
assert.deepStrictEqual(propOutputSplitBySubProject.size, 4);
assert.deepStrictEqual(
propOutputSplitBySubProject.has("dependency-diff-check"),
true,
);
assert.deepStrictEqual(
propOutputSplitBySubProject.has(":dependency-diff-check-service"),
true,
);
assert.deepStrictEqual(
propOutputSplitBySubProject.has(":dependency-diff-check-common-core"),
true,
);
assert.deepStrictEqual(
propOutputSplitBySubProject.has(":dependency-diff-check-client-starter"),
true,
);
const retMap = parseGradleProperties(
propOutputSplitBySubProject.get("dependency-diff-check"),
);
assert.deepStrictEqual(retMap.rootProject, "dependency-diff-check");
assert.deepStrictEqual(retMap.projects.length, 3);
assert.deepStrictEqual(retMap.metadata.group, "com.ajmalab");
assert.deepStrictEqual(retMap.metadata.version, "0.0.1-SNAPSHOT");
});
it("splits parallel gradle dependencies output correctly", async () => {
const parallelGradleDepOutput = readFileSync(
"./test/gradle-dep-parallel.out",
{ encoding: "utf-8" },
);
const relevantTasks = ["dependencies"];
const depOutputSplitBySubProject = splitOutputByGradleProjects(
parallelGradleDepOutput,
relevantTasks,
);
assert.deepStrictEqual(depOutputSplitBySubProject.size, 4);
assert.deepStrictEqual(
depOutputSplitBySubProject.has("dependency-diff-check"),
true,
);
assert.deepStrictEqual(
depOutputSplitBySubProject.has(":dependency-diff-check-service"),
true,
);
assert.deepStrictEqual(
depOutputSplitBySubProject.has(":dependency-diff-check-common-core"),
true,
);
assert.deepStrictEqual(
depOutputSplitBySubProject.has(":dependency-diff-check-client-starter"),
true,
);
const retMap = await parseGradleDep(
depOutputSplitBySubProject.get("dependency-diff-check"),
"dependency-diff-check",
new Map().set(
"dependency-diff-check",
await buildObjectForGradleModule("dependency-diff-check", {
version: "latest",
}),
),
);
assert.deepStrictEqual(retMap.pkgList.length, 12);
assert.deepStrictEqual(retMap.dependenciesList.length, 13);
});
it("splits parallel custom gradle task outputs correctly", async () => {
const parallelGradleOutputWithOverridenTask = readFileSync(
"./test/gradle-build-env-dep.out",
{ encoding: "utf-8" },
);
const overridenTasks = ["buildEnvironment"];
const customDepTaskOuputSplitByProject = splitOutputByGradleProjects(
parallelGradleOutputWithOverridenTask,
overridenTasks,
);
assert.deepStrictEqual(customDepTaskOuputSplitByProject.size, 4);
assert.deepStrictEqual(
customDepTaskOuputSplitByProject.has("dependency-diff-check"),
true,
);
assert.deepStrictEqual(
customDepTaskOuputSplitByProject.has(":dependency-diff-check-service"),
true,
);
assert.deepStrictEqual(
customDepTaskOuputSplitByProject.has(":dependency-diff-check-common-core"),
true,
);
assert.deepStrictEqual(
customDepTaskOuputSplitByProject.has(
":dependency-diff-check-client-starter",
),
true,
);
const retMap = await parseGradleDep(
customDepTaskOuputSplitByProject.get(
":dependency-diff-check-client-starter",
),
"dependency-diff-check",
new Map().set(
"dependency-diff-check",
await buildObjectForGradleModule("dependency-diff-check", {
version: "latest",
}),
),
);
assert.deepStrictEqual(retMap.pkgList.length, 22);
assert.deepStrictEqual(retMap.dependenciesList.length, 23);
});
it("parse gradle dependencies", async () => {
const modulesMap = new Map();
modulesMap.set(
"test-project",
await buildObjectForGradleModule("test-project", {
version: "latest",
}),
);
modulesMap.set(
"dependency-diff-check-common-core",
await buildObjectForGradleModule("dependency-diff-check-common-core", {
version: "latest",
}),
);
modulesMap.set(
"app",
await buildObjectForGradleModule("app", {
version: "latest",
}),
);
modulesMap.set(
"failing-project",
await buildObjectForGradleModule("failing-project", {
version: "latest",
}),
);
assert.deepStrictEqual(await parseGradleDep(null), {});
let parsedList = await parseGradleDep(
readFileSync("./test/gradle-dep.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 33);
assert.deepStrictEqual(parsedList.dependenciesList.length, 34);
assert.deepStrictEqual(parsedList.pkgList[0], {
group: "org.ethereum",
name: "solcJ-all",
qualifiers: {
type: "jar",
},
version: "0.4.25",
"bom-ref": "pkg:maven/org.ethereum/solcJ-all@0.4.25?type=jar",
purl: "pkg:maven/org.ethereum/solcJ-all@0.4.25?type=jar",
});
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-android-dep.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 104);
assert.deepStrictEqual(parsedList.dependenciesList.length, 105);
assert.deepStrictEqual(parsedList.pkgList[0], {
group: "com.android.support.test",
name: "runner",
qualifiers: {
type: "jar",
},
scope: "optional",
version: "1.0.2",
properties: [
{
name: "GradleProfileName",
value: "androidTestImplementation",
},
],
"bom-ref": "pkg:maven/com.android.support.test/runner@1.0.2?type=jar",
purl: "pkg:maven/com.android.support.test/runner@1.0.2?type=jar",
});
assert.deepStrictEqual(parsedList.pkgList[103], {
group: "androidx.core",
name: "core",
qualifiers: {
type: "jar",
},
version: "1.7.0",
scope: "optional",
properties: [
{
name: "GradleProfileName",
value: "releaseUnitTestRuntimeClasspath",
},
],
"bom-ref": "pkg:maven/androidx.core/core@1.7.0?type=jar",
purl: "pkg:maven/androidx.core/core@1.7.0?type=jar",
});
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-out1.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 89);
assert.deepStrictEqual(parsedList.dependenciesList.length, 90);
assert.deepStrictEqual(parsedList.pkgList[0], {
group: "org.springframework.boot",
name: "spring-boot-starter-web",
version: "2.2.0.RELEASE",
qualifiers: { type: "jar" },
properties: [
{
name: "GradleProfileName",
value: "compileClasspath",
},
],
"bom-ref":
"pkg:maven/org.springframework.boot/spring-boot-starter-web@2.2.0.RELEASE?type=jar",
purl: "pkg:maven/org.springframework.boot/spring-boot-starter-web@2.2.0.RELEASE?type=jar",
});
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-rich1.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 4);
assert.deepStrictEqual(parsedList.pkgList[parsedList.pkgList.length - 1], {
group: "ch.qos.logback",
name: "logback-core",
qualifiers: { type: "jar" },
version: "1.4.5",
"bom-ref": "pkg:maven/ch.qos.logback/logback-core@1.4.5?type=jar",
purl: "pkg:maven/ch.qos.logback/logback-core@1.4.5?type=jar",
});
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-rich2.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 2);
assert.deepStrictEqual(parsedList.pkgList, [
{
group: "io.appium",
name: "java-client",
qualifiers: { type: "jar" },
version: "8.1.1",
"bom-ref": "pkg:maven/io.appium/java-client@8.1.1?type=jar",
purl: "pkg:maven/io.appium/java-client@8.1.1?type=jar",
},
{
group: "org.seleniumhq.selenium",
name: "selenium-support",
qualifiers: { type: "jar" },
version: "4.5.0",
"bom-ref":
"pkg:maven/org.seleniumhq.selenium/selenium-support@4.5.0?type=jar",
purl: "pkg:maven/org.seleniumhq.selenium/selenium-support@4.5.0?type=jar",
},
]);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-rich3.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 1);
assert.deepStrictEqual(parsedList.pkgList, [
{
group: "org.seleniumhq.selenium",
name: "selenium-remote-driver",
version: "4.5.0",
qualifiers: { type: "jar" },
"bom-ref":
"pkg:maven/org.seleniumhq.selenium/selenium-remote-driver@4.5.0?type=jar",
purl: "pkg:maven/org.seleniumhq.selenium/selenium-remote-driver@4.5.0?type=jar",
},
]);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-rich4.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 1);
assert.deepStrictEqual(parsedList.pkgList, [
{
group: "org.seleniumhq.selenium",
name: "selenium-api",
version: "4.5.0",
qualifiers: { type: "jar" },
"bom-ref":
"pkg:maven/org.seleniumhq.selenium/selenium-api@4.5.0?type=jar",
purl: "pkg:maven/org.seleniumhq.selenium/selenium-api@4.5.0?type=jar",
},
]);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-rich5.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 67);
assert.deepStrictEqual(parsedList.dependenciesList.length, 68);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-out-249.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 21);
assert.deepStrictEqual(parsedList.dependenciesList.length, 22);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-service.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 35);
assert.deepStrictEqual(parsedList.dependenciesList.length, 36);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-s.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 28);
assert.deepStrictEqual(parsedList.dependenciesList.length, 29);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-core.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 18);
assert.deepStrictEqual(parsedList.dependenciesList.length, 19);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-single.out", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 152);
assert.deepStrictEqual(parsedList.dependenciesList.length, 153);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-android-app.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 102);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-android-jetify.dep", {
encoding: "utf-8",
}),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 1);
assert.deepStrictEqual(parsedList.pkgList, [
{
group: "androidx.appcompat",
name: "appcompat",
version: "1.2.0",
qualifiers: { type: "jar" },
"bom-ref": "pkg:maven/androidx.appcompat/appcompat@1.2.0?type=jar",
purl: "pkg:maven/androidx.appcompat/appcompat@1.2.0?type=jar",
},
]);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-sm.dep", { encoding: "utf-8" }),
"test-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 6);
assert.deepStrictEqual(parsedList.dependenciesList.length, 7);
parsedList = await parseGradleDep(
readFileSync("./test/data/gradle-dependencies-559.txt", {
encoding: "utf-8",
}),
"failing-project",
modulesMap,
);
assert.deepStrictEqual(parsedList.pkgList.length, 372);
});
it("parse gradle projects", () => {
assert.deepStrictEqual(parseGradleProjects(null), {
projects: [],
rootProject: "root",
});
let retMap = parseGradleProjects(
readFileSync("./test/data/gradle-projects.out", { encoding: "utf-8" }),
);
assert.deepStrictEqual(retMap.rootProject, "elasticsearch");
assert.deepStrictEqual(retMap.projects.length, 368);
retMap = parseGradleProjects(
readFileSync("./test/data/gradle-projects1.out", { encoding: "utf-8" }),
);
assert.deepStrictEqual(retMap.rootProject, "elasticsearch");
assert.deepStrictEqual(retMap.projects.length, 409);
retMap = parseGradleProjects(
readFileSync("./test/data/gradle-projects2.out", { encoding: "utf-8" }),
);
assert.deepStrictEqual(retMap.rootProject, "fineract");
assert.deepStrictEqual(retMap.projects.length, 22);
retMap = parseGradleProjects(
readFileSync("./test/data/gradle-android-app.dep", { encoding: "utf-8" }),
);
assert.deepStrictEqual(retMap.rootProject, "root");
assert.deepStrictEqual(retMap.projects, [":app"]);
retMap = parseGradleProjects(
readFileSync("./test/data/gradle-properties-sm.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap.rootProject, "root");
assert.deepStrictEqual(retMap.projects, [
":module:dummy:core",
":module:dummy:service",
":module:dummy:starter",
":custom:foo:service",
]);
});
it("parse gradle properties", () => {
assert.deepStrictEqual(parseGradleProperties(null), {
projects: [],
rootProject: "root",
metadata: {
group: "",
version: "latest",
properties: [],
},
});
let retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties.txt", { encoding: "utf-8" }),
);
assert.deepStrictEqual(retMap, {
rootProject: "dependency-diff-check",
projects: [
":dependency-diff-check-client-starter",
":dependency-diff-check-common-core",
":dependency-diff-check-service",
],
metadata: {
group: "com.ajmalab",
version: "0.0.1-SNAPSHOT",
properties: [
{
name: "GradleModule",
value: "dependency-diff-check",
},
{
name: "buildFile",
value:
"/home/almalinux/work/sandbox/dependency-diff-check/build.gradle",
},
{
name: "projectDir",
value: "/home/almalinux/work/sandbox/dependency-diff-check",
},
{
name: "rootDir",
value: "/home/almalinux/work/sandbox/dependency-diff-check",
},
],
},
});
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-single.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap, {
rootProject: "java-test",
projects: [":app"],
metadata: {
group: "com.ajmalab.demo",
version: "latest",
properties: [
{
name: "GradleModule",
value: "java-test",
},
{
name: "buildFile",
value: "/home/almalinux/work/sandbox/java-test/build.gradle",
},
{
name: "projectDir",
value: "/home/almalinux/work/sandbox/java-test",
},
{ name: "rootDir", value: "/home/almalinux/work/sandbox/java-test" },
],
},
});
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-single2.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap, {
rootProject: "java-test",
projects: [],
metadata: {
group: "com.ajmalab.demo",
version: "latest",
properties: [
{
name: "GradleModule",
value: "java-test",
},
{
name: "buildFile",
value: "/home/almalinux/work/sandbox/java-test/build.gradle",
},
{ name: "projectDir", value: "/home/almalinux/work/sandbox/java-test" },
{ name: "rootDir", value: "/home/almalinux/work/sandbox/java-test" },
],
},
});
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-elastic.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap.rootProject, "elasticsearch");
assert.deepStrictEqual(retMap.projects.length, 409);
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-android.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap.rootProject, "CdxgenAndroidTest");
assert.deepStrictEqual(retMap.projects.length, 2);
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-sm.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap.rootProject, "root");
assert.deepStrictEqual(retMap.projects, []);
retMap = parseGradleProperties(
readFileSync("./test/data/gradle-properties-559.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(retMap.rootProject, "failing-project");
assert.deepStrictEqual(retMap.projects, []);
});
it("parse maven tree", () => {
assert.deepStrictEqual(parseMavenTree(null), {});
let parsedList = parseMavenTree(
readFileSync("./test/data/sample-mvn-tree.txt", { encoding: "utf-8" }),
);
assert.deepStrictEqual(parsedList.pkgList.length, 61);
assert.deepStrictEqual(parsedList.dependenciesList.length, 61);
assert.deepStrictEqual(parsedList.pkgList[0], {
"bom-ref": "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
group: "com.pogeyan.cmis",
name: "copper-server",
version: "1.15.2",
qualifiers: { type: "war" },
properties: [],
purl: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
scope: undefined,
});
assert.deepStrictEqual(parsedList.dependenciesList[0], {
ref: "pkg:maven/com.pogeyan.cmis/copper-server@1.15.2?type=war",
dependsOn: [
"pkg:maven/com.fasterxml.jackson.core/jackson-core@2.12.0?type=jar",
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.12.0?type=jar",
"pkg:maven/com.github.davidb/metrics-influxdb@0.9.3?type=jar",
"pkg:maven/com.pogeyan.cmis/copper-server-api@1.15.2?type=jar",
"pkg:maven/com.pogeyan.cmis/copper-server-impl@1.15.2?type=jar",
"pkg:maven/com.pogeyan.cmis/copper-server-ldap@1.15.2?type=jar",
"pkg:maven/com.pogeyan.cmis/copper-server-mongo@1.15.2?type=jar",
"pkg:maven/com.pogeyan.cmis/copper-server-repo@1.15.2?type=jar",
"pkg:maven/com.typesafe.akka/akka-actor_2.11@2.4.14?type=jar",
"pkg:maven/com.typesafe.akka/akka-cluster_2.11@2.4.14?type=jar",
"pkg:maven/commons-fileupload/commons-fileupload@1.4?type=jar",
"pkg:maven/commons-io/commons-io@2.6?type=jar",
"pkg:maven/io.dropwizard.metrics/metrics-core@3.1.2?type=jar",
"pkg:maven/javax/javaee-web-api@7.0?type=jar",
"pkg:maven/junit/junit@4.12?type=jar",
"pkg:maven/org.apache.chemistry.opencmis/chemistry-opencmis-server-support@1.0.0?type=jar",
"pkg:maven/org.apache.commons/commons-lang3@3.4?type=jar",
"pkg:maven/org.codehaus.jackson/jackson-mapper-asl@1.9.13?type=jar",
"pkg:maven/org.slf4j/slf4j-log4j12@1.7.21?type=jar",
],
});
parsedList = parseMavenTree(
readFileSync("./test/data/mvn-dep-tree-simple.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(parsedList.pkgList.length, 39);
assert.deepStrictEqual(parsedList.dependenciesList.length, 39);
assert.deepStrictEqual(parsedList.pkgList[0], {
"bom-ref":
"pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
purl: "pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
group: "com.gitlab.security_products.tests",
name: "java-maven",
version: "1.0-SNAPSHOT",
qualifiers: { type: "jar" },
properties: [],
scope: undefined,
});
assert.deepStrictEqual(parsedList.dependenciesList[0], {
ref: "pkg:maven/com.gitlab.security_products.tests/java-maven@1.0-SNAPSHOT?type=jar",
dependsOn: [
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.2?type=jar",
"pkg:maven/com.github.jnr/jffi@1.3.11?classifier=native&type=jar",
"pkg:maven/com.github.jnr/jffi@1.3.11?type=jar",
"pkg:maven/io.netty/netty@3.9.1.Final?type=jar",
"pkg:maven/junit/junit@3.8.1?type=jar",
"pkg:maven/org.apache.geode/geode-core@1.1.1?type=jar",
"pkg:maven/org.apache.maven/maven-artifact@3.3.9?type=jar",
"pkg:maven/org.mozilla/rhino@1.7.10?type=jar",
"pkg:maven/org.powermock/powermock-api-mockito@1.7.3?type=jar",
],
});
parsedList = parseMavenTree(
readFileSync("./test/data/mvn-p2-plugin.txt", {
encoding: "utf-8",
}),
);
assert.deepStrictEqual(parsedList.pkgList.length, 79);
assert.deepStrictEqual(parsedList.pkgList[0], {
"bom-ref":
"pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repository",
purl: "pkg:maven/example.group/eclipse-repository@1.0.0-SNAPSHOT?type=eclipse-repo