@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.
Files changed (157) hide show
  1. package/README.md +64 -22
  2. package/bin/audit.js +21 -7
  3. package/bin/cdxgen.js +238 -116
  4. package/bin/convert.js +28 -13
  5. package/bin/hbom.js +490 -0
  6. package/bin/repl.js +580 -29
  7. package/bin/validate.js +34 -4
  8. package/bin/verify.js +40 -5
  9. package/data/README.md +298 -25
  10. package/data/component-tags.json +6 -0
  11. package/data/crypto-oid.json +16 -0
  12. package/data/predictive-audit-allowlist.json +11 -0
  13. package/data/queries-darwin.json +12 -1
  14. package/data/queries-win.json +7 -1
  15. package/data/queries.json +39 -2
  16. package/data/rules/ai-agent-governance.yaml +16 -0
  17. package/data/rules/asar-archives.yaml +150 -0
  18. package/data/rules/chrome-extensions.yaml +8 -0
  19. package/data/rules/ci-permissions.yaml +42 -18
  20. package/data/rules/container-risk.yaml +14 -7
  21. package/data/rules/dependency-sources.yaml +11 -0
  22. package/data/rules/hbom-compliance.yaml +325 -0
  23. package/data/rules/hbom-performance.yaml +307 -0
  24. package/data/rules/hbom-security.yaml +248 -0
  25. package/data/rules/host-topology.yaml +165 -0
  26. package/data/rules/mcp-servers.yaml +18 -3
  27. package/data/rules/obom-runtime.yaml +907 -22
  28. package/data/rules/package-integrity.yaml +14 -0
  29. package/data/rules/rootfs-hardening.yaml +179 -0
  30. package/data/rules/vscode-extensions.yaml +9 -0
  31. package/lib/audit/index.js +209 -8
  32. package/lib/audit/index.poku.js +332 -0
  33. package/lib/audit/reporters.js +222 -0
  34. package/lib/audit/targets.js +146 -1
  35. package/lib/audit/targets.poku.js +186 -0
  36. package/lib/cli/asar.poku.js +328 -0
  37. package/lib/cli/index.js +506 -88
  38. package/lib/cli/index.poku.js +1352 -212
  39. package/lib/evinser/evinser.js +14 -9
  40. package/lib/helpers/analyzer.js +1406 -29
  41. package/lib/helpers/analyzer.poku.js +342 -0
  42. package/lib/helpers/analyzerScope.js +712 -0
  43. package/lib/helpers/asarutils.js +1556 -0
  44. package/lib/helpers/asarutils.poku.js +443 -0
  45. package/lib/helpers/auditCategories.js +12 -0
  46. package/lib/helpers/auditCategories.poku.js +32 -0
  47. package/lib/helpers/cbomutils.js +271 -1
  48. package/lib/helpers/cbomutils.poku.js +248 -5
  49. package/lib/helpers/display.js +291 -1
  50. package/lib/helpers/display.poku.js +149 -0
  51. package/lib/helpers/evidenceUtils.js +58 -0
  52. package/lib/helpers/evidenceUtils.poku.js +54 -0
  53. package/lib/helpers/exportUtils.js +9 -0
  54. package/lib/helpers/gtfobins.js +142 -8
  55. package/lib/helpers/gtfobins.poku.js +24 -1
  56. package/lib/helpers/hbom.js +710 -0
  57. package/lib/helpers/hbom.poku.js +496 -0
  58. package/lib/helpers/hbomAnalysis.js +268 -0
  59. package/lib/helpers/hbomAnalysis.poku.js +249 -0
  60. package/lib/helpers/hbomLoader.js +35 -0
  61. package/lib/helpers/hostTopology.js +803 -0
  62. package/lib/helpers/hostTopology.poku.js +363 -0
  63. package/lib/helpers/inventoryStats.js +69 -0
  64. package/lib/helpers/inventoryStats.poku.js +86 -0
  65. package/lib/helpers/lolbas.js +19 -1
  66. package/lib/helpers/lolbas.poku.js +23 -0
  67. package/lib/helpers/osqueryTransform.js +47 -0
  68. package/lib/helpers/osqueryTransform.poku.js +47 -0
  69. package/lib/helpers/plugins.js +349 -0
  70. package/lib/helpers/plugins.poku.js +57 -0
  71. package/lib/helpers/protobom.js +156 -45
  72. package/lib/helpers/protobom.poku.js +140 -5
  73. package/lib/helpers/remote/dependency-track.js +36 -3
  74. package/lib/helpers/remote/dependency-track.poku.js +44 -0
  75. package/lib/helpers/source.js +24 -0
  76. package/lib/helpers/source.poku.js +32 -0
  77. package/lib/helpers/utils.js +1438 -93
  78. package/lib/helpers/utils.poku.js +846 -4
  79. package/lib/managers/binary.e2e.poku.js +367 -0
  80. package/lib/managers/binary.js +2293 -353
  81. package/lib/managers/binary.poku.js +1699 -1
  82. package/lib/managers/docker.js +201 -79
  83. package/lib/managers/docker.poku.js +337 -12
  84. package/lib/server/server.js +2 -27
  85. package/lib/stages/postgen/annotator.js +38 -0
  86. package/lib/stages/postgen/annotator.poku.js +107 -1
  87. package/lib/stages/postgen/auditBom.js +121 -18
  88. package/lib/stages/postgen/auditBom.poku.js +1366 -31
  89. package/lib/stages/postgen/hostTopologyAudit.poku.js +186 -0
  90. package/lib/stages/postgen/postgen.js +192 -1
  91. package/lib/stages/postgen/postgen.poku.js +321 -0
  92. package/lib/stages/postgen/ruleEngine.js +116 -0
  93. package/lib/stages/pregen/envAudit.js +14 -3
  94. package/package.json +23 -21
  95. package/types/bin/hbom.d.ts +3 -0
  96. package/types/bin/hbom.d.ts.map +1 -0
  97. package/types/bin/repl.d.ts.map +1 -1
  98. package/types/lib/audit/index.d.ts +44 -0
  99. package/types/lib/audit/index.d.ts.map +1 -1
  100. package/types/lib/audit/reporters.d.ts +16 -0
  101. package/types/lib/audit/reporters.d.ts.map +1 -1
  102. package/types/lib/audit/targets.d.ts.map +1 -1
  103. package/types/lib/cli/index.d.ts +16 -0
  104. package/types/lib/cli/index.d.ts.map +1 -1
  105. package/types/lib/evinser/evinser.d.ts +4 -0
  106. package/types/lib/evinser/evinser.d.ts.map +1 -1
  107. package/types/lib/helpers/analyzer.d.ts +33 -0
  108. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  109. package/types/lib/helpers/analyzerScope.d.ts +11 -0
  110. package/types/lib/helpers/analyzerScope.d.ts.map +1 -0
  111. package/types/lib/helpers/asarutils.d.ts +34 -0
  112. package/types/lib/helpers/asarutils.d.ts.map +1 -0
  113. package/types/lib/helpers/auditCategories.d.ts +5 -0
  114. package/types/lib/helpers/auditCategories.d.ts.map +1 -1
  115. package/types/lib/helpers/cbomutils.d.ts +3 -2
  116. package/types/lib/helpers/cbomutils.d.ts.map +1 -1
  117. package/types/lib/helpers/display.d.ts.map +1 -1
  118. package/types/lib/helpers/evidenceUtils.d.ts +8 -0
  119. package/types/lib/helpers/evidenceUtils.d.ts.map +1 -0
  120. package/types/lib/helpers/exportUtils.d.ts.map +1 -1
  121. package/types/lib/helpers/gtfobins.d.ts +8 -0
  122. package/types/lib/helpers/gtfobins.d.ts.map +1 -1
  123. package/types/lib/helpers/hbom.d.ts +49 -0
  124. package/types/lib/helpers/hbom.d.ts.map +1 -0
  125. package/types/lib/helpers/hbomAnalysis.d.ts +62 -0
  126. package/types/lib/helpers/hbomAnalysis.d.ts.map +1 -0
  127. package/types/lib/helpers/hbomLoader.d.ts +7 -0
  128. package/types/lib/helpers/hbomLoader.d.ts.map +1 -0
  129. package/types/lib/helpers/hostTopology.d.ts +12 -0
  130. package/types/lib/helpers/hostTopology.d.ts.map +1 -0
  131. package/types/lib/helpers/inventoryStats.d.ts +11 -0
  132. package/types/lib/helpers/inventoryStats.d.ts.map +1 -0
  133. package/types/lib/helpers/lolbas.d.ts.map +1 -1
  134. package/types/lib/helpers/osqueryTransform.d.ts +3 -0
  135. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -1
  136. package/types/lib/helpers/plugins.d.ts +58 -0
  137. package/types/lib/helpers/plugins.d.ts.map +1 -0
  138. package/types/lib/helpers/protobom.d.ts +3 -4
  139. package/types/lib/helpers/protobom.d.ts.map +1 -1
  140. package/types/lib/helpers/remote/dependency-track.d.ts +10 -3
  141. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  142. package/types/lib/helpers/source.d.ts.map +1 -1
  143. package/types/lib/helpers/utils.d.ts +45 -8
  144. package/types/lib/helpers/utils.d.ts.map +1 -1
  145. package/types/lib/managers/binary.d.ts +5 -0
  146. package/types/lib/managers/binary.d.ts.map +1 -1
  147. package/types/lib/managers/docker.d.ts.map +1 -1
  148. package/types/lib/server/server.d.ts +2 -1
  149. package/types/lib/server/server.d.ts.map +1 -1
  150. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  151. package/types/lib/stages/postgen/auditBom.d.ts +26 -1
  152. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  153. package/types/lib/stages/postgen/postgen.d.ts +2 -1
  154. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  155. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  156. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  157. package/data/spdx-model-v3.0.1.jsonld +0 -15999
