@cyclonedx/cdxgen 11.4.3 → 11.5.0
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 +63 -63
- package/bin/cdxgen.js +32 -9
- package/lib/cli/index.js +341 -98
- package/lib/helpers/envcontext.js +20 -18
- package/lib/helpers/logger.js +0 -2
- package/lib/helpers/utils.js +763 -63
- package/lib/helpers/utils.test.js +448 -27
- package/lib/helpers/validator.js +10 -0
- package/lib/managers/binary.js +89 -11
- package/lib/managers/docker.js +47 -37
- package/lib/server/server.js +120 -6
- package/lib/server/server.test.js +235 -0
- package/package.json +33 -14
- package/types/lib/cli/index.d.ts +8 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +3 -8
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/logger.d.ts +0 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +67 -66
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/validator.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +22 -2
- package/types/lib/server/server.d.ts.map +1 -1
package/lib/helpers/utils.js
CHANGED
|
@@ -507,6 +507,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
507
507
|
oci: ["docker", "oci", "container", "podman"],
|
|
508
508
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
509
509
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
510
|
+
nix: ["nix", "nixos", "flake"],
|
|
510
511
|
};
|
|
511
512
|
|
|
512
513
|
// Package manager aliases
|
|
@@ -580,8 +581,11 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
|
|
|
580
581
|
const allProjectTypes = [...projectTypes];
|
|
581
582
|
// Convert the project types into base types
|
|
582
583
|
const baseProjectTypes = [];
|
|
583
|
-
// Support for
|
|
584
|
-
if (
|
|
584
|
+
// Support for arbitrary versioned ruby type
|
|
585
|
+
if (
|
|
586
|
+
options.projectType?.length &&
|
|
587
|
+
projectTypes.filter((p) => p.startsWith("ruby")).length
|
|
588
|
+
) {
|
|
585
589
|
baseProjectTypes.push("ruby");
|
|
586
590
|
}
|
|
587
591
|
const baseExcludeTypes = [];
|
|
@@ -692,6 +696,7 @@ export const cdxgenAgent = got.extend({
|
|
|
692
696
|
retry: {
|
|
693
697
|
limit: 0,
|
|
694
698
|
},
|
|
699
|
+
followRedirect: !isSecureMode,
|
|
695
700
|
hooks: {
|
|
696
701
|
beforeRequest: [
|
|
697
702
|
(options) => {
|
|
@@ -725,6 +730,8 @@ export const cdxgenAgent = got.extend({
|
|
|
725
730
|
* @param {string} dirPath Root directory for search
|
|
726
731
|
* @param {string} pattern Glob pattern (eg: *.gradle)
|
|
727
732
|
* @param {Object} options CLI options
|
|
733
|
+
*
|
|
734
|
+
* @returns {Array[string]} List of matched files
|
|
728
735
|
*/
|
|
729
736
|
export function getAllFiles(dirPath, pattern, options = {}) {
|
|
730
737
|
let ignoreList = [
|
|
@@ -737,7 +744,7 @@ export function getAllFiles(dirPath, pattern, options = {}) {
|
|
|
737
744
|
"**/coverage/**",
|
|
738
745
|
];
|
|
739
746
|
// Only ignore node_modules if the caller is not looking for package.json
|
|
740
|
-
if (!pattern.includes("package.json")) {
|
|
747
|
+
if (!pattern.includes("package.json") && !options.includeNodeModulesDir) {
|
|
741
748
|
ignoreList.push("**/node_modules/**");
|
|
742
749
|
}
|
|
743
750
|
// ignore docs only for non-lock file lookups
|
|
@@ -752,7 +759,27 @@ export function getAllFiles(dirPath, pattern, options = {}) {
|
|
|
752
759
|
if (options?.exclude && Array.isArray(options.exclude)) {
|
|
753
760
|
ignoreList = ignoreList.concat(options.exclude);
|
|
754
761
|
}
|
|
755
|
-
|
|
762
|
+
const includeDot = pattern.startsWith(".") || options.includeNodeModulesDir;
|
|
763
|
+
const defaultHits = getAllFilesWithIgnore(
|
|
764
|
+
dirPath,
|
|
765
|
+
pattern,
|
|
766
|
+
includeDot,
|
|
767
|
+
ignoreList,
|
|
768
|
+
);
|
|
769
|
+
// Support for specifying the pattern via options
|
|
770
|
+
if (options?.include?.length) {
|
|
771
|
+
const includeOnlyHits = getAllFilesWithIgnore(
|
|
772
|
+
dirPath,
|
|
773
|
+
options.include,
|
|
774
|
+
includeDot,
|
|
775
|
+
ignoreList,
|
|
776
|
+
);
|
|
777
|
+
if (!includeOnlyHits.length) {
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
return defaultHits.filter((f) => includeOnlyHits.includes(f));
|
|
781
|
+
}
|
|
782
|
+
return defaultHits;
|
|
756
783
|
}
|
|
757
784
|
|
|
758
785
|
/**
|
|
@@ -760,16 +787,24 @@ export function getAllFiles(dirPath, pattern, options = {}) {
|
|
|
760
787
|
*
|
|
761
788
|
* @param {string} dirPath Root directory for search
|
|
762
789
|
* @param {string} pattern Glob pattern (eg: *.gradle)
|
|
790
|
+
* @param {Boolean} includeDot whether hidden files can be included.
|
|
763
791
|
* @param {Array} ignoreList Directory patterns to ignore
|
|
792
|
+
*
|
|
793
|
+
* @returns {Array[string]} List of matched files
|
|
764
794
|
*/
|
|
765
|
-
export function getAllFilesWithIgnore(
|
|
795
|
+
export function getAllFilesWithIgnore(
|
|
796
|
+
dirPath,
|
|
797
|
+
pattern,
|
|
798
|
+
includeDot,
|
|
799
|
+
ignoreList,
|
|
800
|
+
) {
|
|
766
801
|
try {
|
|
767
802
|
const files = globSync(pattern, {
|
|
768
803
|
cwd: dirPath,
|
|
769
804
|
absolute: true,
|
|
770
805
|
nocase: true,
|
|
771
806
|
nodir: true,
|
|
772
|
-
dot:
|
|
807
|
+
dot: includeDot,
|
|
773
808
|
follow: false,
|
|
774
809
|
ignore: ignoreList,
|
|
775
810
|
});
|
|
@@ -2285,6 +2320,164 @@ export function parsePnpmWorkspace(workspaceFile) {
|
|
|
2285
2320
|
};
|
|
2286
2321
|
}
|
|
2287
2322
|
|
|
2323
|
+
/**
|
|
2324
|
+
* Helper function to find a package path in pnpm node_modules structure
|
|
2325
|
+
*
|
|
2326
|
+
* @param {string} baseDir Base directory containing node_modules
|
|
2327
|
+
* @param {string} packageName Package name (with or without scope)
|
|
2328
|
+
* @param {string} version Package version
|
|
2329
|
+
* @returns {string|null} Path to the package directory or null if not found
|
|
2330
|
+
*/
|
|
2331
|
+
export function findPnpmPackagePath(baseDir, packageName, version) {
|
|
2332
|
+
if (!baseDir || !packageName) {
|
|
2333
|
+
return null;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2337
|
+
if (!safeExistsSync(nodeModulesDir)) {
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Try direct node_modules lookup first (for symlinked packages)
|
|
2342
|
+
const directPath = join(nodeModulesDir, packageName);
|
|
2343
|
+
if (
|
|
2344
|
+
safeExistsSync(directPath) &&
|
|
2345
|
+
safeExistsSync(join(directPath, "package.json"))
|
|
2346
|
+
) {
|
|
2347
|
+
return directPath;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// Try pnpm's .pnpm directory structure
|
|
2351
|
+
const pnpmDir = join(nodeModulesDir, ".pnpm");
|
|
2352
|
+
if (safeExistsSync(pnpmDir)) {
|
|
2353
|
+
// pnpm stores packages as {name}@{version} in .pnpm directory
|
|
2354
|
+
const encodedName = packageName.replace("/", "%2f");
|
|
2355
|
+
let pnpmPackagePath;
|
|
2356
|
+
|
|
2357
|
+
// Try different formats that pnpm might use
|
|
2358
|
+
const possiblePaths = [
|
|
2359
|
+
join(pnpmDir, `${encodedName}@${version}`, "node_modules", packageName),
|
|
2360
|
+
join(pnpmDir, `${packageName}@${version}`, "node_modules", packageName),
|
|
2361
|
+
join(pnpmDir, `${encodedName}@${version}`),
|
|
2362
|
+
join(pnpmDir, `${packageName}@${version}`),
|
|
2363
|
+
];
|
|
2364
|
+
|
|
2365
|
+
for (const possiblePath of possiblePaths) {
|
|
2366
|
+
if (
|
|
2367
|
+
safeExistsSync(possiblePath) &&
|
|
2368
|
+
safeExistsSync(join(possiblePath, "package.json"))
|
|
2369
|
+
) {
|
|
2370
|
+
pnpmPackagePath = possiblePath;
|
|
2371
|
+
break;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
if (pnpmPackagePath) {
|
|
2376
|
+
return pnpmPackagePath;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/**
|
|
2384
|
+
* pnpm packages with metadata from local node_modules
|
|
2385
|
+
*
|
|
2386
|
+
* @param {Array} pkgList Package list to enhance
|
|
2387
|
+
* @param {string} lockFilePath Path to the pnpm-lock.yaml file
|
|
2388
|
+
* @returns {Array} Enhanced package list
|
|
2389
|
+
*/
|
|
2390
|
+
export async function pnpmMetadata(pkgList, lockFilePath) {
|
|
2391
|
+
if (!pkgList || !pkgList.length || !lockFilePath) {
|
|
2392
|
+
return pkgList;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
const baseDir = dirname(lockFilePath);
|
|
2396
|
+
const nodeModulesDir = join(baseDir, "node_modules");
|
|
2397
|
+
|
|
2398
|
+
// Only proceed if node_modules exists
|
|
2399
|
+
if (!safeExistsSync(nodeModulesDir)) {
|
|
2400
|
+
return pkgList;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
if (DEBUG_MODE) {
|
|
2404
|
+
console.log(
|
|
2405
|
+
`Metadata for ${pkgList.length} pnpm packages using local node_modules at ${nodeModulesDir}`,
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
let enhancedCount = 0;
|
|
2410
|
+
for (const pkg of pkgList) {
|
|
2411
|
+
// Skip if package already has complete metadata
|
|
2412
|
+
if (pkg.description && pkg.author && pkg.license) {
|
|
2413
|
+
continue;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// Find the package path in node_modules
|
|
2417
|
+
const packagePath = findPnpmPackagePath(baseDir, pkg.name, pkg.version);
|
|
2418
|
+
if (!packagePath) {
|
|
2419
|
+
continue;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
const packageJsonPath = join(packagePath, "package.json");
|
|
2423
|
+
if (!safeExistsSync(packageJsonPath)) {
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
try {
|
|
2428
|
+
// Parse the local package.json to get metadata
|
|
2429
|
+
const localPkgList = await parsePkgJson(packageJsonPath, true);
|
|
2430
|
+
if (localPkgList && localPkgList.length === 1) {
|
|
2431
|
+
const localMetadata = localPkgList[0];
|
|
2432
|
+
if (localMetadata && Object.keys(localMetadata).length) {
|
|
2433
|
+
if (!pkg.description && localMetadata.description) {
|
|
2434
|
+
pkg.description = localMetadata.description;
|
|
2435
|
+
}
|
|
2436
|
+
if (!pkg.author && localMetadata.author) {
|
|
2437
|
+
pkg.author = localMetadata.author;
|
|
2438
|
+
}
|
|
2439
|
+
if (!pkg.license && localMetadata.license) {
|
|
2440
|
+
pkg.license = localMetadata.license;
|
|
2441
|
+
}
|
|
2442
|
+
if (!pkg.homepage && localMetadata.homepage) {
|
|
2443
|
+
pkg.homepage = localMetadata.homepage;
|
|
2444
|
+
}
|
|
2445
|
+
if (!pkg.repository && localMetadata.repository) {
|
|
2446
|
+
pkg.repository = localMetadata.repository;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// Add a property to track that we enhanced from local node_modules
|
|
2450
|
+
if (!pkg.properties) {
|
|
2451
|
+
pkg.properties = [];
|
|
2452
|
+
}
|
|
2453
|
+
pkg.properties.push({
|
|
2454
|
+
name: "LocalNodeModulesPath",
|
|
2455
|
+
value: packagePath,
|
|
2456
|
+
});
|
|
2457
|
+
|
|
2458
|
+
enhancedCount++;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
// Silently ignore parsing errors for individual packages
|
|
2463
|
+
if (DEBUG_MODE) {
|
|
2464
|
+
console.log(
|
|
2465
|
+
`Failed to parse package.json at ${packageJsonPath}:`,
|
|
2466
|
+
error.message,
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
if (DEBUG_MODE && enhancedCount > 0) {
|
|
2473
|
+
console.log(
|
|
2474
|
+
`Enhanced metadata for ${enhancedCount} packages from local node_modules`,
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
return pkgList;
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2288
2481
|
/**
|
|
2289
2482
|
* Parse nodejs pnpm lock file
|
|
2290
2483
|
*
|
|
@@ -2646,6 +2839,21 @@ export async function parsePnpmLock(
|
|
|
2646
2839
|
packages[fullName]?.resolution ||
|
|
2647
2840
|
snapshots[fullName]?.resolution;
|
|
2648
2841
|
const integrity = resolution?.integrity;
|
|
2842
|
+
const cpu =
|
|
2843
|
+
packages[pkgKeys[k]]?.cpu ||
|
|
2844
|
+
snapshots[pkgKeys[k]]?.cpu ||
|
|
2845
|
+
packages[fullName]?.cpu ||
|
|
2846
|
+
snapshots[fullName]?.cpu;
|
|
2847
|
+
const os =
|
|
2848
|
+
packages[pkgKeys[k]]?.os ||
|
|
2849
|
+
snapshots[pkgKeys[k]]?.os ||
|
|
2850
|
+
packages[fullName]?.os ||
|
|
2851
|
+
snapshots[fullName]?.os;
|
|
2852
|
+
const libc =
|
|
2853
|
+
packages[pkgKeys[k]]?.libc ||
|
|
2854
|
+
snapshots[pkgKeys[k]]?.libc ||
|
|
2855
|
+
packages[fullName]?.libc ||
|
|
2856
|
+
snapshots[fullName]?.libc;
|
|
2649
2857
|
// In lock file version 9, dependencies is under snapshots
|
|
2650
2858
|
const deps =
|
|
2651
2859
|
packages[pkgKeys[k]]?.dependencies ||
|
|
@@ -2849,7 +3057,7 @@ export async function parsePnpmLock(
|
|
|
2849
3057
|
value: pnpmLock,
|
|
2850
3058
|
},
|
|
2851
3059
|
];
|
|
2852
|
-
if (hasBin) {
|
|
3060
|
+
if (hasBin || os || cpu || libc) {
|
|
2853
3061
|
properties.push({
|
|
2854
3062
|
name: "cdx:npm:has_binary",
|
|
2855
3063
|
value: `${hasBin}`,
|
|
@@ -2861,6 +3069,14 @@ export async function parsePnpmLock(
|
|
|
2861
3069
|
value: deprecatedMessage,
|
|
2862
3070
|
});
|
|
2863
3071
|
}
|
|
3072
|
+
const binary_metadata = { os, cpu, libc };
|
|
3073
|
+
Object.entries(binary_metadata).forEach(([key, value]) => {
|
|
3074
|
+
if (!value) return;
|
|
3075
|
+
properties.push({
|
|
3076
|
+
name: `cdx:pnpm:${key}`,
|
|
3077
|
+
value: Array.isArray(value) ? value.join(", ") : value,
|
|
3078
|
+
});
|
|
3079
|
+
});
|
|
2864
3080
|
if (srcFilesMap[decodeURIComponent(purlString)]) {
|
|
2865
3081
|
for (const sf of srcFilesMap[decodeURIComponent(purlString)]) {
|
|
2866
3082
|
properties.push({
|
|
@@ -3075,6 +3291,12 @@ export async function parsePnpmLock(
|
|
|
3075
3291
|
});
|
|
3076
3292
|
}
|
|
3077
3293
|
}
|
|
3294
|
+
|
|
3295
|
+
// Enhance metadata from local node_modules if available
|
|
3296
|
+
if (pkgList?.length) {
|
|
3297
|
+
pkgList = await pnpmMetadata(pkgList, pnpmLock);
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3078
3300
|
if (shouldFetchLicense() && pkgList && pkgList.length) {
|
|
3079
3301
|
if (DEBUG_MODE) {
|
|
3080
3302
|
console.log(
|
|
@@ -4725,13 +4947,19 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
4725
4947
|
p.name = p.name.split("[")[0];
|
|
4726
4948
|
}
|
|
4727
4949
|
let res;
|
|
4950
|
+
let url_addition;
|
|
4951
|
+
if (p.version?.trim().length) {
|
|
4952
|
+
url_addition = `${p.name}/${p.version.trim()}/json`;
|
|
4953
|
+
} else {
|
|
4954
|
+
url_addition = `${p.name}/json`;
|
|
4955
|
+
}
|
|
4728
4956
|
try {
|
|
4729
|
-
res = await cdxgenAgent.get(`${PYPI_URL +
|
|
4957
|
+
res = await cdxgenAgent.get(`${PYPI_URL + url_addition}`, {
|
|
4730
4958
|
responseType: "json",
|
|
4731
4959
|
});
|
|
4732
4960
|
} catch (_err) {
|
|
4733
4961
|
// retry by prefixing django- to the package name
|
|
4734
|
-
res = await cdxgenAgent.get(`${PYPI_URL}django-${
|
|
4962
|
+
res = await cdxgenAgent.get(`${PYPI_URL}django-${url_addition}`, {
|
|
4735
4963
|
responseType: "json",
|
|
4736
4964
|
});
|
|
4737
4965
|
p.name = `django-${p.name}`;
|
|
@@ -4774,6 +5002,12 @@ export async function getPyMetadata(pkgList, fetchDepsInfo) {
|
|
|
4774
5002
|
p.license.push(licenseId);
|
|
4775
5003
|
}
|
|
4776
5004
|
}
|
|
5005
|
+
if (body.info.license_expression) {
|
|
5006
|
+
const licenseId = findLicenseId(body.info.license_expression);
|
|
5007
|
+
if (licenseId && !p.license.includes(licenseId)) {
|
|
5008
|
+
p.license.push(licenseId);
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
4777
5011
|
if (body.info.home_page) {
|
|
4778
5012
|
if (body.info.home_page.includes("git")) {
|
|
4779
5013
|
p.repository = { url: body.info.home_page };
|
|
@@ -5611,20 +5845,66 @@ export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
|
5611
5845
|
}
|
|
5612
5846
|
|
|
5613
5847
|
/**
|
|
5614
|
-
* Method to parse requirements.txt
|
|
5848
|
+
* Method to parse requirements.txt file. This must be replaced with atom parsedeps.
|
|
5615
5849
|
*
|
|
5616
|
-
* @param {
|
|
5850
|
+
* @param {String} reqFile Requirements.txt file
|
|
5617
5851
|
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
5852
|
+
*
|
|
5853
|
+
* @returns {Promise[Array<Object>]} List of direct dependencies from the requirements file
|
|
5618
5854
|
*/
|
|
5619
|
-
export async function parseReqFile(
|
|
5855
|
+
export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
5856
|
+
return await parseReqData(reqFile, null, fetchDepsInfo);
|
|
5857
|
+
}
|
|
5858
|
+
|
|
5859
|
+
/**
|
|
5860
|
+
* Method to parse requirements.txt file. Must only be used internally.
|
|
5861
|
+
*
|
|
5862
|
+
* @param {String} reqFile Requirements.txt file
|
|
5863
|
+
* @param {Object} reqData Requirements.txt data for internal invocations from setup.py file etc.
|
|
5864
|
+
*
|
|
5865
|
+
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
5866
|
+
*
|
|
5867
|
+
* @returns {Promise[Array<Object>]} List of direct dependencies from the requirements file
|
|
5868
|
+
*/
|
|
5869
|
+
async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
5620
5870
|
const pkgList = [];
|
|
5621
5871
|
let compScope;
|
|
5872
|
+
if (!reqFile && !reqData) {
|
|
5873
|
+
console.warn(
|
|
5874
|
+
"Either the requirements file or the data needs to be provided for parsing.",
|
|
5875
|
+
);
|
|
5876
|
+
return pkgList;
|
|
5877
|
+
}
|
|
5878
|
+
reqData = reqData || readFileSync(reqFile, { encoding: "utf-8" });
|
|
5879
|
+
const evidence = reqFile
|
|
5880
|
+
? {
|
|
5881
|
+
identity: {
|
|
5882
|
+
field: "purl",
|
|
5883
|
+
confidence: 0.5,
|
|
5884
|
+
methods: [
|
|
5885
|
+
{
|
|
5886
|
+
technique: "manifest-analysis",
|
|
5887
|
+
confidence: 0.5,
|
|
5888
|
+
value: reqFile,
|
|
5889
|
+
},
|
|
5890
|
+
],
|
|
5891
|
+
},
|
|
5892
|
+
}
|
|
5893
|
+
: undefined;
|
|
5622
5894
|
reqData
|
|
5623
5895
|
.replace(/\r/g, "")
|
|
5624
5896
|
.replace(/ [\\]\n/g, "")
|
|
5625
5897
|
.replace(/ {4}/g, " ")
|
|
5626
5898
|
.split("\n")
|
|
5627
5899
|
.forEach((l) => {
|
|
5900
|
+
const properties = reqFile
|
|
5901
|
+
? [
|
|
5902
|
+
{
|
|
5903
|
+
name: "SrcFile",
|
|
5904
|
+
value: reqFile,
|
|
5905
|
+
},
|
|
5906
|
+
]
|
|
5907
|
+
: [];
|
|
5628
5908
|
l = l.trim();
|
|
5629
5909
|
let markers;
|
|
5630
5910
|
if (l.includes(" ; ")) {
|
|
@@ -5659,11 +5939,11 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
5659
5939
|
const name = tmpA[0].trim().replace(";", "");
|
|
5660
5940
|
const versionSpecifiers = l.replace(name, "");
|
|
5661
5941
|
if (!PYTHON_STD_MODULES.includes(name)) {
|
|
5662
|
-
const properties = [];
|
|
5663
5942
|
const apkg = {
|
|
5664
5943
|
name,
|
|
5665
5944
|
version: versionStr,
|
|
5666
5945
|
scope: compScope,
|
|
5946
|
+
evidence,
|
|
5667
5947
|
};
|
|
5668
5948
|
if (
|
|
5669
5949
|
versionSpecifiers?.length > 0 &&
|
|
@@ -5695,7 +5975,9 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
5695
5975
|
name,
|
|
5696
5976
|
version: undefined,
|
|
5697
5977
|
scope: compScope,
|
|
5978
|
+
evidence,
|
|
5698
5979
|
properties: [
|
|
5980
|
+
...properties,
|
|
5699
5981
|
{
|
|
5700
5982
|
name: "cdx:pypi:versionSpecifiers",
|
|
5701
5983
|
value: versionSpecifiers?.length
|
|
@@ -5718,7 +6000,9 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
5718
6000
|
name,
|
|
5719
6001
|
version: undefined,
|
|
5720
6002
|
scope: compScope,
|
|
6003
|
+
evidence,
|
|
5721
6004
|
properties: [
|
|
6005
|
+
...properties,
|
|
5722
6006
|
{
|
|
5723
6007
|
name: "cdx:pypi:versionSpecifiers",
|
|
5724
6008
|
value: versionSpecifiers?.length
|
|
@@ -5743,6 +6027,7 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
5743
6027
|
name,
|
|
5744
6028
|
version: undefined,
|
|
5745
6029
|
scope: compScope,
|
|
6030
|
+
evidence,
|
|
5746
6031
|
properties: [
|
|
5747
6032
|
{
|
|
5748
6033
|
name: "cdx:pypi:versionSpecifiers",
|
|
@@ -5761,6 +6046,7 @@ export async function parseReqFile(reqData, fetchDepsInfo) {
|
|
|
5761
6046
|
name,
|
|
5762
6047
|
version: null,
|
|
5763
6048
|
scope: compScope,
|
|
6049
|
+
evidence,
|
|
5764
6050
|
properties: [
|
|
5765
6051
|
{
|
|
5766
6052
|
name: "cdx:pypi:versionSpecifiers",
|
|
@@ -5884,7 +6170,7 @@ export async function parseSetupPyFile(setupPyData) {
|
|
|
5884
6170
|
lines = lines.concat(tmpA);
|
|
5885
6171
|
}
|
|
5886
6172
|
});
|
|
5887
|
-
return await
|
|
6173
|
+
return await parseReqData(null, lines.join("\n"), false);
|
|
5888
6174
|
}
|
|
5889
6175
|
|
|
5890
6176
|
/**
|
|
@@ -7323,6 +7609,7 @@ export async function parseGemspecData(gemspecData, gemspecFile) {
|
|
|
7323
7609
|
pkg?.version?.includes("File.") ||
|
|
7324
7610
|
pkg?.version?.includes("::")
|
|
7325
7611
|
) {
|
|
7612
|
+
const origVersion = pkg.version;
|
|
7326
7613
|
pkg.version = undefined;
|
|
7327
7614
|
// Can we find the version from the directory name?
|
|
7328
7615
|
if (gemspecFile) {
|
|
@@ -7333,10 +7620,17 @@ export async function parseGemspecData(gemspecData, gemspecFile) {
|
|
|
7333
7620
|
}
|
|
7334
7621
|
}
|
|
7335
7622
|
if (!versionHackMatch && !pkg.version) {
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7623
|
+
if (origVersion?.toLowerCase().includes("version")) {
|
|
7624
|
+
if (DEBUG_MODE) {
|
|
7625
|
+
console.log(
|
|
7626
|
+
`Unable to identify the version for '${pkg.name}' from the string '${origVersion}'. Spec file: ${gemspecFile}`,
|
|
7627
|
+
);
|
|
7628
|
+
}
|
|
7629
|
+
} else {
|
|
7630
|
+
console.log(
|
|
7631
|
+
`Unable to identify the version for '${pkg.name}'. Spec file: ${gemspecFile}`,
|
|
7632
|
+
);
|
|
7633
|
+
}
|
|
7340
7634
|
}
|
|
7341
7635
|
}
|
|
7342
7636
|
for (const aprop of ["authors", "licenses"]) {
|
|
@@ -9236,15 +9530,35 @@ export function parseConanLockData(conanLockData) {
|
|
|
9236
9530
|
if (!conanLockData) {
|
|
9237
9531
|
return pkgList;
|
|
9238
9532
|
}
|
|
9239
|
-
const
|
|
9240
|
-
if (
|
|
9533
|
+
const lockFile = JSON.parse(conanLockData);
|
|
9534
|
+
if (
|
|
9535
|
+
(!lockFile || !lockFile.graph_lock || !lockFile.graph_lock.nodes) &&
|
|
9536
|
+
!lockFile.requires
|
|
9537
|
+
) {
|
|
9241
9538
|
return pkgList;
|
|
9242
9539
|
}
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9540
|
+
if (lockFile.graph_lock?.nodes) {
|
|
9541
|
+
const depends = lockFile.graph_lock.nodes;
|
|
9542
|
+
for (const nk of Object.keys(depends)) {
|
|
9543
|
+
if (depends[nk].ref) {
|
|
9544
|
+
const [purl, name, version] =
|
|
9545
|
+
mapConanPkgRefToPurlStringAndNameAndVersion(depends[nk].ref);
|
|
9546
|
+
if (purl !== null) {
|
|
9547
|
+
pkgList.push({
|
|
9548
|
+
name,
|
|
9549
|
+
version,
|
|
9550
|
+
purl,
|
|
9551
|
+
"bom-ref": decodeURIComponent(purl),
|
|
9552
|
+
});
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
9555
|
+
}
|
|
9556
|
+
} else if (lockFile.requires) {
|
|
9557
|
+
const depends = lockFile.requires;
|
|
9558
|
+
for (const nk of Object.keys(depends)) {
|
|
9559
|
+
depends[nk] = depends[nk].split("%").shift();
|
|
9246
9560
|
const [purl, name, version] = mapConanPkgRefToPurlStringAndNameAndVersion(
|
|
9247
|
-
|
|
9561
|
+
depends[nk],
|
|
9248
9562
|
);
|
|
9249
9563
|
if (purl !== null) {
|
|
9250
9564
|
pkgList.push({
|
|
@@ -9397,6 +9711,218 @@ export async function parseNupkg(nupkgFile) {
|
|
|
9397
9711
|
return parseNuspecData(nupkgFile, nuspecData);
|
|
9398
9712
|
}
|
|
9399
9713
|
|
|
9714
|
+
/**
|
|
9715
|
+
* Method to parse flake.nix files
|
|
9716
|
+
*
|
|
9717
|
+
* @param {String} flakeNixFile flake.nix file to parse
|
|
9718
|
+
* @returns {Object} Object containing package information
|
|
9719
|
+
*/
|
|
9720
|
+
export function parseFlakeNix(flakeNixFile) {
|
|
9721
|
+
const pkgList = [];
|
|
9722
|
+
const dependencies = [];
|
|
9723
|
+
|
|
9724
|
+
if (!existsSync(flakeNixFile)) {
|
|
9725
|
+
return { pkgList, dependencies };
|
|
9726
|
+
}
|
|
9727
|
+
|
|
9728
|
+
try {
|
|
9729
|
+
const flakeContent = readFileSync(flakeNixFile, "utf-8");
|
|
9730
|
+
|
|
9731
|
+
// Extract inputs from flake.nix using regex
|
|
9732
|
+
const inputsRegex = /inputs\s*=\s*\{[^}]*\}/g;
|
|
9733
|
+
let match;
|
|
9734
|
+
while ((match = inputsRegex.exec(flakeContent)) !== null) {
|
|
9735
|
+
const inputBlock = match[0];
|
|
9736
|
+
|
|
9737
|
+
// Match different input patterns including nested inputs
|
|
9738
|
+
const inputPatterns = [
|
|
9739
|
+
/([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)\.url\s*=\s*"([^"]+)"/g,
|
|
9740
|
+
/([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*)\s*=\s*\{\s*url\s*=\s*"([^"]+)"[^}]*\}/gs,
|
|
9741
|
+
];
|
|
9742
|
+
|
|
9743
|
+
const addedPackages = new Set();
|
|
9744
|
+
|
|
9745
|
+
for (const pattern of inputPatterns) {
|
|
9746
|
+
let subMatch;
|
|
9747
|
+
pattern.lastIndex = 0;
|
|
9748
|
+
while ((subMatch = pattern.exec(inputBlock)) !== null) {
|
|
9749
|
+
const name = subMatch[1];
|
|
9750
|
+
const url = subMatch[2] || subMatch[3];
|
|
9751
|
+
|
|
9752
|
+
if (name && url && !addedPackages.has(name)) {
|
|
9753
|
+
addedPackages.add(name);
|
|
9754
|
+
const pkg = {
|
|
9755
|
+
name: name,
|
|
9756
|
+
version: "latest",
|
|
9757
|
+
description: `Nix flake input: ${name}`,
|
|
9758
|
+
scope: "required",
|
|
9759
|
+
properties: [
|
|
9760
|
+
{
|
|
9761
|
+
name: "SrcFile",
|
|
9762
|
+
value: flakeNixFile,
|
|
9763
|
+
},
|
|
9764
|
+
{
|
|
9765
|
+
name: "cdx:nix:input_url",
|
|
9766
|
+
value: url,
|
|
9767
|
+
},
|
|
9768
|
+
],
|
|
9769
|
+
evidence: {
|
|
9770
|
+
identity: {
|
|
9771
|
+
field: "purl",
|
|
9772
|
+
confidence: 0.8,
|
|
9773
|
+
methods: [
|
|
9774
|
+
{
|
|
9775
|
+
technique: "manifest-analysis",
|
|
9776
|
+
confidence: 0.8,
|
|
9777
|
+
value: flakeNixFile,
|
|
9778
|
+
},
|
|
9779
|
+
],
|
|
9780
|
+
},
|
|
9781
|
+
},
|
|
9782
|
+
};
|
|
9783
|
+
|
|
9784
|
+
pkg.purl = generateNixPurl(name, "latest");
|
|
9785
|
+
pkg["bom-ref"] = pkg.purl;
|
|
9786
|
+
|
|
9787
|
+
pkgList.push(pkg);
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
}
|
|
9791
|
+
}
|
|
9792
|
+
} catch (error) {
|
|
9793
|
+
console.warn(`Failed to parse ${flakeNixFile}: ${error.message}`);
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9796
|
+
return { pkgList, dependencies };
|
|
9797
|
+
}
|
|
9798
|
+
|
|
9799
|
+
/**
|
|
9800
|
+
* Method to parse flake.lock files
|
|
9801
|
+
*
|
|
9802
|
+
* @param {String} flakeLockFile flake.lock file to parse
|
|
9803
|
+
* @returns {Object} Object containing locked dependency information
|
|
9804
|
+
*/
|
|
9805
|
+
export function parseFlakeLock(flakeLockFile) {
|
|
9806
|
+
const pkgList = [];
|
|
9807
|
+
const dependencies = [];
|
|
9808
|
+
|
|
9809
|
+
if (!existsSync(flakeLockFile)) {
|
|
9810
|
+
return { pkgList, dependencies };
|
|
9811
|
+
}
|
|
9812
|
+
|
|
9813
|
+
try {
|
|
9814
|
+
const lockContent = readFileSync(flakeLockFile, "utf-8");
|
|
9815
|
+
const lockData = JSON.parse(lockContent);
|
|
9816
|
+
|
|
9817
|
+
if (lockData.nodes) {
|
|
9818
|
+
for (const [nodeName, nodeData] of Object.entries(lockData.nodes)) {
|
|
9819
|
+
if (nodeName === "root" || !nodeData.locked) continue;
|
|
9820
|
+
|
|
9821
|
+
const locked = nodeData.locked;
|
|
9822
|
+
|
|
9823
|
+
let version = "latest";
|
|
9824
|
+
if (locked.rev) {
|
|
9825
|
+
version = locked.rev.substring(0, 7);
|
|
9826
|
+
} else if (locked.ref) {
|
|
9827
|
+
version = locked.ref;
|
|
9828
|
+
}
|
|
9829
|
+
|
|
9830
|
+
const pkg = {
|
|
9831
|
+
name: nodeName,
|
|
9832
|
+
version: version,
|
|
9833
|
+
description: `Nix flake dependency: ${nodeName}`,
|
|
9834
|
+
scope: "required",
|
|
9835
|
+
properties: [
|
|
9836
|
+
{
|
|
9837
|
+
name: "SrcFile",
|
|
9838
|
+
value: flakeLockFile,
|
|
9839
|
+
},
|
|
9840
|
+
],
|
|
9841
|
+
evidence: {
|
|
9842
|
+
identity: {
|
|
9843
|
+
field: "purl",
|
|
9844
|
+
confidence: 1.0,
|
|
9845
|
+
methods: [
|
|
9846
|
+
{
|
|
9847
|
+
technique: "manifest-analysis",
|
|
9848
|
+
confidence: 1.0,
|
|
9849
|
+
value: flakeLockFile,
|
|
9850
|
+
},
|
|
9851
|
+
],
|
|
9852
|
+
},
|
|
9853
|
+
},
|
|
9854
|
+
};
|
|
9855
|
+
|
|
9856
|
+
if (locked.narHash) {
|
|
9857
|
+
pkg.properties.push({
|
|
9858
|
+
name: "cdx:nix:nar_hash",
|
|
9859
|
+
value: locked.narHash,
|
|
9860
|
+
});
|
|
9861
|
+
}
|
|
9862
|
+
|
|
9863
|
+
if (locked.lastModified) {
|
|
9864
|
+
pkg.properties.push({
|
|
9865
|
+
name: "cdx:nix:last_modified",
|
|
9866
|
+
value: locked.lastModified.toString(),
|
|
9867
|
+
});
|
|
9868
|
+
}
|
|
9869
|
+
|
|
9870
|
+
if (locked.rev) {
|
|
9871
|
+
pkg.properties.push({
|
|
9872
|
+
name: "cdx:nix:revision",
|
|
9873
|
+
value: locked.rev,
|
|
9874
|
+
});
|
|
9875
|
+
}
|
|
9876
|
+
|
|
9877
|
+
if (locked.ref) {
|
|
9878
|
+
pkg.properties.push({
|
|
9879
|
+
name: "cdx:nix:ref",
|
|
9880
|
+
value: locked.ref,
|
|
9881
|
+
});
|
|
9882
|
+
}
|
|
9883
|
+
|
|
9884
|
+
pkg.purl = generateNixPurl(nodeName, version);
|
|
9885
|
+
pkg["bom-ref"] = pkg.purl;
|
|
9886
|
+
|
|
9887
|
+
pkgList.push(pkg);
|
|
9888
|
+
}
|
|
9889
|
+
|
|
9890
|
+
// Generate dependency relationships from root inputs
|
|
9891
|
+
if (lockData.nodes?.root?.inputs) {
|
|
9892
|
+
const rootInputs = Object.keys(lockData.nodes.root.inputs);
|
|
9893
|
+
if (rootInputs.length > 0) {
|
|
9894
|
+
dependencies.push({
|
|
9895
|
+
ref: "pkg:nix/flake@latest",
|
|
9896
|
+
dependsOn: rootInputs
|
|
9897
|
+
.map(
|
|
9898
|
+
(input) =>
|
|
9899
|
+
pkgList.find((pkg) => pkg.name === input)?.["bom-ref"],
|
|
9900
|
+
)
|
|
9901
|
+
.filter(Boolean),
|
|
9902
|
+
});
|
|
9903
|
+
}
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
} catch (error) {
|
|
9907
|
+
console.warn(`Failed to parse ${flakeLockFile}: ${error.message}`);
|
|
9908
|
+
}
|
|
9909
|
+
|
|
9910
|
+
return { pkgList, dependencies };
|
|
9911
|
+
}
|
|
9912
|
+
|
|
9913
|
+
/**
|
|
9914
|
+
* Generate a Nix PURL from input information
|
|
9915
|
+
*
|
|
9916
|
+
* @param {String} name Package name
|
|
9917
|
+
* @param {String} version Package version
|
|
9918
|
+
* @returns {String} PURL string
|
|
9919
|
+
*/
|
|
9920
|
+
function generateNixPurl(name, version) {
|
|
9921
|
+
// For now, use a generic nix PURL type
|
|
9922
|
+
// In the future, this could be more sophisticated based on the source type
|
|
9923
|
+
return `pkg:nix/${name}@${version}`;
|
|
9924
|
+
}
|
|
9925
|
+
|
|
9400
9926
|
/**
|
|
9401
9927
|
* Method to parse .nuspec files
|
|
9402
9928
|
*
|
|
@@ -9816,7 +10342,7 @@ export function parseCsProjData(csProjData, projFile, pkgNameVersions = {}) {
|
|
|
9816
10342
|
continue;
|
|
9817
10343
|
}
|
|
9818
10344
|
pkg.name = pref.Include;
|
|
9819
|
-
pkg.version = pref.Version;
|
|
10345
|
+
pkg.version = pref.Version || pkgNameVersions[pkg.name];
|
|
9820
10346
|
pkg.purl = `pkg:nuget/${pkg.name}@${pkg.version}`;
|
|
9821
10347
|
pkg["bom-ref"] = pkg.purl;
|
|
9822
10348
|
if (projFile) {
|
|
@@ -9853,9 +10379,11 @@ export function parseCsProjData(csProjData, projFile, pkgNameVersions = {}) {
|
|
|
9853
10379
|
pkg.name = incParts[0];
|
|
9854
10380
|
pkg.properties = [];
|
|
9855
10381
|
if (incParts.length > 1 && incParts[1].includes("Version")) {
|
|
9856
|
-
pkg.version =
|
|
10382
|
+
pkg.version =
|
|
10383
|
+
incParts[1].replace("Version=", "").trim() ||
|
|
10384
|
+
pkgNameVersions[pkg.name];
|
|
9857
10385
|
}
|
|
9858
|
-
const version = pkg.version
|
|
10386
|
+
const version = pkg.version;
|
|
9859
10387
|
if (version) {
|
|
9860
10388
|
pkg.purl = `pkg:nuget/${pkg.name}@${version}`;
|
|
9861
10389
|
} else {
|
|
@@ -9999,25 +10527,27 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
9999
10527
|
return { pkgList, dependenciesList };
|
|
10000
10528
|
}
|
|
10001
10529
|
csProjData = JSON.parse(csProjData);
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10530
|
+
let purlString;
|
|
10531
|
+
if (csProjData.project?.restore?.projectName) {
|
|
10532
|
+
purlString = new PackageURL(
|
|
10533
|
+
"nuget",
|
|
10534
|
+
"",
|
|
10535
|
+
csProjData.project?.restore?.projectName,
|
|
10536
|
+
csProjData.project.version || "latest",
|
|
10537
|
+
null,
|
|
10538
|
+
null,
|
|
10539
|
+
).toString();
|
|
10540
|
+
rootPkg = {
|
|
10541
|
+
group: "",
|
|
10542
|
+
name: csProjData.project.restore.projectName,
|
|
10543
|
+
version: csProjData.project.version || "latest",
|
|
10544
|
+
type: "application",
|
|
10545
|
+
purl: purlString,
|
|
10546
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
10547
|
+
};
|
|
10548
|
+
pkgList.push(rootPkg);
|
|
10549
|
+
}
|
|
10019
10550
|
const rootPkgDeps = new Set();
|
|
10020
|
-
|
|
10021
10551
|
// create root pkg deps
|
|
10022
10552
|
if (csProjData.targets && csProjData.projectFileDependencyGroups) {
|
|
10023
10553
|
for (const frameworkTarget in csProjData.projectFileDependencyGroups) {
|
|
@@ -10074,11 +10604,12 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
10074
10604
|
rootPkgDeps.add(dpurl);
|
|
10075
10605
|
}
|
|
10076
10606
|
}
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10607
|
+
if (purlString && rootPkgDeps.size) {
|
|
10608
|
+
dependenciesList.push({
|
|
10609
|
+
ref: purlString,
|
|
10610
|
+
dependsOn: Array.from(rootPkgDeps).sort(),
|
|
10611
|
+
});
|
|
10612
|
+
}
|
|
10082
10613
|
}
|
|
10083
10614
|
|
|
10084
10615
|
if (csProjData.libraries && csProjData.targets) {
|
|
@@ -10453,7 +10984,10 @@ export function parseComposerJson(composerJsonFile) {
|
|
|
10453
10984
|
const composerData = JSON.parse(
|
|
10454
10985
|
readFileSync(composerJsonFile, { encoding: "utf-8" }),
|
|
10455
10986
|
);
|
|
10456
|
-
const rootRequires =
|
|
10987
|
+
const rootRequires = {
|
|
10988
|
+
...composerData.require,
|
|
10989
|
+
...composerData["require-dev"],
|
|
10990
|
+
};
|
|
10457
10991
|
const pkgName = composerData.name;
|
|
10458
10992
|
if (pkgName) {
|
|
10459
10993
|
moduleParent.group = dirname(pkgName);
|
|
@@ -11321,7 +11855,7 @@ export async function collectMvnDependencies(
|
|
|
11321
11855
|
copyArgs = copyArgs.concat(addArgs);
|
|
11322
11856
|
}
|
|
11323
11857
|
if (basePath && basePath !== MAVEN_CACHE_DIR) {
|
|
11324
|
-
console.log(`Executing '${mavenCmd}
|
|
11858
|
+
console.log(`Executing '${mavenCmd} in ${basePath}`);
|
|
11325
11859
|
const result = safeSpawnSync(mavenCmd, copyArgs, {
|
|
11326
11860
|
cwd: basePath,
|
|
11327
11861
|
encoding: "utf-8",
|
|
@@ -13304,7 +13838,7 @@ export function executeAtom(src, args, extra_env = {}) {
|
|
|
13304
13838
|
}
|
|
13305
13839
|
}
|
|
13306
13840
|
if (DEBUG_MODE) {
|
|
13307
|
-
console.log("Executing", ATOM_BIN
|
|
13841
|
+
console.log("Executing", ATOM_BIN);
|
|
13308
13842
|
}
|
|
13309
13843
|
const env = {
|
|
13310
13844
|
...process.env,
|
|
@@ -13606,9 +14140,9 @@ export function createUVLock(basePath, options) {
|
|
|
13606
14140
|
* @param {string} tempVenvDir Temp venv dir
|
|
13607
14141
|
* @param {Object} parentComponent Parent component
|
|
13608
14142
|
*
|
|
13609
|
-
* @returns List of packages from the virtual env
|
|
14143
|
+
* @returns {Object} List of packages from the virtual env
|
|
13610
14144
|
*/
|
|
13611
|
-
export function getPipFrozenTree(
|
|
14145
|
+
export async function getPipFrozenTree(
|
|
13612
14146
|
basePath,
|
|
13613
14147
|
reqOrSetupFile,
|
|
13614
14148
|
tempVenvDir,
|
|
@@ -13623,6 +14157,20 @@ export function getPipFrozenTree(
|
|
|
13623
14157
|
const env = {
|
|
13624
14158
|
...process.env,
|
|
13625
14159
|
};
|
|
14160
|
+
|
|
14161
|
+
// FIX: Create a set of explicit dependencies from requirements.txt to identify root packages.
|
|
14162
|
+
const explicitDeps = new Set();
|
|
14163
|
+
if (reqOrSetupFile?.endsWith(".txt") && safeExistsSync(reqOrSetupFile)) {
|
|
14164
|
+
// We only need the package names, so we pass `false` to avoid fetching full metadata.
|
|
14165
|
+
const tempPkgList = await parseReqFile(reqOrSetupFile, null, false);
|
|
14166
|
+
for (const pkg of tempPkgList) {
|
|
14167
|
+
if (pkg.name) {
|
|
14168
|
+
// Normalize the name (lowercase, hyphenated) for accurate lookups.
|
|
14169
|
+
explicitDeps.add(pkg.name.replace(/_/g, "-").toLowerCase());
|
|
14170
|
+
}
|
|
14171
|
+
}
|
|
14172
|
+
}
|
|
14173
|
+
|
|
13626
14174
|
/**
|
|
13627
14175
|
* Let's start with an attempt to create a new temporary virtual environment in case we aren't in one
|
|
13628
14176
|
*
|
|
@@ -13804,7 +14352,7 @@ export function getPipFrozenTree(
|
|
|
13804
14352
|
`**PIP**: Trying pip install using the arguments ${pipInstallArgs.join(" ")}`,
|
|
13805
14353
|
);
|
|
13806
14354
|
if (DEBUG_MODE) {
|
|
13807
|
-
console.log("Executing", python_cmd_for_tree
|
|
14355
|
+
console.log("Executing", python_cmd_for_tree);
|
|
13808
14356
|
}
|
|
13809
14357
|
// Attempt to perform pip install
|
|
13810
14358
|
result = safeSpawnSync(python_cmd_for_tree, pipInstallArgs, {
|
|
@@ -13883,7 +14431,6 @@ export function getPipFrozenTree(
|
|
|
13883
14431
|
console.info(
|
|
13884
14432
|
"\nEXPERIMENTAL: Invoke cdxgen with '--feature-flags safe-pip-install' to recover a partial dependency tree for projects with build errors.\n",
|
|
13885
14433
|
);
|
|
13886
|
-
console.log("args used:", pipInstallArgs);
|
|
13887
14434
|
if (result.stderr) {
|
|
13888
14435
|
console.log(result.stderr);
|
|
13889
14436
|
}
|
|
@@ -14034,12 +14581,14 @@ export function getPipFrozenTree(
|
|
|
14034
14581
|
};
|
|
14035
14582
|
if (scope !== "excluded") {
|
|
14036
14583
|
pkgList.push(apkg);
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14584
|
+
if (explicitDeps.size === 0 || explicitDeps.has(name)) {
|
|
14585
|
+
rootList.push({
|
|
14586
|
+
name,
|
|
14587
|
+
version,
|
|
14588
|
+
purl: purlString,
|
|
14589
|
+
"bom-ref": decodeURIComponent(purlString),
|
|
14590
|
+
});
|
|
14591
|
+
}
|
|
14043
14592
|
flattenDeps(dependenciesMap, pkgList, reqOrSetupFile, t);
|
|
14044
14593
|
} else {
|
|
14045
14594
|
formulationList.push(apkg);
|
|
@@ -15829,3 +16378,154 @@ function collectAllLdConfs(basePath, ldConf, allLdConfDirs, libPaths) {
|
|
|
15829
16378
|
}
|
|
15830
16379
|
}
|
|
15831
16380
|
}
|
|
16381
|
+
|
|
16382
|
+
/**
|
|
16383
|
+
* Get information about the runtime.
|
|
16384
|
+
*
|
|
16385
|
+
* @returns {Object} Object containing the name and version of the runtime
|
|
16386
|
+
*/
|
|
16387
|
+
export function getRuntimeInformation() {
|
|
16388
|
+
const runtimeInfo = {};
|
|
16389
|
+
|
|
16390
|
+
if (typeof globalThis.Deno !== "undefined" && globalThis.Deno.version?.deno) {
|
|
16391
|
+
runtimeInfo.runtime = "Deno";
|
|
16392
|
+
runtimeInfo.version = globalThis.Deno.version.deno;
|
|
16393
|
+
} else if (typeof globalThis.Bun !== "undefined" && globalThis.Bun.version) {
|
|
16394
|
+
runtimeInfo.runtime = "Bun";
|
|
16395
|
+
runtimeInfo.version = globalThis.Bun.version;
|
|
16396
|
+
} else if (
|
|
16397
|
+
typeof globalThis.process !== "undefined" &&
|
|
16398
|
+
globalThis.process.versions?.node
|
|
16399
|
+
) {
|
|
16400
|
+
runtimeInfo.runtime = "Node.js";
|
|
16401
|
+
runtimeInfo.version = globalThis.process.versions.node;
|
|
16402
|
+
const report = process.report.getReport();
|
|
16403
|
+
const nodeSourceUrl = report?.header?.release?.sourceUrl;
|
|
16404
|
+
// Collect the bundled components in node.js
|
|
16405
|
+
if (report?.header?.componentVersions) {
|
|
16406
|
+
const nodeBundledComponents = [];
|
|
16407
|
+
for (const [name, version] of Object.entries(
|
|
16408
|
+
report.header.componentVersions,
|
|
16409
|
+
)) {
|
|
16410
|
+
if (name === "node") {
|
|
16411
|
+
continue;
|
|
16412
|
+
}
|
|
16413
|
+
const apkg = {
|
|
16414
|
+
name,
|
|
16415
|
+
version,
|
|
16416
|
+
description: `Bundled with Node.js ${runtimeInfo.version}`,
|
|
16417
|
+
type: "library",
|
|
16418
|
+
scope: "excluded",
|
|
16419
|
+
purl: `pkg:generic/${name}@${version}`,
|
|
16420
|
+
"bom-ref": `pkg:generic/${name}@${version}`,
|
|
16421
|
+
};
|
|
16422
|
+
if (nodeSourceUrl) {
|
|
16423
|
+
apkg.externalReferences = [
|
|
16424
|
+
{
|
|
16425
|
+
url: nodeSourceUrl,
|
|
16426
|
+
type: "source-distribution",
|
|
16427
|
+
comment: "Node.js release url",
|
|
16428
|
+
},
|
|
16429
|
+
];
|
|
16430
|
+
}
|
|
16431
|
+
nodeBundledComponents.push(apkg);
|
|
16432
|
+
}
|
|
16433
|
+
if (nodeBundledComponents.length) {
|
|
16434
|
+
runtimeInfo.components = nodeBundledComponents;
|
|
16435
|
+
}
|
|
16436
|
+
}
|
|
16437
|
+
if (report.sharedObjects) {
|
|
16438
|
+
const osSharedObjects = [];
|
|
16439
|
+
for (const aso of report.sharedObjects) {
|
|
16440
|
+
const name = basename(aso);
|
|
16441
|
+
if (name === "node") {
|
|
16442
|
+
continue;
|
|
16443
|
+
}
|
|
16444
|
+
const apkg = {
|
|
16445
|
+
name,
|
|
16446
|
+
type: "library",
|
|
16447
|
+
scope: "excluded",
|
|
16448
|
+
purl: `pkg:generic/${name}#${aso}`,
|
|
16449
|
+
"bom-ref": `pkg:generic/${name}`,
|
|
16450
|
+
};
|
|
16451
|
+
osSharedObjects.push(apkg);
|
|
16452
|
+
}
|
|
16453
|
+
if (osSharedObjects.length) {
|
|
16454
|
+
runtimeInfo.components = osSharedObjects;
|
|
16455
|
+
}
|
|
16456
|
+
}
|
|
16457
|
+
} else {
|
|
16458
|
+
runtimeInfo.runtime = "Unknown";
|
|
16459
|
+
runtimeInfo.version = "N/A";
|
|
16460
|
+
}
|
|
16461
|
+
|
|
16462
|
+
return runtimeInfo;
|
|
16463
|
+
}
|
|
16464
|
+
|
|
16465
|
+
/**
|
|
16466
|
+
* Checks for dangerous Unicode characters that could enable homograph attacks
|
|
16467
|
+
*
|
|
16468
|
+
* @param {string} str String to check
|
|
16469
|
+
* @returns {boolean} true if dangerous Unicode is found
|
|
16470
|
+
*/
|
|
16471
|
+
// biome-ignore-start lint/suspicious/noControlCharactersInRegex: validation
|
|
16472
|
+
export function hasDangerousUnicode(str) {
|
|
16473
|
+
// Check for bidirectional control characters
|
|
16474
|
+
const bidiChars = /[\u202A-\u202E\u2066-\u2069]/;
|
|
16475
|
+
if (bidiChars.test(str)) {
|
|
16476
|
+
return true;
|
|
16477
|
+
}
|
|
16478
|
+
|
|
16479
|
+
// Check for zero-width characters that could be used for obfuscation
|
|
16480
|
+
const zeroWidthChars = /[\u200B-\u200D\uFEFF]/;
|
|
16481
|
+
if (zeroWidthChars.test(str)) {
|
|
16482
|
+
return true;
|
|
16483
|
+
}
|
|
16484
|
+
|
|
16485
|
+
// Check for control characters (except common ones like \n, \r, \t)
|
|
16486
|
+
const controlChars = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/;
|
|
16487
|
+
if (controlChars.test(str)) {
|
|
16488
|
+
return true;
|
|
16489
|
+
}
|
|
16490
|
+
|
|
16491
|
+
return false;
|
|
16492
|
+
}
|
|
16493
|
+
// biome-ignore-end lint/suspicious/noControlCharactersInRegex: validation
|
|
16494
|
+
|
|
16495
|
+
/**
|
|
16496
|
+
* Validates that a root is a legitimate Windows drive letter format
|
|
16497
|
+
*
|
|
16498
|
+
* @param {string} root Root to validate
|
|
16499
|
+
* @returns {boolean} true if valid drive format
|
|
16500
|
+
*/
|
|
16501
|
+
export function isValidDriveRoot(root) {
|
|
16502
|
+
// Must be at most 3 characters: letter, colon, backslash
|
|
16503
|
+
if (root.length > 3) {
|
|
16504
|
+
return false;
|
|
16505
|
+
}
|
|
16506
|
+
|
|
16507
|
+
// Check each character individually to prevent Unicode lookalikes
|
|
16508
|
+
const driveLetter = root.charAt(0);
|
|
16509
|
+
const colon = root.charAt(1);
|
|
16510
|
+
const backslash = root.charAt(2);
|
|
16511
|
+
|
|
16512
|
+
// Drive letter must be ASCII A-Z or a-z
|
|
16513
|
+
const charCode = driveLetter.charCodeAt(0);
|
|
16514
|
+
const isAsciiLetter =
|
|
16515
|
+
(charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122);
|
|
16516
|
+
if (!isAsciiLetter) {
|
|
16517
|
+
return false;
|
|
16518
|
+
}
|
|
16519
|
+
|
|
16520
|
+
// Colon must be exactly ASCII colon (0x3A)
|
|
16521
|
+
if (colon.charCodeAt(0) !== 0x3a) {
|
|
16522
|
+
return false;
|
|
16523
|
+
}
|
|
16524
|
+
|
|
16525
|
+
// Backslash (optional) must be exactly ASCII backslash (0x5C)
|
|
16526
|
+
if (backslash && backslash.charCodeAt(0) !== 0x5c) {
|
|
16527
|
+
return false;
|
|
16528
|
+
}
|
|
16529
|
+
|
|
16530
|
+
return true;
|
|
16531
|
+
}
|