@cyclonedx/cdxgen 12.3.2 → 12.4.0

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