@cyclonedx/cdxgen 12.3.3 → 12.4.1
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 +69 -25
- package/bin/audit.js +21 -7
- package/bin/cdxgen.js +270 -127
- package/bin/convert.js +34 -15
- package/bin/hbom.js +495 -0
- package/bin/repl.js +592 -37
- package/bin/validate.js +31 -4
- package/bin/verify.js +18 -5
- package/data/README.md +298 -25
- package/data/component-tags.json +6 -0
- package/data/crypto-oid.json +16 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -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 +210 -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 +527 -99
- package/lib/cli/index.poku.js +1469 -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/bomUtils.js +155 -1
- package/lib/helpers/bomUtils.poku.js +79 -1
- 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 +350 -0
- package/lib/helpers/plugins.poku.js +57 -0
- package/lib/helpers/protobom.js +209 -45
- package/lib/helpers/protobom.poku.js +183 -5
- package/lib/helpers/protobomLoader.js +43 -0
- package/lib/helpers/protobomLoader.poku.js +31 -0
- 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 +4 -28
- 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 +406 -8
- package/lib/stages/postgen/postgen.poku.js +484 -0
- package/lib/stages/postgen/ruleEngine.js +116 -0
- package/lib/stages/pregen/envAudit.js +14 -3
- package/lib/validator/bomValidator.js +90 -38
- package/lib/validator/bomValidator.poku.js +90 -0
- package/lib/validator/complianceRules.js +4 -2
- package/lib/validator/index.poku.js +14 -0
- 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 +1 -1
- 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/bomUtils.d.ts +10 -0
- package/types/lib/helpers/bomUtils.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 +76 -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 +5 -4
- package/types/lib/helpers/protobom.d.ts.map +1 -1
- package/types/lib/helpers/protobomLoader.d.ts +17 -0
- package/types/lib/helpers/protobomLoader.d.ts.map +1 -0
- 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/types/lib/third-party/arborist/lib/node.d.ts +23 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.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: [] };
|