@cdxgen/cdxgen 13.5.0 → 13.6.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 +6 -6
- package/bin/cdxgen.js +4 -0
- package/bin/evinse.js +3 -0
- package/bin/validate.js +6 -0
- package/data/license-aliases.json +24 -2
- package/data/remediations.json +124 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/cli/bomAssembly.js +92 -3
- package/lib/cli/index.js +108 -1
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +237 -46
- package/lib/cli/managedBom.js +142 -16
- package/lib/cli/nativeBom.js +42 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +8 -0
- package/lib/core/fs.js +12 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/gradleutils.js +828 -224
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +89 -2
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +796 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +346 -43
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/deps.js +35 -0
- package/lib/inventory/dosai.js +139 -5
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/envcontext.js +307 -13
- package/lib/inventory/evidenceUtils.js +11 -0
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +31 -3
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +49 -2
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +12 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +6 -1
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +1 -1
- package/lib/validator/index.js +1 -1
- package/package.json +3 -2
- package/types/lib/cli/bomAssembly.d.ts +13 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +4 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +47 -13
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +20 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +19 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +17 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +52 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +10 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
package/lib/evinser/evinser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { builtinModules } from "node:module";
|
|
2
3
|
import path, { basename, join, resolve } from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
4
5
|
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
isScalasemLanguage,
|
|
55
56
|
} from "../ecosystems/scalasem.js";
|
|
56
57
|
import {
|
|
58
|
+
atomBomFileArgs,
|
|
57
59
|
atomBuildToolArgs,
|
|
58
60
|
atomCompileCommandsArgs,
|
|
59
61
|
buildAtomCommandEnv,
|
|
@@ -91,6 +93,7 @@ import {
|
|
|
91
93
|
readDosaiMethodsReport,
|
|
92
94
|
} from "../inventory/dosai.js";
|
|
93
95
|
import { parseOccurrenceEvidenceLocation } from "../inventory/evidenceUtils.js";
|
|
96
|
+
import { splitPurlLoose } from "../inventory/purl.js";
|
|
94
97
|
import { executeOsQuery } from "../managers/binary.js";
|
|
95
98
|
import { MAX_JSON_TEXT_BYTES, readJsonFile } from "../parsers/largeJson.js";
|
|
96
99
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
@@ -98,7 +101,8 @@ import { createOrLoad } from "./db.js";
|
|
|
98
101
|
import { findPurlLocations } from "./scalasem.js";
|
|
99
102
|
import { createSemanticsSlices } from "./swiftsem.js";
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
// keyed by type names, which can be "constructor", "toString", ...: no prototype
|
|
105
|
+
const typePurlsCache = Object.create(null);
|
|
102
106
|
|
|
103
107
|
function appendUniqueProperty(properties, name, value) {
|
|
104
108
|
if (value === undefined || value === null || value === "") {
|
|
@@ -261,6 +265,7 @@ export function buildEvinseOptions(options, args, input) {
|
|
|
261
265
|
force: false,
|
|
262
266
|
withReachables: options.deep,
|
|
263
267
|
usagesSlicesFile: options.usagesSlicesFile,
|
|
268
|
+
callsitesSlicesFile: options.callsitesSlicesFile,
|
|
264
269
|
dataFlowSlicesFile: options.dataFlowSlicesFile,
|
|
265
270
|
reachablesSlicesFile: options.reachablesSlicesFile,
|
|
266
271
|
semanticsSlicesFile: options.semanticsSlicesFile,
|
|
@@ -734,6 +739,7 @@ export async function createSlice(
|
|
|
734
739
|
args.push("--slice-depth");
|
|
735
740
|
args.push(readEnvironmentVariable("ATOM_SLICE_DEPTH"));
|
|
736
741
|
}
|
|
742
|
+
args.push(...atomBomFileArgs(sliceType, options));
|
|
737
743
|
args.push(...atomCompileCommandsArgs(filePath, language, options));
|
|
738
744
|
args.push(...buildToolArgs);
|
|
739
745
|
args.push(resolve(filePath));
|
|
@@ -1573,6 +1579,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1573
1579
|
cryptoGeneratePurls = retMap.cryptoGeneratePurls;
|
|
1574
1580
|
}
|
|
1575
1581
|
// Reuse existing usages slices
|
|
1582
|
+
let usagesAtomFile;
|
|
1576
1583
|
if (options.usagesSlicesFile && usableSlicesFile(options.usagesSlicesFile)) {
|
|
1577
1584
|
usageSlice = filterAtomSliceData(
|
|
1578
1585
|
readSlicesFile(options.usagesSlicesFile, "usages"),
|
|
@@ -1582,6 +1589,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1582
1589
|
} else {
|
|
1583
1590
|
// Generate our own slices
|
|
1584
1591
|
retMap = await createSlice(language, dirPath, "usages", options);
|
|
1592
|
+
usagesAtomFile = retMap?.atomFile || "";
|
|
1585
1593
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
1586
1594
|
usageSlice = filterAtomSliceData(
|
|
1587
1595
|
readSlicesFile(retMap.slicesFile, "usages"),
|
|
@@ -1661,6 +1669,35 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1661
1669
|
servicesMap = retMap.servicesMap;
|
|
1662
1670
|
userDefinedTypesMap = retMap.userDefinedTypesMap;
|
|
1663
1671
|
}
|
|
1672
|
+
// Parse the callsites slice (JS/TS): one record per external use site, each
|
|
1673
|
+
// record's own (file, line) is an occurrence of the module's package. The
|
|
1674
|
+
// usages slice above still provides services and endpoints.
|
|
1675
|
+
if (JS_LANGUAGES.includes(language)) {
|
|
1676
|
+
let callsitesSlice;
|
|
1677
|
+
if (
|
|
1678
|
+
options.callsitesSlicesFile &&
|
|
1679
|
+
usableSlicesFile(options.callsitesSlicesFile)
|
|
1680
|
+
) {
|
|
1681
|
+
callsitesSlice = readSlicesFile(options.callsitesSlicesFile, "callsites");
|
|
1682
|
+
} else if (
|
|
1683
|
+
usagesAtomFile !== undefined &&
|
|
1684
|
+
!safeExistsSync(usagesAtomFile)
|
|
1685
|
+
) {
|
|
1686
|
+
// The usages run above built no atom (stopped at the timeout, or atom
|
|
1687
|
+
// failed); a callsites run would rebuild the same atom and fail the same way.
|
|
1688
|
+
console.log(
|
|
1689
|
+
"Skipping the callsites slice since atom could not build the atom for the usages slice.",
|
|
1690
|
+
);
|
|
1691
|
+
} else {
|
|
1692
|
+
retMap = await createSlice(language, dirPath, "callsites", options);
|
|
1693
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
1694
|
+
callsitesSlice = readSlicesFile(retMap.slicesFile, "callsites");
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
if (callsitesSlice?.callsites?.length) {
|
|
1698
|
+
attributeJsCallsites(callsitesSlice, purlImportsMap, purlLocationMap);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1664
1701
|
// A component whose calls the slices do not show (code inside the project,
|
|
1665
1702
|
// such as a vendored library) is still used where its header is included
|
|
1666
1703
|
for (const [purl, includeLocations] of cIncludeLocations) {
|
|
@@ -1968,8 +2005,15 @@ export async function parseSliceUsages(
|
|
|
1968
2005
|
) {
|
|
1969
2006
|
const fileName = slice.fileName;
|
|
1970
2007
|
const typesToLookup = new Set();
|
|
1971
|
-
|
|
1972
|
-
const
|
|
2008
|
+
// keyed by type names, which can be "constructor", "toString", ...: no prototype
|
|
2009
|
+
const lKeyOverrides = Object.create(null);
|
|
2010
|
+
// A user defined type has no usages; its fields' types are evidence at the fields' lines.
|
|
2011
|
+
const usages =
|
|
2012
|
+
slice.usages ||
|
|
2013
|
+
(slice.fields || [])
|
|
2014
|
+
.filter((f) => f?.typeFullName)
|
|
2015
|
+
.map((f) => ({ targetObj: f }));
|
|
2016
|
+
const isJs = JS_LANGUAGES.includes(language);
|
|
1973
2017
|
// What should be the line number to use. slice.lineNumber would be quite coarse and could lead to reports such as
|
|
1974
2018
|
// #1670. Line numbers under targetObj and definedBy is a safe bet for dynamic languages, but occassionally leads to
|
|
1975
2019
|
// confusion when inter-procedural tracking works better than expected.
|
|
@@ -1992,13 +2036,27 @@ export async function parseSliceUsages(
|
|
|
1992
2036
|
sliceLineNumber ||
|
|
1993
2037
|
ausage?.targetObj?.lineNumber ||
|
|
1994
2038
|
ausage?.definedBy?.lineNumber;
|
|
2039
|
+
// JavaScript entries name the imported module they resolve to; attribute those by package
|
|
2040
|
+
// and keep the symbol-based matching below for entries without one.
|
|
2041
|
+
if (isJs) {
|
|
2042
|
+
attributeJsModules(ausage, fileName, purlImportsMap, purlLocationMap);
|
|
2043
|
+
}
|
|
2044
|
+
const viaSymbols = (o) => !(isJs && o?.module);
|
|
1995
2045
|
// First capture the types in the targetObj and definedBy
|
|
1996
2046
|
for (const atype of [
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2047
|
+
...(viaSymbols(ausage?.targetObj)
|
|
2048
|
+
? [
|
|
2049
|
+
[ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
|
|
2050
|
+
[ausage?.targetObj?.isExternal, ausage?.targetObj?.resolvedMethod],
|
|
2051
|
+
]
|
|
2052
|
+
: []),
|
|
2053
|
+
...(viaSymbols(ausage?.definedBy)
|
|
2054
|
+
? [
|
|
2055
|
+
[ausage?.definedBy?.name?.includes("::"), ausage?.definedBy?.name],
|
|
2056
|
+
[ausage?.definedBy?.isExternal, ausage?.definedBy?.typeFullName],
|
|
2057
|
+
[ausage?.definedBy?.isExternal, ausage?.definedBy?.resolvedMethod],
|
|
2058
|
+
]
|
|
2059
|
+
: []),
|
|
2002
2060
|
...(ausage?.fields || []).map((f) => [f?.isExternal, f?.typeFullName]),
|
|
2003
2061
|
]) {
|
|
2004
2062
|
if (
|
|
@@ -2039,7 +2097,8 @@ export async function parseSliceUsages(
|
|
|
2039
2097
|
for (const acall of []
|
|
2040
2098
|
.concat(ausage?.invokedCalls || [])
|
|
2041
2099
|
.concat(ausage?.argToCalls || [])
|
|
2042
|
-
.concat(ausage?.procedures || [])
|
|
2100
|
+
.concat(ausage?.procedures || [])
|
|
2101
|
+
.filter(viaSymbols)) {
|
|
2043
2102
|
if (
|
|
2044
2103
|
acall.resolvedMethod?.startsWith("@") ||
|
|
2045
2104
|
acall?.callName?.includes("::")
|
|
@@ -2139,9 +2198,7 @@ export async function parseSliceUsages(
|
|
|
2139
2198
|
if (!purlLocationMap[apurl]) {
|
|
2140
2199
|
purlLocationMap[apurl] = new Set();
|
|
2141
2200
|
}
|
|
2142
|
-
|
|
2143
|
-
purlLocationMap[apurl].add(...lKeyOverrides[atype]);
|
|
2144
|
-
}
|
|
2201
|
+
addAllLocations(purlLocationMap[apurl], lKeyOverrides[atype]);
|
|
2145
2202
|
}
|
|
2146
2203
|
}
|
|
2147
2204
|
} else {
|
|
@@ -2149,9 +2206,7 @@ export async function parseSliceUsages(
|
|
|
2149
2206
|
if (!purlLocationMap[apurl]) {
|
|
2150
2207
|
purlLocationMap[apurl] = new Set();
|
|
2151
2208
|
}
|
|
2152
|
-
|
|
2153
|
-
purlLocationMap[apurl].add(...lKeyOverrides[atype]);
|
|
2154
|
-
}
|
|
2209
|
+
addAllLocations(purlLocationMap[apurl], lKeyOverrides[atype]);
|
|
2155
2210
|
}
|
|
2156
2211
|
}
|
|
2157
2212
|
}
|
|
@@ -2173,9 +2228,7 @@ export async function parseSliceUsages(
|
|
|
2173
2228
|
if (!purlLocationMap[ns.purl]) {
|
|
2174
2229
|
purlLocationMap[ns.purl] = new Set();
|
|
2175
2230
|
}
|
|
2176
|
-
|
|
2177
|
-
purlLocationMap[ns.purl].add(...lKeyOverrides[atype]);
|
|
2178
|
-
}
|
|
2231
|
+
addAllLocations(purlLocationMap[ns.purl], lKeyOverrides[atype]);
|
|
2179
2232
|
}
|
|
2180
2233
|
typePurlsCache[atype] = nsHits;
|
|
2181
2234
|
} else {
|
|
@@ -2583,6 +2636,17 @@ export function detectServicesFromOpenAPI(
|
|
|
2583
2636
|
* @param {Array} slice Usages array for each objectSlice
|
|
2584
2637
|
* @param {Object} servicesMap Existing service map
|
|
2585
2638
|
*/
|
|
2639
|
+
/**
|
|
2640
|
+
* The text routes and endpoints are read from: JavaScript slices keep a call's source text in
|
|
2641
|
+
* `code` and the resolved callee name in `resolvedMethod`; other languages have only the latter.
|
|
2642
|
+
*
|
|
2643
|
+
* @param {Object|undefined} entry slice entry
|
|
2644
|
+
* @returns {string|undefined} text
|
|
2645
|
+
*/
|
|
2646
|
+
function sourceTextOf(entry) {
|
|
2647
|
+
return entry?.code || entry?.resolvedMethod;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2586
2650
|
export function detectServicesFromUsages(language, slice, servicesMap = {}) {
|
|
2587
2651
|
const usages = slice.usages;
|
|
2588
2652
|
if (!usages) {
|
|
@@ -2595,30 +2659,30 @@ export function detectServicesFromUsages(language, slice, servicesMap = {}) {
|
|
|
2595
2659
|
let authenticated;
|
|
2596
2660
|
if (language === "ruby" && definedBy?.name?.includes("/")) {
|
|
2597
2661
|
endpoints = extractEndpoints(language, definedBy.name);
|
|
2598
|
-
} else if (targetObj
|
|
2662
|
+
} else if (sourceTextOf(targetObj)) {
|
|
2599
2663
|
if (language !== "php") {
|
|
2600
|
-
endpoints = extractEndpoints(language, targetObj
|
|
2664
|
+
endpoints = extractEndpoints(language, sourceTextOf(targetObj));
|
|
2601
2665
|
}
|
|
2602
|
-
if (targetObj
|
|
2666
|
+
if (sourceTextOf(targetObj).toLowerCase().includes("auth")) {
|
|
2603
2667
|
authenticated = true;
|
|
2604
2668
|
}
|
|
2605
|
-
} else if (definedBy
|
|
2669
|
+
} else if (sourceTextOf(definedBy)) {
|
|
2606
2670
|
if (language !== "php") {
|
|
2607
|
-
endpoints = extractEndpoints(language, definedBy
|
|
2671
|
+
endpoints = extractEndpoints(language, sourceTextOf(definedBy));
|
|
2608
2672
|
}
|
|
2609
|
-
if (definedBy
|
|
2673
|
+
if (sourceTextOf(definedBy).toLowerCase().includes("auth")) {
|
|
2610
2674
|
authenticated = true;
|
|
2611
2675
|
}
|
|
2612
2676
|
}
|
|
2613
2677
|
if (usage.invokedCalls) {
|
|
2614
2678
|
for (const acall of usage.invokedCalls) {
|
|
2615
|
-
if (acall
|
|
2679
|
+
if (sourceTextOf(acall)) {
|
|
2616
2680
|
if (language !== "php") {
|
|
2617
2681
|
const tmpEndpoints = extractEndpoints(
|
|
2618
2682
|
language,
|
|
2619
|
-
acall
|
|
2683
|
+
sourceTextOf(acall),
|
|
2620
2684
|
);
|
|
2621
|
-
if (acall.
|
|
2685
|
+
if (sourceTextOf(acall).toLowerCase().includes("auth")) {
|
|
2622
2686
|
authenticated = true;
|
|
2623
2687
|
}
|
|
2624
2688
|
if (tmpEndpoints?.length) {
|
|
@@ -2824,6 +2888,72 @@ export function extractEndpoints(language, code) {
|
|
|
2824
2888
|
return endpoints;
|
|
2825
2889
|
}
|
|
2826
2890
|
|
|
2891
|
+
/**
|
|
2892
|
+
* Key naming the package a purl refers to, independent of its version,
|
|
2893
|
+
* qualifiers and subpath. Namespace and name are the decoded purl segments,
|
|
2894
|
+
* so an encoded scope (`%40angular/common`) keys like a plain one.
|
|
2895
|
+
*
|
|
2896
|
+
* @param {string} purl Purl string
|
|
2897
|
+
* @returns {string|undefined} `type/namespace/name`, or undefined for a non-purl
|
|
2898
|
+
*/
|
|
2899
|
+
export function versionlessPurlKey(purl) {
|
|
2900
|
+
const parts = splitPurlLoose(purl);
|
|
2901
|
+
if (!parts) {
|
|
2902
|
+
return undefined;
|
|
2903
|
+
}
|
|
2904
|
+
return `${parts.type}/${parts.namespace || ""}/${parts.name}`;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
/**
|
|
2908
|
+
* Index the data-flow frames recorded under versionless flow purls by
|
|
2909
|
+
* {@link versionlessPurlKey}. A flow purl without a version attaches to every
|
|
2910
|
+
* BOM component of the same package name, whatever its version (maintainer
|
|
2911
|
+
* decision, 2026-10-27); chen leaves the version off exactly when it cannot
|
|
2912
|
+
* pick one between apps that pin the package differently.
|
|
2913
|
+
*
|
|
2914
|
+
* @param {Object} dataFlowFrames Frames keyed by flow purl
|
|
2915
|
+
* @returns {Object} Frames keyed by versionless purl key
|
|
2916
|
+
*/
|
|
2917
|
+
export function indexVersionlessPurls(dataFlowFrames) {
|
|
2918
|
+
const index = {};
|
|
2919
|
+
for (const [purl, frames] of Object.entries(dataFlowFrames || {})) {
|
|
2920
|
+
if (splitPurlLoose(purl)?.version === null) {
|
|
2921
|
+
const key = versionlessPurlKey(purl);
|
|
2922
|
+
if (!key) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
index[key] ??= [];
|
|
2926
|
+
index[key].push(...frames);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return index;
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
/**
|
|
2933
|
+
* Index the crypto generate pools recorded under versionless flow purls by
|
|
2934
|
+
* {@link versionlessPurlKey}, the crypto counterpart of
|
|
2935
|
+
* {@link indexVersionlessPurls}.
|
|
2936
|
+
*
|
|
2937
|
+
* @param {Object} cryptoGeneratePurls Algorithm bom-refs keyed by flow purl
|
|
2938
|
+
* @returns {Object} Algorithm bom-ref sets keyed by versionless purl key
|
|
2939
|
+
*/
|
|
2940
|
+
export function indexVersionlessCrypto(cryptoGeneratePurls) {
|
|
2941
|
+
const index = {};
|
|
2942
|
+
for (const [purl, algos] of Object.entries(cryptoGeneratePurls || {})) {
|
|
2943
|
+
if (splitPurlLoose(purl)?.version === null) {
|
|
2944
|
+
const key = versionlessPurlKey(purl);
|
|
2945
|
+
if (!key) {
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2948
|
+
index[key] ??= new Set();
|
|
2949
|
+
for (const ref of algos || []) {
|
|
2950
|
+
index[key].add(ref);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
return index;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2827
2957
|
/**
|
|
2828
2958
|
* Method to create the SBOM with evidence file called evinse file.
|
|
2829
2959
|
*
|
|
@@ -2875,6 +3005,15 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2875
3005
|
let servicesPresent = false;
|
|
2876
3006
|
const propertyEvidencePresent =
|
|
2877
3007
|
metadataProperties.length || Object.keys(componentPropertiesMap).length;
|
|
3008
|
+
// Versionless flow purls attach to every same-name component (see
|
|
3009
|
+
// indexVersionlessPurls), so look both ways: exact purl first, then the
|
|
3010
|
+
// versionless index.
|
|
3011
|
+
const versionlessFrames = indexVersionlessPurls(dataFlowFrames);
|
|
3012
|
+
const versionlessCrypto = indexVersionlessCrypto(cryptoGeneratePurls);
|
|
3013
|
+
const depCryptoPurls =
|
|
3014
|
+
Object.keys(versionlessCrypto).length && cryptoGeneratePurls
|
|
3015
|
+
? { ...cryptoGeneratePurls }
|
|
3016
|
+
: cryptoGeneratePurls;
|
|
2878
3017
|
for (const comp of [bomJson.metadata?.component, ...components]) {
|
|
2879
3018
|
if (!comp?.purl) {
|
|
2880
3019
|
continue;
|
|
@@ -2895,7 +3034,12 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2895
3034
|
.map((l) => parseOccurrenceEvidenceLocation(l));
|
|
2896
3035
|
occEvidencePresent = true;
|
|
2897
3036
|
}
|
|
2898
|
-
const
|
|
3037
|
+
const compKey = versionlessPurlKey(comp.purl);
|
|
3038
|
+
let dfFrames = dataFlowFrames[comp.purl];
|
|
3039
|
+
const extraFrames = compKey ? versionlessFrames[compKey] : undefined;
|
|
3040
|
+
if (extraFrames?.length) {
|
|
3041
|
+
dfFrames = [...(dfFrames || []), ...extraFrames];
|
|
3042
|
+
}
|
|
2899
3043
|
if (dfFrames?.length) {
|
|
2900
3044
|
if (!comp.evidence) {
|
|
2901
3045
|
comp.evidence = {};
|
|
@@ -2909,10 +3053,20 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2909
3053
|
}
|
|
2910
3054
|
}
|
|
2911
3055
|
// Add crypto tags if this purl offers any generation algorithm
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3056
|
+
let compCryptoPurls = cryptoGeneratePurls?.[comp.purl];
|
|
3057
|
+
const extraCrypto = compKey ? versionlessCrypto[compKey] : undefined;
|
|
3058
|
+
if (extraCrypto?.size) {
|
|
3059
|
+
compCryptoPurls = new Set([...(compCryptoPurls || []), ...extraCrypto]);
|
|
3060
|
+
// The provides section below looks pool entries up by bom-ref, so the
|
|
3061
|
+
// merged set has to be reachable that way too.
|
|
3062
|
+
if (depCryptoPurls) {
|
|
3063
|
+
depCryptoPurls[comp.purl] = compCryptoPurls;
|
|
3064
|
+
if (comp["bom-ref"]) {
|
|
3065
|
+
depCryptoPurls[comp["bom-ref"]] = compCryptoPurls;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
if (compCryptoPurls && Array.from(compCryptoPurls).length) {
|
|
2916
3070
|
comp.tags = ["crypto", "crypto-generate"];
|
|
2917
3071
|
}
|
|
2918
3072
|
} // for
|
|
@@ -2944,14 +3098,14 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2944
3098
|
}
|
|
2945
3099
|
// Fix the dependencies section with provides information
|
|
2946
3100
|
if (
|
|
2947
|
-
|
|
2948
|
-
Object.keys(
|
|
3101
|
+
depCryptoPurls &&
|
|
3102
|
+
Object.keys(depCryptoPurls).length &&
|
|
2949
3103
|
bomJson.dependencies
|
|
2950
3104
|
) {
|
|
2951
3105
|
const newDependencies = [];
|
|
2952
3106
|
for (const depObj of bomJson.dependencies) {
|
|
2953
|
-
if (depObj.ref &&
|
|
2954
|
-
const providedAlgos = Array.from(
|
|
3107
|
+
if (depObj.ref && depCryptoPurls[depObj.ref]) {
|
|
3108
|
+
const providedAlgos = Array.from(depCryptoPurls[depObj.ref]);
|
|
2955
3109
|
if (providedAlgos.length) {
|
|
2956
3110
|
depObj.provides = providedAlgos;
|
|
2957
3111
|
}
|
|
@@ -3196,15 +3350,15 @@ export function collectReachableFrames(_language, reachablesSlice) {
|
|
|
3196
3350
|
if (tagStr.includes("crypto")) {
|
|
3197
3351
|
isCryptoFlow = true;
|
|
3198
3352
|
}
|
|
3353
|
+
// Join with a space: findCryptoAlgos treats every non-alphanumeric as a
|
|
3354
|
+
// word separator, and a space is the one separator cleanStr keeps.
|
|
3199
3355
|
if (isCryptoFlow && fnode.code) {
|
|
3200
|
-
codeSnippets = `${codeSnippets}
|
|
3356
|
+
codeSnippets = `${codeSnippets} ${fnode.code}`;
|
|
3201
3357
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
fnode.code.includes("sign")
|
|
3207
|
-
) {
|
|
3358
|
+
// Pool membership is tag-driven: a node contributes its pkg: tags only
|
|
3359
|
+
// when the tagger marked it crypto-generate. Code substring tests read
|
|
3360
|
+
// assign/design/signup into encrypt/decrypt/sign and are gone.
|
|
3361
|
+
if (tagStr.includes("crypto-generate")) {
|
|
3208
3362
|
cpurls = tagStr.split(", ").filter((t) => t.startsWith("pkg:"));
|
|
3209
3363
|
for (const cpurl of cpurls) {
|
|
3210
3364
|
if (!cryptoGeneratePurls[cpurl]) {
|
|
@@ -3407,6 +3561,155 @@ export function getClassTypeFromSignature(language, typeFullName) {
|
|
|
3407
3561
|
return simplifyType(typeFullName);
|
|
3408
3562
|
}
|
|
3409
3563
|
|
|
3564
|
+
const JS_LANGUAGES = ["javascript", "js", "ts", "typescript"];
|
|
3565
|
+
|
|
3566
|
+
/**
|
|
3567
|
+
* Add every location of a set to a purl's locations (Set.add takes a single value).
|
|
3568
|
+
*
|
|
3569
|
+
* @param {Set<string>} target purl location set
|
|
3570
|
+
* @param {Set<string>|undefined} locations locations to add
|
|
3571
|
+
*/
|
|
3572
|
+
function addAllLocations(target, locations) {
|
|
3573
|
+
for (const l of locations || []) {
|
|
3574
|
+
target.add(l);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
/**
|
|
3579
|
+
* npm package name of a JavaScript module specifier: `@scope/pkg/sub` -> `@scope/pkg`,
|
|
3580
|
+
* `lodash/fp` -> `lodash`. Node.js builtins (`node:fs`, `fs`) are not packages.
|
|
3581
|
+
*
|
|
3582
|
+
* @param {string} module module specifier
|
|
3583
|
+
* @returns {string|undefined} package name
|
|
3584
|
+
*/
|
|
3585
|
+
export function jsPackageOfModule(module) {
|
|
3586
|
+
if (!module || module.startsWith("node:") || module.startsWith(".")) {
|
|
3587
|
+
return undefined;
|
|
3588
|
+
}
|
|
3589
|
+
const parts = module.split("/");
|
|
3590
|
+
const pkg = module.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
3591
|
+
if (isNodeBuiltin(pkg)) {
|
|
3592
|
+
return undefined;
|
|
3593
|
+
}
|
|
3594
|
+
return pkg;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
function isNodeBuiltin(name) {
|
|
3598
|
+
return NODE_BUILTINS.has(name);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
const NODE_BUILTINS = new Set(builtinModules);
|
|
3602
|
+
|
|
3603
|
+
const jsPurlsByPackageCache = new WeakMap();
|
|
3604
|
+
|
|
3605
|
+
function jsPurlsByPackage(purlImportsMap) {
|
|
3606
|
+
let byPackage = jsPurlsByPackageCache.get(purlImportsMap);
|
|
3607
|
+
if (!byPackage) {
|
|
3608
|
+
byPackage = {};
|
|
3609
|
+
for (const apurl of Object.keys(purlImportsMap || {})) {
|
|
3610
|
+
try {
|
|
3611
|
+
const purlObj = Purl.parse(apurl);
|
|
3612
|
+
if (purlObj.type !== "npm") {
|
|
3613
|
+
continue;
|
|
3614
|
+
}
|
|
3615
|
+
const name = purlObj.namespace
|
|
3616
|
+
? `${purlObj.namespace}/${purlObj.name}`
|
|
3617
|
+
: purlObj.name;
|
|
3618
|
+
(byPackage[name] = byPackage[name] || []).push(apurl);
|
|
3619
|
+
} catch (_e) {
|
|
3620
|
+
// not a valid purl
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
jsPurlsByPackageCache.set(purlImportsMap, byPackage);
|
|
3624
|
+
}
|
|
3625
|
+
return byPackage;
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
/**
|
|
3629
|
+
* Attribute a JavaScript usage's module-qualified entries to the packages providing them.
|
|
3630
|
+
*
|
|
3631
|
+
* Each call entry with a `module` is a use of that module's package at the call's line. When
|
|
3632
|
+
* the slice's object is an import binding (defined by a require/import naming the module),
|
|
3633
|
+
* every call the object takes part in is a use of that package too.
|
|
3634
|
+
*
|
|
3635
|
+
* @param {Object} ausage usage entry of an object slice
|
|
3636
|
+
* @param {string} fileName file of the slice
|
|
3637
|
+
* @param {Object} purlImportsMap purl -> imported modules and symbols
|
|
3638
|
+
* @param {Object} purlLocationMap purl -> Set of `file#line`
|
|
3639
|
+
*/
|
|
3640
|
+
export function attributeJsModules(
|
|
3641
|
+
ausage,
|
|
3642
|
+
fileName,
|
|
3643
|
+
purlImportsMap,
|
|
3644
|
+
purlLocationMap,
|
|
3645
|
+
) {
|
|
3646
|
+
const byPackage = jsPurlsByPackage(purlImportsMap);
|
|
3647
|
+
const record = (module, line) => {
|
|
3648
|
+
const pkg = jsPackageOfModule(module);
|
|
3649
|
+
if (!pkg || !line) {
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
for (const apurl of byPackage[pkg] || []) {
|
|
3653
|
+
if (!purlLocationMap[apurl]) {
|
|
3654
|
+
purlLocationMap[apurl] = new Set();
|
|
3655
|
+
}
|
|
3656
|
+
purlLocationMap[apurl].add(`${fileName}#${line}`);
|
|
3657
|
+
}
|
|
3658
|
+
};
|
|
3659
|
+
const calls = []
|
|
3660
|
+
.concat(ausage?.invokedCalls || [])
|
|
3661
|
+
.concat(ausage?.argToCalls || []);
|
|
3662
|
+
for (const c of [ausage?.targetObj, ausage?.definedBy, ...calls]) {
|
|
3663
|
+
if (c?.module) {
|
|
3664
|
+
record(c.module, c.lineNumber);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
// Only an import binding stands for its module: a value merely returned by a package's call
|
|
3668
|
+
// (`const s = purl.toString()`) is not a use of that package wherever it goes.
|
|
3669
|
+
const definedBy = ausage?.definedBy;
|
|
3670
|
+
const isImportBinding =
|
|
3671
|
+
definedBy?.name === "require" ||
|
|
3672
|
+
definedBy?.name === "import" ||
|
|
3673
|
+
/^(require|import)\(/.test(definedBy?.code || "");
|
|
3674
|
+
const bindingModule = isImportBinding ? definedBy?.module : undefined;
|
|
3675
|
+
if (bindingModule) {
|
|
3676
|
+
for (const c of calls) {
|
|
3677
|
+
record(bindingModule, c?.lineNumber);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
/**
|
|
3683
|
+
* Attribute a callsites slice's records to the packages providing their modules.
|
|
3684
|
+
*
|
|
3685
|
+
* Each record of `{"callsites": [...]}` (atom `callsites` command) is one external use site
|
|
3686
|
+
* carrying its own file and line; the record is an occurrence of every purl whose package
|
|
3687
|
+
* matches the record's module.
|
|
3688
|
+
*
|
|
3689
|
+
* @param {Object} callsitesSlice callsites slice with a `callsites` array
|
|
3690
|
+
* @param {Object} purlImportsMap purl -> imported modules and symbols
|
|
3691
|
+
* @param {Object} purlLocationMap purl -> Set of `file#line`
|
|
3692
|
+
*/
|
|
3693
|
+
export function attributeJsCallsites(
|
|
3694
|
+
callsitesSlice,
|
|
3695
|
+
purlImportsMap,
|
|
3696
|
+
purlLocationMap,
|
|
3697
|
+
) {
|
|
3698
|
+
const byPackage = jsPurlsByPackage(purlImportsMap);
|
|
3699
|
+
for (const record of callsitesSlice?.callsites || []) {
|
|
3700
|
+
const pkg = jsPackageOfModule(record?.module);
|
|
3701
|
+
if (!pkg || !(record.line > 0)) {
|
|
3702
|
+
continue;
|
|
3703
|
+
}
|
|
3704
|
+
for (const apurl of byPackage[pkg] || []) {
|
|
3705
|
+
if (!purlLocationMap[apurl]) {
|
|
3706
|
+
purlLocationMap[apurl] = new Set();
|
|
3707
|
+
}
|
|
3708
|
+
purlLocationMap[apurl].add(`${record.file}#${record.line}`);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3410
3713
|
function addToOverrides(lKeyOverrides, atype, fileName, ausageLineNumber) {
|
|
3411
3714
|
if (!lKeyOverrides[atype]) {
|
|
3412
3715
|
lKeyOverrides[atype] = new Set();
|