@@ -1,11 +1,23 @@
1
1
  import { lstatSync, readdirSync, readFileSync } from "node:fs";
2
- import { basename, isAbsolute, join, relative, resolve } from "node:path";
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 getAllFiles = (deep, dir, extn, files, result, regex) => {
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
- getAllFiles(deep, src, ".js"),
891
- getAllFiles(deep, src, ".jsx"),
892
- getAllFiles(deep, src, ".cjs"),
893
- getAllFiles(deep, src, ".mjs"),
894
- getAllFiles(deep, src, ".ts"),
895
- getAllFiles(deep, src, ".tsx"),
896
- getAllFiles(deep, src, ".vue"),
897
- getAllFiles(deep, src, ".svelte"),
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(deep, src, ".js"),
1290
- ...getAllFiles(deep, src, ".jsx"),
1291
- ...getAllFiles(deep, src, ".cjs"),
1292
- ...getAllFiles(deep, src, ".mjs"),
1293
- ...getAllFiles(deep, src, ".ts"),
1294
- ...getAllFiles(deep, src, ".tsx"),
1295
- ...getAllFiles(deep, src, ".vue"),
1296
- ...getAllFiles(deep, src, ".svelte"),
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(deep, src, ".js"),
2485
- ...getAllFiles(deep, src, ".jsx"),
2486
- ...getAllFiles(deep, src, ".cjs"),
2487
- ...getAllFiles(deep, src, ".mjs"),
2488
- ...getAllFiles(deep, src, ".ts"),
2489
- ...getAllFiles(deep, src, ".tsx"),
2490
- ...getAllFiles(deep, src, ".vue"),
2491
- ...getAllFiles(deep, src, ".svelte"),
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: [] };