@cyclonedx/cdxgen 12.3.2 → 12.4.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 +70 -22
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +238 -116
- package/bin/convert.js +28 -13
- package/bin/hbom.js +490 -0
- package/bin/repl.js +580 -29
- package/bin/validate.js +34 -4
- package/bin/verify.js +40 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/queries-darwin.json +12 -1
- package/data/queries-win.json +7 -1
- package/data/queries.json +39 -2
- package/data/rules/ai-agent-governance.yaml +16 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/chrome-extensions.yaml +8 -0
- package/data/rules/ci-permissions.yaml +171 -15
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +76 -5
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +18 -3
- package/data/rules/obom-runtime.yaml +907 -22
- package/data/rules/package-integrity.yaml +36 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +9 -0
- package/lib/audit/index.js +209 -8
- package/lib/audit/index.poku.js +332 -0
- package/lib/audit/reporters.js +222 -0
- package/lib/audit/targets.js +146 -1
- package/lib/audit/targets.poku.js +186 -0
- package/lib/cli/asar.poku.js +328 -0
- package/lib/cli/index.js +647 -127
- package/lib/cli/index.poku.js +1905 -187
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/agentFormulationParser.js +6 -2
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/analyzer.js +1444 -38
- package/lib/helpers/analyzer.poku.js +409 -0
- package/lib/helpers/analyzerScope.js +712 -0
- package/lib/helpers/asarutils.js +1556 -0
- package/lib/helpers/asarutils.poku.js +443 -0
- package/lib/helpers/auditCategories.js +12 -0
- package/lib/helpers/auditCategories.poku.js +32 -0
- package/lib/helpers/cbomutils.js +271 -1
- package/lib/helpers/cbomutils.poku.js +248 -5
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +336 -23
- package/lib/helpers/display.poku.js +179 -43
- package/lib/helpers/evidenceUtils.js +58 -0
- package/lib/helpers/evidenceUtils.poku.js +54 -0
- package/lib/helpers/exportUtils.js +9 -0
- package/lib/helpers/gtfobins.js +142 -8
- package/lib/helpers/gtfobins.poku.js +24 -1
- package/lib/helpers/hbom.js +710 -0
- package/lib/helpers/hbom.poku.js +496 -0
- package/lib/helpers/hbomAnalysis.js +268 -0
- package/lib/helpers/hbomAnalysis.poku.js +249 -0
- package/lib/helpers/hbomLoader.js +35 -0
- package/lib/helpers/hostTopology.js +803 -0
- package/lib/helpers/hostTopology.poku.js +363 -0
- package/lib/helpers/inventoryStats.js +69 -0
- package/lib/helpers/inventoryStats.poku.js +86 -0
- package/lib/helpers/lolbas.js +19 -1
- package/lib/helpers/lolbas.poku.js +23 -0
- package/lib/helpers/mcpConfigParser.js +21 -5
- package/lib/helpers/mcpConfigParser.poku.js +39 -2
- package/lib/helpers/osqueryTransform.js +47 -0
- package/lib/helpers/osqueryTransform.poku.js +47 -0
- package/lib/helpers/plugins.js +349 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/protobom.js +156 -45
- package/lib/helpers/protobom.poku.js +140 -5
- package/lib/helpers/remote/dependency-track.js +36 -3
- package/lib/helpers/remote/dependency-track.poku.js +44 -0
- package/lib/helpers/source.js +24 -0
- package/lib/helpers/source.poku.js +32 -0
- package/lib/helpers/utils.js +2454 -198
- package/lib/helpers/utils.poku.js +1798 -74
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2306 -350
- package/lib/managers/binary.poku.js +1700 -1
- package/lib/managers/docker.js +441 -95
- package/lib/managers/docker.poku.js +1479 -14
- package/lib/server/server.js +2 -24
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/annotator.js +38 -0
- package/lib/stages/postgen/annotator.poku.js +107 -1
- package/lib/stages/postgen/auditBom.js +121 -18
- package/lib/stages/postgen/auditBom.poku.js +2967 -990
- package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
- package/lib/stages/postgen/postgen.js +192 -1
- package/lib/stages/postgen/postgen.poku.js +321 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/package.json +24 -21
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +44 -0
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/reporters.d.ts +16 -0
- package/types/lib/audit/reporters.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +16 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +4 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +33 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/analyzerScope.d.ts +11 -0
- package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
- package/types/lib/helpers/asarutils.d.ts +34 -0
- package/types/lib/helpers/asarutils.d.ts.map +1 -0
- package/types/lib/helpers/auditCategories.d.ts +5 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +3 -2
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/evidenceUtils.d.ts +8 -0
- package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +8 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -1
- package/types/lib/helpers/hbom.d.ts +49 -0
- package/types/lib/helpers/hbom.d.ts.map +1 -0
- package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
- package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/helpers/hbomLoader.d.ts +7 -0
- package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
- package/types/lib/helpers/hostTopology.d.ts +12 -0
- package/types/lib/helpers/hostTopology.d.ts.map +1 -0
- package/types/lib/helpers/inventoryStats.d.ts +11 -0
- package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts +1 -1
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/helpers/osqueryTransform.d.ts +3 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
- package/types/lib/helpers/plugins.d.ts +58 -0
- package/types/lib/helpers/plugins.d.ts.map +1 -0
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/protobom.d.ts +3 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/helpers/source.d.ts.map +1 -1
- package/types/lib/helpers/utils.d.ts +74 -8
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +5 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +2 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +26 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +2 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/data/spdx-model-v3.0.1.jsonld +0 -15999
package/lib/helpers/analyzer.js
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
basename,
|
|
4
|
+
isAbsolute,
|
|
5
|
+
join,
|
|
6
|
+
matchesGlob,
|
|
7
|
+
relative,
|
|
8
|
+
resolve,
|
|
9
|
+
} from "node:path";
|
|
3
10
|
import process from "node:process";
|
|
4
11
|
import { URL } from "node:url";
|
|
5
12
|
|
|
6
13
|
import { parse } from "@babel/parser";
|
|
7
14
|
import traverse from "@babel/traverse";
|
|
8
15
|
|
|
16
|
+
import {
|
|
17
|
+
getScopedStaticValueByName,
|
|
18
|
+
getStaticObjectProperty,
|
|
19
|
+
resolveStaticValue,
|
|
20
|
+
} from "./analyzerScope.js";
|
|
9
21
|
import { classifyMcpReference } from "./mcp.js";
|
|
10
22
|
import { isLocalHost, sanitizeMcpRefToken } from "./mcpDiscovery.js";
|
|
23
|
+
import {
|
|
24
|
+
sanitizeBomPropertyValue,
|
|
25
|
+
sanitizeBomUrl,
|
|
26
|
+
} from "./propertySanitizer.js";
|
|
11
27
|
|
|
12
28
|
const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
|
|
13
29
|
? process.env.ASTGEN_IGNORE_DIRS.split(",")
|
|
@@ -40,10 +56,74 @@ const IGNORE_FILE_PATTERN = new RegExp(
|
|
|
40
56
|
"i",
|
|
41
57
|
);
|
|
42
58
|
|
|
43
|
-
const
|
|
59
|
+
const normalizeAnalyzerPathForGlob = (filePath) =>
|
|
60
|
+
String(filePath || "").replaceAll("\\", "/");
|
|
61
|
+
|
|
62
|
+
const normalizeAnalyzerSearchOptions = (deepOrOptions = false) => {
|
|
63
|
+
if (deepOrOptions && typeof deepOrOptions === "object") {
|
|
64
|
+
return {
|
|
65
|
+
deep: Boolean(deepOrOptions.deep),
|
|
66
|
+
exclude: Array.isArray(deepOrOptions.exclude)
|
|
67
|
+
? deepOrOptions.exclude
|
|
68
|
+
: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
deep: Boolean(deepOrOptions),
|
|
73
|
+
exclude: [],
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const shouldExcludeAnalyzerPath = (
|
|
78
|
+
rootDir,
|
|
79
|
+
filePath,
|
|
80
|
+
excludePatterns,
|
|
81
|
+
isDirectory = false,
|
|
82
|
+
) => {
|
|
83
|
+
if (!excludePatterns?.length) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const normalizedAbsolutePath = normalizeAnalyzerPathForGlob(
|
|
87
|
+
resolve(filePath),
|
|
88
|
+
);
|
|
89
|
+
const normalizedRelativePath = normalizeAnalyzerPathForGlob(
|
|
90
|
+
relative(rootDir, filePath),
|
|
91
|
+
);
|
|
92
|
+
const candidatePaths = [
|
|
93
|
+
normalizedAbsolutePath,
|
|
94
|
+
normalizedRelativePath,
|
|
95
|
+
normalizedRelativePath ? `./${normalizedRelativePath}` : "",
|
|
96
|
+
].filter(Boolean);
|
|
97
|
+
if (isDirectory) {
|
|
98
|
+
candidatePaths.push(
|
|
99
|
+
`${normalizedAbsolutePath}/`,
|
|
100
|
+
normalizedRelativePath ? `${normalizedRelativePath}/` : "",
|
|
101
|
+
normalizedRelativePath ? `./${normalizedRelativePath}/` : "",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return excludePatterns.some((pattern) => {
|
|
105
|
+
const normalizedPattern = normalizeAnalyzerPathForGlob(pattern);
|
|
106
|
+
return candidatePaths.some((candidatePath) =>
|
|
107
|
+
matchesGlob(candidatePath, normalizedPattern),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getAllFiles = (
|
|
113
|
+
deep,
|
|
114
|
+
dir,
|
|
115
|
+
extn,
|
|
116
|
+
files,
|
|
117
|
+
result,
|
|
118
|
+
regex,
|
|
119
|
+
rootDir,
|
|
120
|
+
excludePatterns,
|
|
121
|
+
) => {
|
|
44
122
|
files = files || readdirSync(dir);
|
|
45
123
|
result = result || [];
|
|
46
124
|
regex = regex || new RegExp(`\\${extn}$`);
|
|
125
|
+
rootDir = rootDir || dir;
|
|
126
|
+
excludePatterns = excludePatterns || [];
|
|
47
127
|
|
|
48
128
|
for (let i = 0; i < files.length; i++) {
|
|
49
129
|
if (IGNORE_FILE_PATTERN.test(files[i]) || files[i].startsWith(".")) {
|
|
@@ -54,6 +134,16 @@ const getAllFiles = (deep, dir, extn, files, result, regex) => {
|
|
|
54
134
|
if (fileStat.isSymbolicLink()) {
|
|
55
135
|
continue;
|
|
56
136
|
}
|
|
137
|
+
if (
|
|
138
|
+
shouldExcludeAnalyzerPath(
|
|
139
|
+
rootDir,
|
|
140
|
+
file,
|
|
141
|
+
excludePatterns,
|
|
142
|
+
fileStat.isDirectory(),
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
57
147
|
if (fileStat.isDirectory()) {
|
|
58
148
|
// Ignore directories
|
|
59
149
|
const dirName = basename(file);
|
|
@@ -77,6 +167,8 @@ const getAllFiles = (deep, dir, extn, files, result, regex) => {
|
|
|
77
167
|
readdirSync(file),
|
|
78
168
|
result,
|
|
79
169
|
regex,
|
|
170
|
+
rootDir,
|
|
171
|
+
excludePatterns,
|
|
80
172
|
);
|
|
81
173
|
} catch (_error) {
|
|
82
174
|
// ignore
|
|
@@ -882,16 +974,23 @@ const parseFileASTTree = (src, file, allImports, allExports) => {
|
|
|
882
974
|
* Return paths to all (j|tsx?) files.
|
|
883
975
|
*/
|
|
884
976
|
const getAllSrcJSAndTSFiles = (src, deep) =>
|
|
885
|
-
Promise.all(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
977
|
+
Promise.all(
|
|
978
|
+
[".js", ".jsx", ".cjs", ".mjs", ".ts", ".tsx", ".vue", ".svelte"].map(
|
|
979
|
+
(extension) => {
|
|
980
|
+
const searchOptions = normalizeAnalyzerSearchOptions(deep);
|
|
981
|
+
return getAllFiles(
|
|
982
|
+
searchOptions.deep,
|
|
983
|
+
src,
|
|
984
|
+
extension,
|
|
985
|
+
undefined,
|
|
986
|
+
undefined,
|
|
987
|
+
undefined,
|
|
988
|
+
src,
|
|
989
|
+
searchOptions.exclude,
|
|
990
|
+
);
|
|
991
|
+
},
|
|
992
|
+
),
|
|
993
|
+
);
|
|
895
994
|
|
|
896
995
|
export const CHROMIUM_EXTENSION_CAPABILITY_CATEGORIES = [
|
|
897
996
|
"fileAccess",
|
|
@@ -960,6 +1059,113 @@ const SUSPICIOUS_JS_NETWORK_MODULES = new Set([
|
|
|
960
1059
|
"undici",
|
|
961
1060
|
]);
|
|
962
1061
|
|
|
1062
|
+
const JS_FILE_ACCESS_MODULES = new Set([
|
|
1063
|
+
"fs",
|
|
1064
|
+
"fs/promises",
|
|
1065
|
+
"graceful-fs",
|
|
1066
|
+
"node:fs",
|
|
1067
|
+
"node:fs/promises",
|
|
1068
|
+
"original-fs",
|
|
1069
|
+
]);
|
|
1070
|
+
const JS_NETWORK_MODULES = new Set([
|
|
1071
|
+
...SUSPICIOUS_JS_NETWORK_MODULES,
|
|
1072
|
+
"engine.io-client",
|
|
1073
|
+
"node:dgram",
|
|
1074
|
+
"socket.io-client",
|
|
1075
|
+
"sse.js",
|
|
1076
|
+
"ws",
|
|
1077
|
+
]);
|
|
1078
|
+
const JS_HARDWARE_MODULES = new Set([
|
|
1079
|
+
"@abandonware/noble",
|
|
1080
|
+
"bluetooth-serial-port",
|
|
1081
|
+
"electron-hid",
|
|
1082
|
+
"i2c-bus",
|
|
1083
|
+
"node-hid",
|
|
1084
|
+
"noble",
|
|
1085
|
+
"onoff",
|
|
1086
|
+
"pigpio",
|
|
1087
|
+
"raspi-io",
|
|
1088
|
+
"serialport",
|
|
1089
|
+
"spi-device",
|
|
1090
|
+
"usb",
|
|
1091
|
+
"webbluetooth",
|
|
1092
|
+
]);
|
|
1093
|
+
const JS_FILE_ACCESS_MEMBERS = new Set([
|
|
1094
|
+
"access",
|
|
1095
|
+
"appendFile",
|
|
1096
|
+
"chmod",
|
|
1097
|
+
"chown",
|
|
1098
|
+
"copyFile",
|
|
1099
|
+
"cp",
|
|
1100
|
+
"createReadStream",
|
|
1101
|
+
"createWriteStream",
|
|
1102
|
+
"lstat",
|
|
1103
|
+
"mkdir",
|
|
1104
|
+
"mkdtemp",
|
|
1105
|
+
"open",
|
|
1106
|
+
"opendir",
|
|
1107
|
+
"readFile",
|
|
1108
|
+
"readdir",
|
|
1109
|
+
"readlink",
|
|
1110
|
+
"realpath",
|
|
1111
|
+
"rename",
|
|
1112
|
+
"rm",
|
|
1113
|
+
"rmdir",
|
|
1114
|
+
"stat",
|
|
1115
|
+
"symlink",
|
|
1116
|
+
"truncate",
|
|
1117
|
+
"unlink",
|
|
1118
|
+
"utimes",
|
|
1119
|
+
"watch",
|
|
1120
|
+
"watchFile",
|
|
1121
|
+
"writeFile",
|
|
1122
|
+
]);
|
|
1123
|
+
const JS_NETWORK_MEMBERS = new Set([
|
|
1124
|
+
"connect",
|
|
1125
|
+
"createConnection",
|
|
1126
|
+
"createSocket",
|
|
1127
|
+
"fetch",
|
|
1128
|
+
"get",
|
|
1129
|
+
"patch",
|
|
1130
|
+
"post",
|
|
1131
|
+
"put",
|
|
1132
|
+
"request",
|
|
1133
|
+
"send",
|
|
1134
|
+
"subscribe",
|
|
1135
|
+
]);
|
|
1136
|
+
const JS_HARDWARE_MEMBERS = new Set([
|
|
1137
|
+
"getDevices",
|
|
1138
|
+
"open",
|
|
1139
|
+
"requestDevice",
|
|
1140
|
+
"requestPort",
|
|
1141
|
+
]);
|
|
1142
|
+
const JS_CODE_GENERATION_MEMBERS = new Set([
|
|
1143
|
+
"compileFunction",
|
|
1144
|
+
"runInContext",
|
|
1145
|
+
"runInNewContext",
|
|
1146
|
+
"runInThisContext",
|
|
1147
|
+
]);
|
|
1148
|
+
const JS_HARDWARE_CHAIN_PATTERNS = [
|
|
1149
|
+
/^navigator\.(bluetooth|hid|serial|usb)\b/i,
|
|
1150
|
+
/^(chrome|browser)\.(bluetooth|hid|serial|usb|nfc)\b/i,
|
|
1151
|
+
];
|
|
1152
|
+
const JS_FILE_ACCESS_CHAIN_PATTERNS = [
|
|
1153
|
+
/^(window\.)?show(Open|Save|Directory)FilePicker$/i,
|
|
1154
|
+
];
|
|
1155
|
+
const JS_NETWORK_CHAIN_PATTERNS = [
|
|
1156
|
+
/^navigator\.sendBeacon$/i,
|
|
1157
|
+
/^(window\.)?(EventSource|WebSocket|XMLHttpRequest)$/i,
|
|
1158
|
+
];
|
|
1159
|
+
export const JS_CAPABILITY_CATEGORIES = [
|
|
1160
|
+
"fileAccess",
|
|
1161
|
+
"network",
|
|
1162
|
+
"hardware",
|
|
1163
|
+
"childProcess",
|
|
1164
|
+
"codeGeneration",
|
|
1165
|
+
"dynamicFetch",
|
|
1166
|
+
"dynamicImport",
|
|
1167
|
+
];
|
|
1168
|
+
|
|
963
1169
|
const SUSPICIOUS_JS_EXECUTION_MEMBERS = new Set([
|
|
964
1170
|
"exec",
|
|
965
1171
|
"execFile",
|
|
@@ -1028,6 +1234,57 @@ const trackSuspiciousModuleReference = (
|
|
|
1028
1234
|
}
|
|
1029
1235
|
};
|
|
1030
1236
|
|
|
1237
|
+
const trackJsCapabilityModuleReference = (
|
|
1238
|
+
moduleName,
|
|
1239
|
+
localName,
|
|
1240
|
+
capabilityIndicators,
|
|
1241
|
+
aliasMaps,
|
|
1242
|
+
) => {
|
|
1243
|
+
if (!moduleName || typeof moduleName !== "string") {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (JS_FILE_ACCESS_MODULES.has(moduleName)) {
|
|
1247
|
+
capabilityIndicators.fileAccess.add(`import:${moduleName}`);
|
|
1248
|
+
if (localName) {
|
|
1249
|
+
aliasMaps.fileAccess.add(localName);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (JS_NETWORK_MODULES.has(moduleName)) {
|
|
1253
|
+
capabilityIndicators.network.add(`import:${moduleName}`);
|
|
1254
|
+
if (localName) {
|
|
1255
|
+
aliasMaps.network.add(localName);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (JS_HARDWARE_MODULES.has(moduleName)) {
|
|
1259
|
+
capabilityIndicators.hardware.add(`import:${moduleName}`);
|
|
1260
|
+
if (localName) {
|
|
1261
|
+
aliasMaps.hardware.add(localName);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if (SUSPICIOUS_JS_PROCESS_MODULES.has(moduleName)) {
|
|
1265
|
+
capabilityIndicators.childProcess.add(`import:${moduleName}`);
|
|
1266
|
+
if (localName) {
|
|
1267
|
+
aliasMaps.childProcess.add(localName);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
const isStaticStringNode = (node) =>
|
|
1273
|
+
node?.type === "StringLiteral" ||
|
|
1274
|
+
(node?.type === "TemplateLiteral" && node.expressions?.length === 0);
|
|
1275
|
+
|
|
1276
|
+
const isStaticUrlNode = (node) => {
|
|
1277
|
+
if (isStaticStringNode(node)) {
|
|
1278
|
+
return true;
|
|
1279
|
+
}
|
|
1280
|
+
return (
|
|
1281
|
+
node?.type === "NewExpression" &&
|
|
1282
|
+
getMemberChainString(node.callee) === "URL" &&
|
|
1283
|
+
node.arguments?.length &&
|
|
1284
|
+
node.arguments.every((arg) => isStaticStringNode(arg))
|
|
1285
|
+
);
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1031
1288
|
const getMemberChainString = (node) => {
|
|
1032
1289
|
if (!node) {
|
|
1033
1290
|
return "";
|
|
@@ -1066,7 +1323,7 @@ const getMemberChainString = (node) => {
|
|
|
1066
1323
|
return objectChain || propertyChain || "";
|
|
1067
1324
|
};
|
|
1068
1325
|
|
|
1069
|
-
function analyzeSuspiciousJsSource(source) {
|
|
1326
|
+
export function analyzeSuspiciousJsSource(source) {
|
|
1070
1327
|
const executionIndicators = new Set();
|
|
1071
1328
|
const networkIndicators = new Set();
|
|
1072
1329
|
const obfuscationIndicators = new Set();
|
|
@@ -1265,6 +1522,984 @@ export const analyzeSuspiciousJsFile = (filePath) => {
|
|
|
1265
1522
|
return analyzeSuspiciousJsSource(source);
|
|
1266
1523
|
};
|
|
1267
1524
|
|
|
1525
|
+
export function analyzeJsCapabilitiesSource(source) {
|
|
1526
|
+
const capabilityIndicators = {
|
|
1527
|
+
childProcess: new Set(),
|
|
1528
|
+
codeGeneration: new Set(),
|
|
1529
|
+
dynamicFetch: new Set(),
|
|
1530
|
+
dynamicImport: new Set(),
|
|
1531
|
+
fileAccess: new Set(),
|
|
1532
|
+
hardware: new Set(),
|
|
1533
|
+
network: new Set(),
|
|
1534
|
+
};
|
|
1535
|
+
const aliasMaps = {
|
|
1536
|
+
childProcess: new Set(),
|
|
1537
|
+
fileAccess: new Set(),
|
|
1538
|
+
hardware: new Set(),
|
|
1539
|
+
network: new Set(),
|
|
1540
|
+
};
|
|
1541
|
+
let ast;
|
|
1542
|
+
try {
|
|
1543
|
+
ast = parse(source, babelParserOptions);
|
|
1544
|
+
} catch {
|
|
1545
|
+
return {
|
|
1546
|
+
capabilities: [],
|
|
1547
|
+
hasDynamicFetch: false,
|
|
1548
|
+
hasDynamicImport: false,
|
|
1549
|
+
hasEval: false,
|
|
1550
|
+
indicatorMap: {},
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
const addIndicator = (category, rawIndicator) => {
|
|
1554
|
+
const indicator = String(rawIndicator || "").trim();
|
|
1555
|
+
if (!indicator) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
capabilityIndicators[category].add(indicator);
|
|
1559
|
+
};
|
|
1560
|
+
traverse.default(ast, {
|
|
1561
|
+
ImportDeclaration: (path) => {
|
|
1562
|
+
const moduleName = getLiteralStringValue(path?.node?.source);
|
|
1563
|
+
path.node.specifiers.forEach((specifier) => {
|
|
1564
|
+
trackJsCapabilityModuleReference(
|
|
1565
|
+
moduleName,
|
|
1566
|
+
specifier?.local?.name,
|
|
1567
|
+
capabilityIndicators,
|
|
1568
|
+
aliasMaps,
|
|
1569
|
+
);
|
|
1570
|
+
});
|
|
1571
|
+
if (!path.node.specifiers?.length) {
|
|
1572
|
+
trackJsCapabilityModuleReference(
|
|
1573
|
+
moduleName,
|
|
1574
|
+
undefined,
|
|
1575
|
+
capabilityIndicators,
|
|
1576
|
+
aliasMaps,
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
},
|
|
1580
|
+
VariableDeclarator: (path) => {
|
|
1581
|
+
const init = path?.node?.init;
|
|
1582
|
+
if (
|
|
1583
|
+
init?.type === "CallExpression" &&
|
|
1584
|
+
init.callee?.type === "Identifier" &&
|
|
1585
|
+
init.callee.name === "require"
|
|
1586
|
+
) {
|
|
1587
|
+
const moduleName = getLiteralStringValue(init.arguments?.[0]);
|
|
1588
|
+
const localName =
|
|
1589
|
+
path?.node?.id?.type === "Identifier" ? path.node.id.name : undefined;
|
|
1590
|
+
trackJsCapabilityModuleReference(
|
|
1591
|
+
moduleName,
|
|
1592
|
+
localName,
|
|
1593
|
+
capabilityIndicators,
|
|
1594
|
+
aliasMaps,
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
},
|
|
1598
|
+
ImportExpression: (path) => {
|
|
1599
|
+
if (!isStaticStringNode(path?.node?.source)) {
|
|
1600
|
+
addIndicator("dynamicImport", "import(dynamic)");
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
MemberExpression: (path) => {
|
|
1604
|
+
const memberChain = getMemberChainString(path?.node);
|
|
1605
|
+
if (
|
|
1606
|
+
JS_HARDWARE_CHAIN_PATTERNS.some((pattern) => pattern.test(memberChain))
|
|
1607
|
+
) {
|
|
1608
|
+
addIndicator("hardware", memberChain);
|
|
1609
|
+
}
|
|
1610
|
+
if (
|
|
1611
|
+
JS_FILE_ACCESS_CHAIN_PATTERNS.some((pattern) =>
|
|
1612
|
+
pattern.test(memberChain),
|
|
1613
|
+
)
|
|
1614
|
+
) {
|
|
1615
|
+
addIndicator("fileAccess", memberChain);
|
|
1616
|
+
}
|
|
1617
|
+
if (
|
|
1618
|
+
JS_NETWORK_CHAIN_PATTERNS.some((pattern) => pattern.test(memberChain))
|
|
1619
|
+
) {
|
|
1620
|
+
addIndicator("network", memberChain);
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1623
|
+
OptionalMemberExpression: (path) => {
|
|
1624
|
+
const memberChain = getMemberChainString(path?.node);
|
|
1625
|
+
if (
|
|
1626
|
+
JS_HARDWARE_CHAIN_PATTERNS.some((pattern) => pattern.test(memberChain))
|
|
1627
|
+
) {
|
|
1628
|
+
addIndicator("hardware", memberChain);
|
|
1629
|
+
}
|
|
1630
|
+
if (
|
|
1631
|
+
JS_FILE_ACCESS_CHAIN_PATTERNS.some((pattern) =>
|
|
1632
|
+
pattern.test(memberChain),
|
|
1633
|
+
)
|
|
1634
|
+
) {
|
|
1635
|
+
addIndicator("fileAccess", memberChain);
|
|
1636
|
+
}
|
|
1637
|
+
if (
|
|
1638
|
+
JS_NETWORK_CHAIN_PATTERNS.some((pattern) => pattern.test(memberChain))
|
|
1639
|
+
) {
|
|
1640
|
+
addIndicator("network", memberChain);
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
CallExpression: (path) => {
|
|
1644
|
+
const callee = path?.node?.callee;
|
|
1645
|
+
const calleeChain = getMemberChainString(callee);
|
|
1646
|
+
if (callee?.type === "Identifier") {
|
|
1647
|
+
if (callee.name === "fetch") {
|
|
1648
|
+
addIndicator("network", "fetch");
|
|
1649
|
+
if (!isStaticUrlNode(path.node.arguments?.[0])) {
|
|
1650
|
+
addIndicator("dynamicFetch", "fetch(dynamic)");
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (callee.name === "eval") {
|
|
1654
|
+
addIndicator("codeGeneration", "eval");
|
|
1655
|
+
}
|
|
1656
|
+
if (
|
|
1657
|
+
aliasMaps.network.has(callee.name) &&
|
|
1658
|
+
["axios", "got", "fetch"].includes(callee.name)
|
|
1659
|
+
) {
|
|
1660
|
+
addIndicator("network", callee.name);
|
|
1661
|
+
if (!isStaticUrlNode(path.node.arguments?.[0])) {
|
|
1662
|
+
addIndicator("dynamicFetch", `${callee.name}(dynamic)`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (calleeChain === "Buffer.from") {
|
|
1667
|
+
const encodingValue = getLiteralStringValue(path.node.arguments?.[1]);
|
|
1668
|
+
if (encodingValue?.toLowerCase() === "base64") {
|
|
1669
|
+
addIndicator("codeGeneration", "buffer-base64");
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
if (calleeChain.startsWith("vm.")) {
|
|
1673
|
+
const vmMethod = calleeChain.split(".").slice(1).join(".");
|
|
1674
|
+
if (JS_CODE_GENERATION_MEMBERS.has(vmMethod)) {
|
|
1675
|
+
addIndicator("codeGeneration", calleeChain);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (callee?.type === "MemberExpression") {
|
|
1679
|
+
const objectName = getMemberChainString(callee.object);
|
|
1680
|
+
const propertyName = getMemberChainString(callee.property);
|
|
1681
|
+
if (
|
|
1682
|
+
objectName &&
|
|
1683
|
+
aliasMaps.fileAccess.has(objectName) &&
|
|
1684
|
+
JS_FILE_ACCESS_MEMBERS.has(propertyName)
|
|
1685
|
+
) {
|
|
1686
|
+
addIndicator("fileAccess", `${objectName}.${propertyName}`);
|
|
1687
|
+
}
|
|
1688
|
+
if (
|
|
1689
|
+
objectName &&
|
|
1690
|
+
aliasMaps.network.has(objectName) &&
|
|
1691
|
+
JS_NETWORK_MEMBERS.has(propertyName)
|
|
1692
|
+
) {
|
|
1693
|
+
addIndicator("network", `${objectName}.${propertyName}`);
|
|
1694
|
+
if (!isStaticUrlNode(path.node.arguments?.[0])) {
|
|
1695
|
+
addIndicator(
|
|
1696
|
+
"dynamicFetch",
|
|
1697
|
+
`${objectName}.${propertyName}(dynamic)`,
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (
|
|
1702
|
+
objectName &&
|
|
1703
|
+
aliasMaps.hardware.has(objectName) &&
|
|
1704
|
+
JS_HARDWARE_MEMBERS.has(propertyName)
|
|
1705
|
+
) {
|
|
1706
|
+
addIndicator("hardware", `${objectName}.${propertyName}`);
|
|
1707
|
+
}
|
|
1708
|
+
if (
|
|
1709
|
+
objectName &&
|
|
1710
|
+
aliasMaps.childProcess.has(objectName) &&
|
|
1711
|
+
SUSPICIOUS_JS_EXECUTION_MEMBERS.has(propertyName)
|
|
1712
|
+
) {
|
|
1713
|
+
addIndicator("childProcess", `${objectName}.${propertyName}`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (
|
|
1717
|
+
callee?.type === "Identifier" &&
|
|
1718
|
+
callee.name === "require" &&
|
|
1719
|
+
!isStaticStringNode(path.node.arguments?.[0])
|
|
1720
|
+
) {
|
|
1721
|
+
addIndicator("dynamicImport", "require(dynamic)");
|
|
1722
|
+
}
|
|
1723
|
+
},
|
|
1724
|
+
NewExpression: (path) => {
|
|
1725
|
+
const calleeChain = getMemberChainString(path?.node?.callee);
|
|
1726
|
+
if (calleeChain === "Function") {
|
|
1727
|
+
addIndicator("codeGeneration", "Function");
|
|
1728
|
+
}
|
|
1729
|
+
if (
|
|
1730
|
+
["WebSocket", "EventSource", "XMLHttpRequest"].includes(calleeChain)
|
|
1731
|
+
) {
|
|
1732
|
+
addIndicator("network", calleeChain);
|
|
1733
|
+
}
|
|
1734
|
+
},
|
|
1735
|
+
});
|
|
1736
|
+
const indicatorMap = {};
|
|
1737
|
+
const capabilities = [];
|
|
1738
|
+
for (const category of JS_CAPABILITY_CATEGORIES) {
|
|
1739
|
+
const indicators = Array.from(capabilityIndicators[category]).sort();
|
|
1740
|
+
if (indicators.length) {
|
|
1741
|
+
indicatorMap[category] = indicators;
|
|
1742
|
+
capabilities.push(category);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
capabilities,
|
|
1747
|
+
hasDynamicFetch: capabilityIndicators.dynamicFetch.size > 0,
|
|
1748
|
+
hasDynamicImport: capabilityIndicators.dynamicImport.size > 0,
|
|
1749
|
+
hasEval: capabilityIndicators.codeGeneration.has("eval"),
|
|
1750
|
+
indicatorMap,
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
export const analyzeJsCapabilitiesFile = (filePath) => {
|
|
1755
|
+
let source;
|
|
1756
|
+
try {
|
|
1757
|
+
source = fileToParseableCode(filePath);
|
|
1758
|
+
} catch {
|
|
1759
|
+
return {
|
|
1760
|
+
capabilities: [],
|
|
1761
|
+
hasDynamicFetch: false,
|
|
1762
|
+
hasDynamicImport: false,
|
|
1763
|
+
hasEval: false,
|
|
1764
|
+
indicatorMap: {},
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
return analyzeJsCapabilitiesSource(source);
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
const CRYPTO_IMPORT_SOURCES = new Set([
|
|
1771
|
+
"crypto",
|
|
1772
|
+
"jose",
|
|
1773
|
+
"jsonwebtoken",
|
|
1774
|
+
"node:crypto",
|
|
1775
|
+
"node:tls",
|
|
1776
|
+
"openpgp",
|
|
1777
|
+
"sshpk",
|
|
1778
|
+
"tls",
|
|
1779
|
+
]);
|
|
1780
|
+
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
1781
|
+
const JWT_IMPORT_SOURCES = new Set(["jsonwebtoken"]);
|
|
1782
|
+
const JOSE_IMPORT_SOURCES = new Set(["jose"]);
|
|
1783
|
+
const JWS_ALGORITHM_LITERAL_PATTERN =
|
|
1784
|
+
/^(?:ES|HS|PS|RS)(?:256|384|512)$|^Ed(?:25519|448)$/;
|
|
1785
|
+
const NODE_CRYPTO_CALL_PRIMITIVES = new Map([
|
|
1786
|
+
["createCipheriv", "cipher"],
|
|
1787
|
+
["createDecipheriv", "cipher"],
|
|
1788
|
+
["createHash", "hash"],
|
|
1789
|
+
["createHmac", "hmac"],
|
|
1790
|
+
["createSign", "signature"],
|
|
1791
|
+
["createVerify", "signature"],
|
|
1792
|
+
["generateKey", "key-generation"],
|
|
1793
|
+
["generateKeyPair", "key-generation"],
|
|
1794
|
+
["generateKeyPairSync", "key-generation"],
|
|
1795
|
+
["generateKeySync", "key-generation"],
|
|
1796
|
+
["hkdf", "kdf"],
|
|
1797
|
+
["hkdfSync", "kdf"],
|
|
1798
|
+
["pbkdf2", "kdf"],
|
|
1799
|
+
["pbkdf2Sync", "kdf"],
|
|
1800
|
+
["scrypt", "kdf"],
|
|
1801
|
+
["scryptSync", "kdf"],
|
|
1802
|
+
["sign", "signature"],
|
|
1803
|
+
["verify", "signature"],
|
|
1804
|
+
]);
|
|
1805
|
+
const WEBCRYPTO_METHOD_PRIMITIVES = new Map([
|
|
1806
|
+
["decrypt", "cipher"],
|
|
1807
|
+
["deriveBits", "kdf"],
|
|
1808
|
+
["deriveKey", "kdf"],
|
|
1809
|
+
["digest", "hash"],
|
|
1810
|
+
["encrypt", "cipher"],
|
|
1811
|
+
["generateKey", "key-generation"],
|
|
1812
|
+
["importKey", "key-management"],
|
|
1813
|
+
["sign", "signature"],
|
|
1814
|
+
["unwrapKey", "key-management"],
|
|
1815
|
+
["verify", "signature"],
|
|
1816
|
+
["wrapKey", "key-management"],
|
|
1817
|
+
]);
|
|
1818
|
+
const JWT_METHOD_NAMES = new Set([
|
|
1819
|
+
"sign",
|
|
1820
|
+
"verify",
|
|
1821
|
+
"decode",
|
|
1822
|
+
"encrypt",
|
|
1823
|
+
"decrypt",
|
|
1824
|
+
]);
|
|
1825
|
+
const CRYPTO_OBJECT_PROPERTY_KEYS = new Map([
|
|
1826
|
+
["alg", "signature"],
|
|
1827
|
+
["algorithm", "signature"],
|
|
1828
|
+
["enc", "cipher"],
|
|
1829
|
+
["hash", "hash"],
|
|
1830
|
+
["name", "algorithm"],
|
|
1831
|
+
]);
|
|
1832
|
+
|
|
1833
|
+
const recordCryptoAlgorithm = (
|
|
1834
|
+
algorithms,
|
|
1835
|
+
rawName,
|
|
1836
|
+
primitive,
|
|
1837
|
+
source,
|
|
1838
|
+
loc,
|
|
1839
|
+
extra = {},
|
|
1840
|
+
) => {
|
|
1841
|
+
const algorithmName = typeof rawName === "string" ? rawName.trim() : "";
|
|
1842
|
+
if (!algorithmName) {
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
algorithms.push({
|
|
1846
|
+
columnNumber: loc?.column ?? undefined,
|
|
1847
|
+
keyLength:
|
|
1848
|
+
typeof extra.keyLength === "number" ? extra.keyLength : undefined,
|
|
1849
|
+
lineNumber: loc?.line ?? undefined,
|
|
1850
|
+
name: algorithmName,
|
|
1851
|
+
primitive,
|
|
1852
|
+
source,
|
|
1853
|
+
});
|
|
1854
|
+
};
|
|
1855
|
+
|
|
1856
|
+
const recordCryptoAlgorithmsFromValue = (
|
|
1857
|
+
algorithms,
|
|
1858
|
+
value,
|
|
1859
|
+
primitive,
|
|
1860
|
+
source,
|
|
1861
|
+
loc,
|
|
1862
|
+
) => {
|
|
1863
|
+
if (!value) {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (Array.isArray(value)) {
|
|
1867
|
+
value.forEach((entry) => {
|
|
1868
|
+
recordCryptoAlgorithmsFromValue(
|
|
1869
|
+
algorithms,
|
|
1870
|
+
entry,
|
|
1871
|
+
primitive,
|
|
1872
|
+
source,
|
|
1873
|
+
loc,
|
|
1874
|
+
);
|
|
1875
|
+
});
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
if (typeof value === "string") {
|
|
1879
|
+
recordCryptoAlgorithm(algorithms, value, primitive, source, loc);
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
if (typeof value !== "object") {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
const algorithmName =
|
|
1886
|
+
typeof value.name === "string"
|
|
1887
|
+
? value.name
|
|
1888
|
+
: typeof value.algorithm === "string"
|
|
1889
|
+
? value.algorithm
|
|
1890
|
+
: undefined;
|
|
1891
|
+
if (algorithmName) {
|
|
1892
|
+
recordCryptoAlgorithm(algorithms, algorithmName, primitive, source, loc, {
|
|
1893
|
+
keyLength:
|
|
1894
|
+
typeof value.length === "number"
|
|
1895
|
+
? value.length
|
|
1896
|
+
: typeof value.modulusLength === "number"
|
|
1897
|
+
? value.modulusLength
|
|
1898
|
+
: undefined,
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
if (typeof value.hash === "string") {
|
|
1902
|
+
recordCryptoAlgorithm(
|
|
1903
|
+
algorithms,
|
|
1904
|
+
value.hash,
|
|
1905
|
+
"hash",
|
|
1906
|
+
`${source}:hash`,
|
|
1907
|
+
loc,
|
|
1908
|
+
);
|
|
1909
|
+
} else if (
|
|
1910
|
+
value.hash &&
|
|
1911
|
+
typeof value.hash === "object" &&
|
|
1912
|
+
typeof value.hash.name === "string"
|
|
1913
|
+
) {
|
|
1914
|
+
recordCryptoAlgorithm(
|
|
1915
|
+
algorithms,
|
|
1916
|
+
value.hash.name,
|
|
1917
|
+
"hash",
|
|
1918
|
+
`${source}:hash`,
|
|
1919
|
+
loc,
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
if (typeof value.alg === "string") {
|
|
1923
|
+
recordCryptoAlgorithm(
|
|
1924
|
+
algorithms,
|
|
1925
|
+
value.alg,
|
|
1926
|
+
"signature",
|
|
1927
|
+
`${source}:alg`,
|
|
1928
|
+
loc,
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
if (typeof value.enc === "string") {
|
|
1932
|
+
recordCryptoAlgorithm(
|
|
1933
|
+
algorithms,
|
|
1934
|
+
value.enc,
|
|
1935
|
+
"cipher",
|
|
1936
|
+
`${source}:enc`,
|
|
1937
|
+
loc,
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
const uniqueCryptoAlgorithms = (algorithms) => {
|
|
1943
|
+
const seen = new Set();
|
|
1944
|
+
const uniqueAlgorithms = [];
|
|
1945
|
+
for (const algorithm of algorithms || []) {
|
|
1946
|
+
const key = [
|
|
1947
|
+
algorithm.fileName || "",
|
|
1948
|
+
algorithm.name || "",
|
|
1949
|
+
algorithm.primitive || "",
|
|
1950
|
+
algorithm.source || "",
|
|
1951
|
+
algorithm.lineNumber || "",
|
|
1952
|
+
algorithm.columnNumber || "",
|
|
1953
|
+
].join("\u0000");
|
|
1954
|
+
if (seen.has(key)) {
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
seen.add(key);
|
|
1958
|
+
uniqueAlgorithms.push(algorithm);
|
|
1959
|
+
}
|
|
1960
|
+
return uniqueAlgorithms.sort((left, right) => {
|
|
1961
|
+
return `${left.fileName || ""}:${left.lineNumber || 0}:${left.name || ""}`.localeCompare(
|
|
1962
|
+
`${right.fileName || ""}:${right.lineNumber || 0}:${right.name || ""}`,
|
|
1963
|
+
);
|
|
1964
|
+
});
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
const createScopedStaticValueResolver = (path, staticValueByName) => {
|
|
1968
|
+
const scopedStaticValueByName = getScopedStaticValueByName(
|
|
1969
|
+
path,
|
|
1970
|
+
staticValueByName,
|
|
1971
|
+
getLiteralStringValue,
|
|
1972
|
+
getMemberExpressionPropertyName,
|
|
1973
|
+
);
|
|
1974
|
+
return (astNode) =>
|
|
1975
|
+
resolveStaticValue(
|
|
1976
|
+
astNode,
|
|
1977
|
+
scopedStaticValueByName,
|
|
1978
|
+
getLiteralStringValue,
|
|
1979
|
+
getMemberExpressionPropertyName,
|
|
1980
|
+
);
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
const recordCryptoObjectPropertiesFromValue = (
|
|
1984
|
+
algorithms,
|
|
1985
|
+
optionsValue,
|
|
1986
|
+
source,
|
|
1987
|
+
loc,
|
|
1988
|
+
) => {
|
|
1989
|
+
if (!optionsValue || typeof optionsValue !== "object") {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
for (const [propertyName, primitive] of CRYPTO_OBJECT_PROPERTY_KEYS) {
|
|
1993
|
+
const propertyValue = getStaticObjectProperty(optionsValue, propertyName);
|
|
1994
|
+
if (propertyValue === undefined) {
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
recordCryptoAlgorithmsFromValue(
|
|
1998
|
+
algorithms,
|
|
1999
|
+
propertyValue,
|
|
2000
|
+
primitive,
|
|
2001
|
+
source,
|
|
2002
|
+
loc,
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
const normalizeCryptoLibraryName = (moduleName) => {
|
|
2008
|
+
return String(moduleName || "").trim();
|
|
2009
|
+
};
|
|
2010
|
+
|
|
2011
|
+
const getDirectCallInfo = (callee) => {
|
|
2012
|
+
if (!callee) {
|
|
2013
|
+
return {};
|
|
2014
|
+
}
|
|
2015
|
+
if (callee.type === "Identifier") {
|
|
2016
|
+
return {
|
|
2017
|
+
calleeName: callee.name,
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
if (
|
|
2021
|
+
(callee.type === "MemberExpression" ||
|
|
2022
|
+
callee.type === "OptionalMemberExpression") &&
|
|
2023
|
+
callee.object?.type === "Identifier"
|
|
2024
|
+
) {
|
|
2025
|
+
return {
|
|
2026
|
+
methodName: getMemberExpressionPropertyName(callee.property),
|
|
2027
|
+
rootAlias: callee.object.name,
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
return {};
|
|
2031
|
+
};
|
|
2032
|
+
|
|
2033
|
+
export function analyzeJsCryptoSource(source) {
|
|
2034
|
+
const algorithms = [];
|
|
2035
|
+
const libraries = new Set();
|
|
2036
|
+
const cryptoFunctionAliases = new Map();
|
|
2037
|
+
const cryptoModuleAliases = new Set();
|
|
2038
|
+
const jwtFunctionAliases = new Map();
|
|
2039
|
+
const jwtModuleAliases = new Set();
|
|
2040
|
+
const joseModuleAliases = new Set();
|
|
2041
|
+
const staticValueByName = new Map();
|
|
2042
|
+
const subtleAliases = new Set();
|
|
2043
|
+
const webcryptoAliases = new Set();
|
|
2044
|
+
let ast;
|
|
2045
|
+
try {
|
|
2046
|
+
ast = parse(source, babelParserOptions);
|
|
2047
|
+
} catch {
|
|
2048
|
+
return { algorithms: [], libraries: [] };
|
|
2049
|
+
}
|
|
2050
|
+
traverse.default(ast, {
|
|
2051
|
+
ImportDeclaration: (path) => {
|
|
2052
|
+
const sourceValue = getLiteralStringValue(path?.node?.source);
|
|
2053
|
+
if (!sourceValue) {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (CRYPTO_IMPORT_SOURCES.has(sourceValue)) {
|
|
2057
|
+
libraries.add(normalizeCryptoLibraryName(sourceValue));
|
|
2058
|
+
}
|
|
2059
|
+
for (const specifier of path.node.specifiers || []) {
|
|
2060
|
+
if (NODE_CRYPTO_MODULE_SOURCES.has(sourceValue)) {
|
|
2061
|
+
if (
|
|
2062
|
+
specifier.type === "ImportDefaultSpecifier" ||
|
|
2063
|
+
specifier.type === "ImportNamespaceSpecifier"
|
|
2064
|
+
) {
|
|
2065
|
+
cryptoModuleAliases.add(specifier.local.name);
|
|
2066
|
+
}
|
|
2067
|
+
if (specifier.type === "ImportSpecifier") {
|
|
2068
|
+
const importedName = specifier.imported?.name;
|
|
2069
|
+
if (!importedName) {
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
if (importedName === "webcrypto") {
|
|
2073
|
+
webcryptoAliases.add(specifier.local.name);
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
if (importedName === "subtle") {
|
|
2077
|
+
subtleAliases.add(specifier.local.name);
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
cryptoFunctionAliases.set(specifier.local.name, importedName);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
if (JWT_IMPORT_SOURCES.has(sourceValue)) {
|
|
2084
|
+
if (
|
|
2085
|
+
specifier.type === "ImportDefaultSpecifier" ||
|
|
2086
|
+
specifier.type === "ImportNamespaceSpecifier"
|
|
2087
|
+
) {
|
|
2088
|
+
jwtModuleAliases.add(specifier.local.name);
|
|
2089
|
+
}
|
|
2090
|
+
if (specifier.type === "ImportSpecifier") {
|
|
2091
|
+
const importedName = specifier.imported?.name;
|
|
2092
|
+
if (importedName) {
|
|
2093
|
+
jwtFunctionAliases.set(specifier.local.name, importedName);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (JOSE_IMPORT_SOURCES.has(sourceValue)) {
|
|
2098
|
+
joseModuleAliases.add(specifier.local.name);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
},
|
|
2102
|
+
VariableDeclarator: (path) => {
|
|
2103
|
+
const idNode = path?.node?.id;
|
|
2104
|
+
const initNode = path?.node?.init;
|
|
2105
|
+
if (!idNode || !initNode) {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
const resolveScopedValue = createScopedStaticValueResolver(
|
|
2109
|
+
path,
|
|
2110
|
+
staticValueByName,
|
|
2111
|
+
);
|
|
2112
|
+
const resolvedValue = resolveScopedValue(initNode);
|
|
2113
|
+
if (idNode.type === "Identifier") {
|
|
2114
|
+
if (resolvedValue !== undefined) {
|
|
2115
|
+
staticValueByName.set(idNode.name, resolvedValue);
|
|
2116
|
+
}
|
|
2117
|
+
const initChain = getMemberChainString(initNode);
|
|
2118
|
+
if (
|
|
2119
|
+
initChain === "crypto.subtle" ||
|
|
2120
|
+
initChain.startsWith("webcrypto.subtle") ||
|
|
2121
|
+
(initNode.type === "MemberExpression" &&
|
|
2122
|
+
webcryptoAliases.has(getMemberChainString(initNode.object)) &&
|
|
2123
|
+
getMemberExpressionPropertyName(initNode.property) === "subtle")
|
|
2124
|
+
) {
|
|
2125
|
+
subtleAliases.add(idNode.name);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (
|
|
2129
|
+
initNode.type === "CallExpression" &&
|
|
2130
|
+
initNode.callee?.type === "Identifier" &&
|
|
2131
|
+
initNode.callee.name === "require"
|
|
2132
|
+
) {
|
|
2133
|
+
const moduleName = getLiteralStringValue(initNode.arguments?.[0]);
|
|
2134
|
+
if (!moduleName) {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
if (CRYPTO_IMPORT_SOURCES.has(moduleName)) {
|
|
2138
|
+
libraries.add(normalizeCryptoLibraryName(moduleName));
|
|
2139
|
+
}
|
|
2140
|
+
if (NODE_CRYPTO_MODULE_SOURCES.has(moduleName)) {
|
|
2141
|
+
if (idNode.type === "Identifier") {
|
|
2142
|
+
cryptoModuleAliases.add(idNode.name);
|
|
2143
|
+
}
|
|
2144
|
+
if (idNode.type === "ObjectPattern") {
|
|
2145
|
+
for (const property of idNode.properties || []) {
|
|
2146
|
+
if (property.type !== "ObjectProperty") {
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
const importedName = getMemberExpressionPropertyName(
|
|
2150
|
+
property.key,
|
|
2151
|
+
);
|
|
2152
|
+
const localName =
|
|
2153
|
+
property.value?.type === "Identifier"
|
|
2154
|
+
? property.value.name
|
|
2155
|
+
: undefined;
|
|
2156
|
+
if (!importedName || !localName) {
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
if (importedName === "webcrypto") {
|
|
2160
|
+
webcryptoAliases.add(localName);
|
|
2161
|
+
} else if (importedName === "subtle") {
|
|
2162
|
+
subtleAliases.add(localName);
|
|
2163
|
+
} else {
|
|
2164
|
+
cryptoFunctionAliases.set(localName, importedName);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
if (JWT_IMPORT_SOURCES.has(moduleName)) {
|
|
2170
|
+
if (idNode.type === "Identifier") {
|
|
2171
|
+
jwtModuleAliases.add(idNode.name);
|
|
2172
|
+
}
|
|
2173
|
+
if (idNode.type === "ObjectPattern") {
|
|
2174
|
+
for (const property of idNode.properties || []) {
|
|
2175
|
+
if (property.type !== "ObjectProperty") {
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
const importedName = getMemberExpressionPropertyName(
|
|
2179
|
+
property.key,
|
|
2180
|
+
);
|
|
2181
|
+
const localName =
|
|
2182
|
+
property.value?.type === "Identifier"
|
|
2183
|
+
? property.value.name
|
|
2184
|
+
: undefined;
|
|
2185
|
+
if (importedName && localName) {
|
|
2186
|
+
jwtFunctionAliases.set(localName, importedName);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (
|
|
2192
|
+
JOSE_IMPORT_SOURCES.has(moduleName) &&
|
|
2193
|
+
idNode.type === "Identifier"
|
|
2194
|
+
) {
|
|
2195
|
+
joseModuleAliases.add(idNode.name);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
AssignmentExpression: (path) => {
|
|
2200
|
+
const leftNode = path?.node?.left;
|
|
2201
|
+
const rightNode = path?.node?.right;
|
|
2202
|
+
if (leftNode?.type !== "Identifier" || !rightNode) {
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
const resolveScopedValue = createScopedStaticValueResolver(
|
|
2206
|
+
path,
|
|
2207
|
+
staticValueByName,
|
|
2208
|
+
);
|
|
2209
|
+
const resolvedValue = resolveScopedValue(rightNode);
|
|
2210
|
+
if (resolvedValue === undefined) {
|
|
2211
|
+
staticValueByName.delete(leftNode.name);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
staticValueByName.set(leftNode.name, resolvedValue);
|
|
2215
|
+
},
|
|
2216
|
+
CallExpression: (path) => {
|
|
2217
|
+
const callNode = path?.node;
|
|
2218
|
+
const loc = callNode?.loc?.start;
|
|
2219
|
+
const callee = callNode?.callee;
|
|
2220
|
+
const calleeChain = getMemberChainString(callee);
|
|
2221
|
+
const directCallInfo = getDirectCallInfo(callee);
|
|
2222
|
+
const resolveScopedValue = createScopedStaticValueResolver(
|
|
2223
|
+
path,
|
|
2224
|
+
staticValueByName,
|
|
2225
|
+
);
|
|
2226
|
+
let nodeCryptoMethod;
|
|
2227
|
+
if (callee?.type === "Identifier") {
|
|
2228
|
+
nodeCryptoMethod = cryptoFunctionAliases.get(callee.name);
|
|
2229
|
+
const jwtMethod = jwtFunctionAliases.get(callee.name);
|
|
2230
|
+
if (jwtMethod && JWT_METHOD_NAMES.has(jwtMethod)) {
|
|
2231
|
+
for (const argument of callNode.arguments || []) {
|
|
2232
|
+
const optionsValue = resolveScopedValue(argument);
|
|
2233
|
+
recordCryptoObjectPropertiesFromValue(
|
|
2234
|
+
algorithms,
|
|
2235
|
+
optionsValue,
|
|
2236
|
+
`jsonwebtoken.${jwtMethod}`,
|
|
2237
|
+
loc,
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
} else if (calleeChain) {
|
|
2242
|
+
const calleeParts = calleeChain.split(".");
|
|
2243
|
+
const rootAlias = directCallInfo.rootAlias;
|
|
2244
|
+
const directMethodName = directCallInfo.methodName;
|
|
2245
|
+
if (
|
|
2246
|
+
rootAlias &&
|
|
2247
|
+
cryptoModuleAliases.has(rootAlias) &&
|
|
2248
|
+
directMethodName
|
|
2249
|
+
) {
|
|
2250
|
+
nodeCryptoMethod = directMethodName;
|
|
2251
|
+
}
|
|
2252
|
+
if (
|
|
2253
|
+
rootAlias &&
|
|
2254
|
+
jwtModuleAliases.has(rootAlias) &&
|
|
2255
|
+
directMethodName &&
|
|
2256
|
+
JWT_METHOD_NAMES.has(directMethodName)
|
|
2257
|
+
) {
|
|
2258
|
+
for (const argument of callNode.arguments || []) {
|
|
2259
|
+
const optionsValue = resolveScopedValue(argument);
|
|
2260
|
+
recordCryptoObjectPropertiesFromValue(
|
|
2261
|
+
algorithms,
|
|
2262
|
+
optionsValue,
|
|
2263
|
+
`jsonwebtoken.${directMethodName}`,
|
|
2264
|
+
loc,
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
if (calleeParts[calleeParts.length - 1] === "setProtectedHeader") {
|
|
2269
|
+
const headerValue = resolveScopedValue(callNode.arguments?.[0]);
|
|
2270
|
+
if (headerValue && typeof headerValue === "object") {
|
|
2271
|
+
if (typeof headerValue.alg === "string") {
|
|
2272
|
+
recordCryptoAlgorithm(
|
|
2273
|
+
algorithms,
|
|
2274
|
+
headerValue.alg,
|
|
2275
|
+
"signature",
|
|
2276
|
+
"jwt.setProtectedHeader",
|
|
2277
|
+
loc,
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
if (typeof headerValue.enc === "string") {
|
|
2281
|
+
recordCryptoAlgorithm(
|
|
2282
|
+
algorithms,
|
|
2283
|
+
headerValue.enc,
|
|
2284
|
+
"cipher",
|
|
2285
|
+
"jwt.setProtectedHeader",
|
|
2286
|
+
loc,
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
if (
|
|
2293
|
+
nodeCryptoMethod &&
|
|
2294
|
+
NODE_CRYPTO_CALL_PRIMITIVES.has(nodeCryptoMethod)
|
|
2295
|
+
) {
|
|
2296
|
+
const primitive = NODE_CRYPTO_CALL_PRIMITIVES.get(nodeCryptoMethod);
|
|
2297
|
+
if (
|
|
2298
|
+
[
|
|
2299
|
+
"createHash",
|
|
2300
|
+
"createHmac",
|
|
2301
|
+
"createCipheriv",
|
|
2302
|
+
"createDecipheriv",
|
|
2303
|
+
"createSign",
|
|
2304
|
+
"createVerify",
|
|
2305
|
+
"hkdf",
|
|
2306
|
+
"hkdfSync",
|
|
2307
|
+
"sign",
|
|
2308
|
+
"verify",
|
|
2309
|
+
].includes(nodeCryptoMethod)
|
|
2310
|
+
) {
|
|
2311
|
+
const argumentIndex = ["hkdf", "hkdfSync"].includes(nodeCryptoMethod)
|
|
2312
|
+
? 0
|
|
2313
|
+
: 0;
|
|
2314
|
+
const algorithmValue = resolveScopedValue(
|
|
2315
|
+
callNode.arguments?.[argumentIndex],
|
|
2316
|
+
);
|
|
2317
|
+
recordCryptoAlgorithmsFromValue(
|
|
2318
|
+
algorithms,
|
|
2319
|
+
algorithmValue,
|
|
2320
|
+
primitive,
|
|
2321
|
+
`node:crypto.${nodeCryptoMethod}`,
|
|
2322
|
+
loc,
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
if (["pbkdf2", "pbkdf2Sync"].includes(nodeCryptoMethod)) {
|
|
2326
|
+
const digestValue = resolveScopedValue(callNode.arguments?.[4]);
|
|
2327
|
+
recordCryptoAlgorithmsFromValue(
|
|
2328
|
+
algorithms,
|
|
2329
|
+
digestValue,
|
|
2330
|
+
primitive,
|
|
2331
|
+
`node:crypto.${nodeCryptoMethod}`,
|
|
2332
|
+
loc,
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
if (["scrypt", "scryptSync"].includes(nodeCryptoMethod)) {
|
|
2336
|
+
recordCryptoAlgorithm(
|
|
2337
|
+
algorithms,
|
|
2338
|
+
"scrypt",
|
|
2339
|
+
primitive,
|
|
2340
|
+
`node:crypto.${nodeCryptoMethod}`,
|
|
2341
|
+
loc,
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
if (
|
|
2345
|
+
[
|
|
2346
|
+
"generateKey",
|
|
2347
|
+
"generateKeySync",
|
|
2348
|
+
"generateKeyPair",
|
|
2349
|
+
"generateKeyPairSync",
|
|
2350
|
+
].includes(nodeCryptoMethod)
|
|
2351
|
+
) {
|
|
2352
|
+
const typeValue = resolveScopedValue(callNode.arguments?.[0]);
|
|
2353
|
+
const optionsValue = resolveScopedValue(callNode.arguments?.[1]);
|
|
2354
|
+
if (typeof typeValue === "string") {
|
|
2355
|
+
const keyLength =
|
|
2356
|
+
typeof optionsValue?.length === "number"
|
|
2357
|
+
? optionsValue.length
|
|
2358
|
+
: typeof optionsValue?.modulusLength === "number"
|
|
2359
|
+
? optionsValue.modulusLength
|
|
2360
|
+
: undefined;
|
|
2361
|
+
recordCryptoAlgorithm(
|
|
2362
|
+
algorithms,
|
|
2363
|
+
typeValue,
|
|
2364
|
+
primitive,
|
|
2365
|
+
`node:crypto.${nodeCryptoMethod}`,
|
|
2366
|
+
loc,
|
|
2367
|
+
{ keyLength },
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
let webcryptoMethod;
|
|
2373
|
+
if (calleeChain.startsWith("crypto.subtle.")) {
|
|
2374
|
+
webcryptoMethod = calleeChain.split(".").slice(2).join(".");
|
|
2375
|
+
} else if (calleeChain.startsWith("subtle.")) {
|
|
2376
|
+
webcryptoMethod = subtleAliases.has("subtle")
|
|
2377
|
+
? calleeChain.split(".").slice(1).join(".")
|
|
2378
|
+
: undefined;
|
|
2379
|
+
} else {
|
|
2380
|
+
const calleeParts = calleeChain.split(".");
|
|
2381
|
+
if (subtleAliases.has(calleeParts[0])) {
|
|
2382
|
+
webcryptoMethod = calleeParts.slice(1).join(".");
|
|
2383
|
+
} else if (
|
|
2384
|
+
webcryptoAliases.has(calleeParts[0]) &&
|
|
2385
|
+
calleeParts[1] === "subtle"
|
|
2386
|
+
) {
|
|
2387
|
+
webcryptoMethod = calleeParts.slice(2).join(".");
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
if (webcryptoMethod && WEBCRYPTO_METHOD_PRIMITIVES.has(webcryptoMethod)) {
|
|
2391
|
+
const primitive = WEBCRYPTO_METHOD_PRIMITIVES.get(webcryptoMethod);
|
|
2392
|
+
const primaryAlgorithmValue = resolveScopedValue(
|
|
2393
|
+
callNode.arguments?.[0],
|
|
2394
|
+
);
|
|
2395
|
+
recordCryptoAlgorithmsFromValue(
|
|
2396
|
+
algorithms,
|
|
2397
|
+
primaryAlgorithmValue,
|
|
2398
|
+
primitive,
|
|
2399
|
+
`webcrypto.${webcryptoMethod}`,
|
|
2400
|
+
loc,
|
|
2401
|
+
);
|
|
2402
|
+
if (webcryptoMethod === "deriveKey") {
|
|
2403
|
+
const derivedAlgorithmValue = resolveScopedValue(
|
|
2404
|
+
callNode.arguments?.[2],
|
|
2405
|
+
);
|
|
2406
|
+
recordCryptoAlgorithmsFromValue(
|
|
2407
|
+
algorithms,
|
|
2408
|
+
derivedAlgorithmValue,
|
|
2409
|
+
"key-generation",
|
|
2410
|
+
`webcrypto.${webcryptoMethod}:derived`,
|
|
2411
|
+
loc,
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
},
|
|
2416
|
+
StringLiteral: (path) => {
|
|
2417
|
+
if (
|
|
2418
|
+
!libraries.has("node:crypto") &&
|
|
2419
|
+
!libraries.has("jose") &&
|
|
2420
|
+
!libraries.has("jsonwebtoken")
|
|
2421
|
+
) {
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const literalValue = String(path?.node?.value || "").trim();
|
|
2425
|
+
if (!JWS_ALGORITHM_LITERAL_PATTERN.test(literalValue)) {
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
const parentNode = path.parent;
|
|
2429
|
+
const grandParentNode = path.parentPath?.parent;
|
|
2430
|
+
const isRelevantContext =
|
|
2431
|
+
(parentNode?.type === "AssignmentPattern" &&
|
|
2432
|
+
parentNode.right === path.node) ||
|
|
2433
|
+
(parentNode?.type === "VariableDeclarator" &&
|
|
2434
|
+
parentNode.init === path.node) ||
|
|
2435
|
+
(parentNode?.type === "BinaryExpression" &&
|
|
2436
|
+
["===", "=="].includes(parentNode.operator)) ||
|
|
2437
|
+
(parentNode?.type === "LogicalExpression" &&
|
|
2438
|
+
["||", "??"].includes(parentNode.operator) &&
|
|
2439
|
+
["AssignmentExpression", "VariableDeclarator"].includes(
|
|
2440
|
+
grandParentNode?.type,
|
|
2441
|
+
));
|
|
2442
|
+
if (!isRelevantContext) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
recordCryptoAlgorithm(
|
|
2446
|
+
algorithms,
|
|
2447
|
+
literalValue,
|
|
2448
|
+
"signature",
|
|
2449
|
+
"source-literal:jws-algorithm",
|
|
2450
|
+
path.node.loc?.start,
|
|
2451
|
+
);
|
|
2452
|
+
},
|
|
2453
|
+
});
|
|
2454
|
+
return {
|
|
2455
|
+
algorithms: uniqueCryptoAlgorithms(algorithms),
|
|
2456
|
+
libraries: Array.from(libraries).sort(),
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
export const analyzeJsCryptoFile = (filePath) => {
|
|
2461
|
+
let source;
|
|
2462
|
+
try {
|
|
2463
|
+
source = fileToParseableCode(filePath);
|
|
2464
|
+
} catch {
|
|
2465
|
+
return { algorithms: [], libraries: [] };
|
|
2466
|
+
}
|
|
2467
|
+
return analyzeJsCryptoSource(source);
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
export const detectJsCryptoInventory = async (src, deep = false) => {
|
|
2471
|
+
let srcFiles = [];
|
|
2472
|
+
try {
|
|
2473
|
+
const promiseMap = await getAllSrcJSAndTSFiles(src, deep);
|
|
2474
|
+
srcFiles = promiseMap.flat();
|
|
2475
|
+
} catch {
|
|
2476
|
+
return { algorithms: [], libraries: [] };
|
|
2477
|
+
}
|
|
2478
|
+
const algorithms = [];
|
|
2479
|
+
const libraries = new Set();
|
|
2480
|
+
for (const file of srcFiles) {
|
|
2481
|
+
let analysis;
|
|
2482
|
+
try {
|
|
2483
|
+
analysis = analyzeJsCryptoFile(file);
|
|
2484
|
+
} catch {
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
for (const library of analysis.libraries || []) {
|
|
2488
|
+
libraries.add(library);
|
|
2489
|
+
}
|
|
2490
|
+
for (const algorithm of analysis.algorithms || []) {
|
|
2491
|
+
algorithms.push({
|
|
2492
|
+
...algorithm,
|
|
2493
|
+
fileName: relative(src, file),
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
return {
|
|
2498
|
+
algorithms: uniqueCryptoAlgorithms(algorithms),
|
|
2499
|
+
libraries: Array.from(libraries).sort(),
|
|
2500
|
+
};
|
|
2501
|
+
};
|
|
2502
|
+
|
|
1268
2503
|
/**
|
|
1269
2504
|
* Detect browser-extension capability signals from source code using Babel AST analysis.
|
|
1270
2505
|
*
|
|
@@ -1281,15 +2516,88 @@ export const detectExtensionCapabilities = (src, deep = false) => {
|
|
|
1281
2516
|
}
|
|
1282
2517
|
let srcFiles = [];
|
|
1283
2518
|
try {
|
|
2519
|
+
const searchOptions = normalizeAnalyzerSearchOptions(deep);
|
|
1284
2520
|
srcFiles = [
|
|
1285
|
-
...getAllFiles(
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
2521
|
+
...getAllFiles(
|
|
2522
|
+
searchOptions.deep,
|
|
2523
|
+
src,
|
|
2524
|
+
".js",
|
|
2525
|
+
undefined,
|
|
2526
|
+
undefined,
|
|
2527
|
+
undefined,
|
|
2528
|
+
src,
|
|
2529
|
+
searchOptions.exclude,
|
|
2530
|
+
),
|
|
2531
|
+
...getAllFiles(
|
|
2532
|
+
searchOptions.deep,
|
|
2533
|
+
src,
|
|
2534
|
+
".jsx",
|
|
2535
|
+
undefined,
|
|
2536
|
+
undefined,
|
|
2537
|
+
undefined,
|
|
2538
|
+
src,
|
|
2539
|
+
searchOptions.exclude,
|
|
2540
|
+
),
|
|
2541
|
+
...getAllFiles(
|
|
2542
|
+
searchOptions.deep,
|
|
2543
|
+
src,
|
|
2544
|
+
".cjs",
|
|
2545
|
+
undefined,
|
|
2546
|
+
undefined,
|
|
2547
|
+
undefined,
|
|
2548
|
+
src,
|
|
2549
|
+
searchOptions.exclude,
|
|
2550
|
+
),
|
|
2551
|
+
...getAllFiles(
|
|
2552
|
+
searchOptions.deep,
|
|
2553
|
+
src,
|
|
2554
|
+
".mjs",
|
|
2555
|
+
undefined,
|
|
2556
|
+
undefined,
|
|
2557
|
+
undefined,
|
|
2558
|
+
src,
|
|
2559
|
+
searchOptions.exclude,
|
|
2560
|
+
),
|
|
2561
|
+
...getAllFiles(
|
|
2562
|
+
searchOptions.deep,
|
|
2563
|
+
src,
|
|
2564
|
+
".ts",
|
|
2565
|
+
undefined,
|
|
2566
|
+
undefined,
|
|
2567
|
+
undefined,
|
|
2568
|
+
src,
|
|
2569
|
+
searchOptions.exclude,
|
|
2570
|
+
),
|
|
2571
|
+
...getAllFiles(
|
|
2572
|
+
searchOptions.deep,
|
|
2573
|
+
src,
|
|
2574
|
+
".tsx",
|
|
2575
|
+
undefined,
|
|
2576
|
+
undefined,
|
|
2577
|
+
undefined,
|
|
2578
|
+
src,
|
|
2579
|
+
searchOptions.exclude,
|
|
2580
|
+
),
|
|
2581
|
+
...getAllFiles(
|
|
2582
|
+
searchOptions.deep,
|
|
2583
|
+
src,
|
|
2584
|
+
".vue",
|
|
2585
|
+
undefined,
|
|
2586
|
+
undefined,
|
|
2587
|
+
undefined,
|
|
2588
|
+
src,
|
|
2589
|
+
searchOptions.exclude,
|
|
2590
|
+
),
|
|
2591
|
+
...getAllFiles(
|
|
2592
|
+
searchOptions.deep,
|
|
2593
|
+
src,
|
|
2594
|
+
".svelte",
|
|
2595
|
+
undefined,
|
|
2596
|
+
undefined,
|
|
2597
|
+
undefined,
|
|
2598
|
+
src,
|
|
2599
|
+
searchOptions.exclude,
|
|
2600
|
+
),
|
|
1293
2601
|
];
|
|
1294
2602
|
} catch (_err) {
|
|
1295
2603
|
return { capabilities: [], indicators: {} };
|
|
@@ -1509,13 +2817,26 @@ const providerFamilyFromModelName = (modelName) => {
|
|
|
1509
2817
|
};
|
|
1510
2818
|
|
|
1511
2819
|
const addUniqueProperty = (properties, name, value) => {
|
|
1512
|
-
|
|
2820
|
+
const sanitizedValue = sanitizeBomPropertyValue(name, value);
|
|
2821
|
+
if (
|
|
2822
|
+
sanitizedValue === undefined ||
|
|
2823
|
+
sanitizedValue === null ||
|
|
2824
|
+
sanitizedValue === ""
|
|
2825
|
+
) {
|
|
1513
2826
|
return;
|
|
1514
2827
|
}
|
|
1515
|
-
|
|
2828
|
+
const normalizedValue =
|
|
2829
|
+
typeof sanitizedValue === "string"
|
|
2830
|
+
? sanitizedValue
|
|
2831
|
+
: String(sanitizedValue);
|
|
2832
|
+
if (
|
|
2833
|
+
properties.some(
|
|
2834
|
+
(prop) => prop.name === name && prop.value === normalizedValue,
|
|
2835
|
+
)
|
|
2836
|
+
) {
|
|
1516
2837
|
return;
|
|
1517
2838
|
}
|
|
1518
|
-
properties.push({ name, value });
|
|
2839
|
+
properties.push({ name, value: normalizedValue });
|
|
1519
2840
|
};
|
|
1520
2841
|
|
|
1521
2842
|
const rootMemberName = (value) => String(value || "").split(".")[0];
|
|
@@ -1823,14 +3144,18 @@ const primitiveComponentForMcp = (serviceInfo, primitive) => {
|
|
|
1823
3144
|
addUniqueProperty(
|
|
1824
3145
|
properties,
|
|
1825
3146
|
"cdx:mcp:toolAnnotations",
|
|
1826
|
-
|
|
3147
|
+
primitive.annotations,
|
|
1827
3148
|
);
|
|
1828
3149
|
}
|
|
1829
3150
|
return {
|
|
1830
3151
|
"bom-ref": primitiveRef,
|
|
1831
|
-
description:
|
|
1832
|
-
|
|
1833
|
-
|
|
3152
|
+
description: String(
|
|
3153
|
+
sanitizeBomPropertyValue(
|
|
3154
|
+
"cdx:mcp:description",
|
|
3155
|
+
primitive.description ||
|
|
3156
|
+
`${primitive.role} exposed by ${serviceInfo.name || "mcp-server"}`,
|
|
3157
|
+
) || "",
|
|
3158
|
+
),
|
|
1834
3159
|
name: primitiveName,
|
|
1835
3160
|
properties,
|
|
1836
3161
|
scope: "required",
|
|
@@ -2056,8 +3381,16 @@ const serviceObjectForMcp = (serviceInfo) => {
|
|
|
2056
3381
|
return {
|
|
2057
3382
|
"bom-ref": serviceRef,
|
|
2058
3383
|
authenticated: serviceInfo.authenticated,
|
|
2059
|
-
description:
|
|
2060
|
-
|
|
3384
|
+
description: String(
|
|
3385
|
+
sanitizeBomPropertyValue(
|
|
3386
|
+
"cdx:mcp:description",
|
|
3387
|
+
serviceInfo.description || "",
|
|
3388
|
+
) || "",
|
|
3389
|
+
),
|
|
3390
|
+
endpoints: Array.from(serviceInfo.endpoints)
|
|
3391
|
+
.map((endpoint) => sanitizeBomUrl(endpoint))
|
|
3392
|
+
.filter(Boolean)
|
|
3393
|
+
.sort(),
|
|
2061
3394
|
group: "mcp",
|
|
2062
3395
|
name: serviceName,
|
|
2063
3396
|
properties,
|
|
@@ -2451,15 +3784,88 @@ export const detectMcpInventory = (src, deep = false) => {
|
|
|
2451
3784
|
const servicesByKey = new Map();
|
|
2452
3785
|
let srcFiles = [];
|
|
2453
3786
|
try {
|
|
3787
|
+
const searchOptions = normalizeAnalyzerSearchOptions(deep);
|
|
2454
3788
|
srcFiles = [
|
|
2455
|
-
...getAllFiles(
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
3789
|
+
...getAllFiles(
|
|
3790
|
+
searchOptions.deep,
|
|
3791
|
+
src,
|
|
3792
|
+
".js",
|
|
3793
|
+
undefined,
|
|
3794
|
+
undefined,
|
|
3795
|
+
undefined,
|
|
3796
|
+
src,
|
|
3797
|
+
searchOptions.exclude,
|
|
3798
|
+
),
|
|
3799
|
+
...getAllFiles(
|
|
3800
|
+
searchOptions.deep,
|
|
3801
|
+
src,
|
|
3802
|
+
".jsx",
|
|
3803
|
+
undefined,
|
|
3804
|
+
undefined,
|
|
3805
|
+
undefined,
|
|
3806
|
+
src,
|
|
3807
|
+
searchOptions.exclude,
|
|
3808
|
+
),
|
|
3809
|
+
...getAllFiles(
|
|
3810
|
+
searchOptions.deep,
|
|
3811
|
+
src,
|
|
3812
|
+
".cjs",
|
|
3813
|
+
undefined,
|
|
3814
|
+
undefined,
|
|
3815
|
+
undefined,
|
|
3816
|
+
src,
|
|
3817
|
+
searchOptions.exclude,
|
|
3818
|
+
),
|
|
3819
|
+
...getAllFiles(
|
|
3820
|
+
searchOptions.deep,
|
|
3821
|
+
src,
|
|
3822
|
+
".mjs",
|
|
3823
|
+
undefined,
|
|
3824
|
+
undefined,
|
|
3825
|
+
undefined,
|
|
3826
|
+
src,
|
|
3827
|
+
searchOptions.exclude,
|
|
3828
|
+
),
|
|
3829
|
+
...getAllFiles(
|
|
3830
|
+
searchOptions.deep,
|
|
3831
|
+
src,
|
|
3832
|
+
".ts",
|
|
3833
|
+
undefined,
|
|
3834
|
+
undefined,
|
|
3835
|
+
undefined,
|
|
3836
|
+
src,
|
|
3837
|
+
searchOptions.exclude,
|
|
3838
|
+
),
|
|
3839
|
+
...getAllFiles(
|
|
3840
|
+
searchOptions.deep,
|
|
3841
|
+
src,
|
|
3842
|
+
".tsx",
|
|
3843
|
+
undefined,
|
|
3844
|
+
undefined,
|
|
3845
|
+
undefined,
|
|
3846
|
+
src,
|
|
3847
|
+
searchOptions.exclude,
|
|
3848
|
+
),
|
|
3849
|
+
...getAllFiles(
|
|
3850
|
+
searchOptions.deep,
|
|
3851
|
+
src,
|
|
3852
|
+
".vue",
|
|
3853
|
+
undefined,
|
|
3854
|
+
undefined,
|
|
3855
|
+
undefined,
|
|
3856
|
+
src,
|
|
3857
|
+
searchOptions.exclude,
|
|
3858
|
+
),
|
|
3859
|
+
...getAllFiles(
|
|
3860
|
+
searchOptions.deep,
|
|
3861
|
+
src,
|
|
3862
|
+
".svelte",
|
|
3863
|
+
undefined,
|
|
3864
|
+
undefined,
|
|
3865
|
+
undefined,
|
|
3866
|
+
src,
|
|
3867
|
+
searchOptions.exclude,
|
|
3868
|
+
),
|
|
2463
3869
|
];
|
|
2464
3870
|
} catch {
|
|
2465
3871
|
return { components: [], dependencies: [], services: [] };
|