@cyclonedx/cdxgen 12.3.3 → 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 +64 -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 +42 -18
- package/data/rules/container-risk.yaml +14 -7
- package/data/rules/dependency-sources.yaml +11 -0
- 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 +14 -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 +506 -88
- package/lib/cli/index.poku.js +1352 -212
- package/lib/evinser/evinser.js +14 -9
- package/lib/helpers/analyzer.js +1406 -29
- package/lib/helpers/analyzer.poku.js +342 -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/display.js +291 -1
- package/lib/helpers/display.poku.js +149 -0
- 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/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/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 +1438 -93
- package/lib/helpers/utils.poku.js +846 -4
- package/lib/managers/binary.e2e.poku.js +367 -0
- package/lib/managers/binary.js +2293 -353
- package/lib/managers/binary.poku.js +1699 -1
- package/lib/managers/docker.js +201 -79
- package/lib/managers/docker.poku.js +337 -12
- package/lib/server/server.js +2 -27
- 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 +1366 -31
- 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 +23 -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/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/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/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/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 +45 -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.map +1 -1
- package/types/lib/server/server.d.ts +2 -1
- 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,11 +1,23 @@
|
|
|
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";
|
|
11
23
|
import {
|
|
@@ -44,10 +56,74 @@ const IGNORE_FILE_PATTERN = new RegExp(
|
|
|
44
56
|
"i",
|
|
45
57
|
);
|
|
46
58
|
|
|
47
|
-
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
|
+
) => {
|
|
48
122
|
files = files || readdirSync(dir);
|
|
49
123
|
result = result || [];
|
|
50
124
|
regex = regex || new RegExp(`\\${extn}$`);
|
|
125
|
+
rootDir = rootDir || dir;
|
|
126
|
+
excludePatterns = excludePatterns || [];
|
|
51
127
|
|
|
52
128
|
for (let i = 0; i < files.length; i++) {
|
|
53
129
|
if (IGNORE_FILE_PATTERN.test(files[i]) || files[i].startsWith(".")) {
|
|
@@ -58,6 +134,16 @@ const getAllFiles = (deep, dir, extn, files, result, regex) => {
|
|
|
58
134
|
if (fileStat.isSymbolicLink()) {
|
|
59
135
|
continue;
|
|
60
136
|
}
|
|
137
|
+
if (
|
|
138
|
+
shouldExcludeAnalyzerPath(
|
|
139
|
+
rootDir,
|
|
140
|
+
file,
|
|
141
|
+
excludePatterns,
|
|
142
|
+
fileStat.isDirectory(),
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
61
147
|
if (fileStat.isDirectory()) {
|
|
62
148
|
// Ignore directories
|
|
63
149
|
const dirName = basename(file);
|
|
@@ -81,6 +167,8 @@ const getAllFiles = (deep, dir, extn, files, result, regex) => {
|
|
|
81
167
|
readdirSync(file),
|
|
82
168
|
result,
|
|
83
169
|
regex,
|
|
170
|
+
rootDir,
|
|
171
|
+
excludePatterns,
|
|
84
172
|
);
|
|
85
173
|
} catch (_error) {
|
|
86
174
|
// ignore
|
|
@@ -886,16 +974,23 @@ const parseFileASTTree = (src, file, allImports, allExports) => {
|
|
|
886
974
|
* Return paths to all (j|tsx?) files.
|
|
887
975
|
*/
|
|
888
976
|
const getAllSrcJSAndTSFiles = (src, deep) =>
|
|
889
|
-
Promise.all(
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
+
);
|
|
899
994
|
|
|
900
995
|
export const CHROMIUM_EXTENSION_CAPABILITY_CATEGORIES = [
|
|
901
996
|
"fileAccess",
|
|
@@ -964,6 +1059,113 @@ const SUSPICIOUS_JS_NETWORK_MODULES = new Set([
|
|
|
964
1059
|
"undici",
|
|
965
1060
|
]);
|
|
966
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
|
+
|
|
967
1169
|
const SUSPICIOUS_JS_EXECUTION_MEMBERS = new Set([
|
|
968
1170
|
"exec",
|
|
969
1171
|
"execFile",
|
|
@@ -1032,6 +1234,57 @@ const trackSuspiciousModuleReference = (
|
|
|
1032
1234
|
}
|
|
1033
1235
|
};
|
|
1034
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
|
+
|
|
1035
1288
|
const getMemberChainString = (node) => {
|
|
1036
1289
|
if (!node) {
|
|
1037
1290
|
return "";
|
|
@@ -1070,7 +1323,7 @@ const getMemberChainString = (node) => {
|
|
|
1070
1323
|
return objectChain || propertyChain || "";
|
|
1071
1324
|
};
|
|
1072
1325
|
|
|
1073
|
-
function analyzeSuspiciousJsSource(source) {
|
|
1326
|
+
export function analyzeSuspiciousJsSource(source) {
|
|
1074
1327
|
const executionIndicators = new Set();
|
|
1075
1328
|
const networkIndicators = new Set();
|
|
1076
1329
|
const obfuscationIndicators = new Set();
|
|
@@ -1269,6 +1522,984 @@ export const analyzeSuspiciousJsFile = (filePath) => {
|
|
|
1269
1522
|
return analyzeSuspiciousJsSource(source);
|
|
1270
1523
|
};
|
|
1271
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
|
+
|
|
1272
2503
|
/**
|
|
1273
2504
|
* Detect browser-extension capability signals from source code using Babel AST analysis.
|
|
1274
2505
|
*
|
|
@@ -1285,15 +2516,88 @@ export const detectExtensionCapabilities = (src, deep = false) => {
|
|
|
1285
2516
|
}
|
|
1286
2517
|
let srcFiles = [];
|
|
1287
2518
|
try {
|
|
2519
|
+
const searchOptions = normalizeAnalyzerSearchOptions(deep);
|
|
1288
2520
|
srcFiles = [
|
|
1289
|
-
...getAllFiles(
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
+
),
|
|
1297
2601
|
];
|
|
1298
2602
|
} catch (_err) {
|
|
1299
2603
|
return { capabilities: [], indicators: {} };
|
|
@@ -2480,15 +3784,88 @@ export const detectMcpInventory = (src, deep = false) => {
|
|
|
2480
3784
|
const servicesByKey = new Map();
|
|
2481
3785
|
let srcFiles = [];
|
|
2482
3786
|
try {
|
|
3787
|
+
const searchOptions = normalizeAnalyzerSearchOptions(deep);
|
|
2483
3788
|
srcFiles = [
|
|
2484
|
-
...getAllFiles(
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
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
|
+
),
|
|
2492
3869
|
];
|
|
2493
3870
|
} catch {
|
|
2494
3871
|
return { components: [], dependencies: [], services: [] };
|