@cyclonedx/cdxgen 12.2.0 → 12.3.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 (181) hide show
  1. package/README.md +242 -90
  2. package/bin/audit.js +191 -0
  3. package/bin/cdxgen.js +532 -168
  4. package/bin/convert.js +99 -0
  5. package/bin/evinse.js +23 -0
  6. package/bin/repl.js +339 -8
  7. package/bin/sign.js +8 -0
  8. package/bin/validate.js +8 -0
  9. package/bin/verify.js +8 -0
  10. package/data/container-knowledge-index.json +125 -0
  11. package/data/gtfobins-index.json +6296 -0
  12. package/data/lolbas-index.json +150 -0
  13. package/data/queries-darwin.json +63 -3
  14. package/data/queries-win.json +45 -3
  15. package/data/queries.json +74 -2
  16. package/data/rules/chrome-extensions.yaml +240 -0
  17. package/data/rules/ci-permissions.yaml +478 -18
  18. package/data/rules/container-risk.yaml +270 -0
  19. package/data/rules/obom-runtime.yaml +891 -0
  20. package/data/rules/package-integrity.yaml +49 -0
  21. package/data/spdx-export.schema.json +6794 -0
  22. package/data/spdx-model-v3.0.1.jsonld +15999 -0
  23. package/lib/audit/index.js +1924 -0
  24. package/lib/audit/index.poku.js +1488 -0
  25. package/lib/audit/progress.js +137 -0
  26. package/lib/audit/progress.poku.js +188 -0
  27. package/lib/audit/reporters.js +618 -0
  28. package/lib/audit/scoring.js +310 -0
  29. package/lib/audit/scoring.poku.js +341 -0
  30. package/lib/audit/targets.js +260 -0
  31. package/lib/audit/targets.poku.js +331 -0
  32. package/lib/cli/index.js +276 -68
  33. package/lib/cli/index.poku.js +368 -0
  34. package/lib/helpers/analyzer.js +1052 -5
  35. package/lib/helpers/analyzer.poku.js +301 -0
  36. package/lib/helpers/annotationFormatter.js +49 -0
  37. package/lib/helpers/annotationFormatter.poku.js +44 -0
  38. package/lib/helpers/bomUtils.js +36 -0
  39. package/lib/helpers/bomUtils.poku.js +51 -0
  40. package/lib/helpers/caxa.js +2 -2
  41. package/lib/helpers/chromextutils.js +1153 -0
  42. package/lib/helpers/chromextutils.poku.js +493 -0
  43. package/lib/helpers/ciParsers/githubActions.js +1632 -45
  44. package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
  45. package/lib/helpers/containerRisk.js +186 -0
  46. package/lib/helpers/containerRisk.poku.js +52 -0
  47. package/lib/helpers/depsUtils.js +16 -0
  48. package/lib/helpers/depsUtils.poku.js +58 -1
  49. package/lib/helpers/display.js +245 -61
  50. package/lib/helpers/display.poku.js +162 -2
  51. package/lib/helpers/exportUtils.js +123 -0
  52. package/lib/helpers/exportUtils.poku.js +60 -0
  53. package/lib/helpers/formulationParsers.js +69 -0
  54. package/lib/helpers/formulationParsers.poku.js +44 -0
  55. package/lib/helpers/gtfobins.js +189 -0
  56. package/lib/helpers/gtfobins.poku.js +49 -0
  57. package/lib/helpers/lolbas.js +267 -0
  58. package/lib/helpers/lolbas.poku.js +39 -0
  59. package/lib/helpers/osqueryTransform.js +84 -0
  60. package/lib/helpers/osqueryTransform.poku.js +49 -0
  61. package/lib/helpers/provenanceUtils.js +193 -0
  62. package/lib/helpers/provenanceUtils.poku.js +145 -0
  63. package/lib/helpers/pylockutils.js +281 -0
  64. package/lib/helpers/pylockutils.poku.js +48 -0
  65. package/lib/helpers/registryProvenance.js +793 -0
  66. package/lib/helpers/registryProvenance.poku.js +452 -0
  67. package/lib/helpers/remote/dependency-track.js +84 -0
  68. package/lib/helpers/remote/dependency-track.poku.js +119 -0
  69. package/lib/helpers/source.js +1267 -0
  70. package/lib/helpers/source.poku.js +771 -0
  71. package/lib/helpers/spdxUtils.js +97 -0
  72. package/lib/helpers/spdxUtils.poku.js +70 -0
  73. package/lib/helpers/table.js +384 -0
  74. package/lib/helpers/table.poku.js +186 -0
  75. package/lib/helpers/unicodeScan.js +147 -0
  76. package/lib/helpers/unicodeScan.poku.js +45 -0
  77. package/lib/helpers/utils.js +882 -136
  78. package/lib/helpers/utils.poku.js +995 -91
  79. package/lib/managers/binary.js +29 -5
  80. package/lib/managers/docker.js +179 -52
  81. package/lib/managers/docker.poku.js +327 -28
  82. package/lib/managers/oci.js +107 -23
  83. package/lib/managers/oci.poku.js +132 -0
  84. package/lib/server/openapi.yaml +50 -0
  85. package/lib/server/server.js +228 -331
  86. package/lib/server/server.poku.js +220 -5
  87. package/lib/stages/postgen/annotator.js +7 -0
  88. package/lib/stages/postgen/annotator.poku.js +40 -0
  89. package/lib/stages/postgen/auditBom.js +20 -5
  90. package/lib/stages/postgen/auditBom.poku.js +1729 -67
  91. package/lib/stages/postgen/postgen.js +40 -0
  92. package/lib/stages/postgen/postgen.poku.js +47 -0
  93. package/lib/stages/postgen/ruleEngine.js +80 -2
  94. package/lib/stages/postgen/spdxConverter.js +796 -0
  95. package/lib/stages/postgen/spdxConverter.poku.js +341 -0
  96. package/lib/validator/bomValidator.js +232 -0
  97. package/lib/validator/bomValidator.poku.js +70 -0
  98. package/lib/validator/complianceRules.js +70 -7
  99. package/lib/validator/complianceRules.poku.js +30 -0
  100. package/lib/validator/reporters/annotations.js +2 -2
  101. package/lib/validator/reporters/console.js +13 -2
  102. package/lib/validator/reporters.poku.js +13 -0
  103. package/package.json +10 -8
  104. package/types/bin/audit.d.ts +3 -0
  105. package/types/bin/audit.d.ts.map +1 -0
  106. package/types/bin/convert.d.ts +3 -0
  107. package/types/bin/convert.d.ts.map +1 -0
  108. package/types/bin/repl.d.ts.map +1 -1
  109. package/types/lib/audit/index.d.ts +115 -0
  110. package/types/lib/audit/index.d.ts.map +1 -0
  111. package/types/lib/audit/progress.d.ts +27 -0
  112. package/types/lib/audit/progress.d.ts.map +1 -0
  113. package/types/lib/audit/reporters.d.ts +35 -0
  114. package/types/lib/audit/reporters.d.ts.map +1 -0
  115. package/types/lib/audit/scoring.d.ts +35 -0
  116. package/types/lib/audit/scoring.d.ts.map +1 -0
  117. package/types/lib/audit/targets.d.ts +63 -0
  118. package/types/lib/audit/targets.d.ts.map +1 -0
  119. package/types/lib/cli/index.d.ts +8 -0
  120. package/types/lib/cli/index.d.ts.map +1 -1
  121. package/types/lib/helpers/analyzer.d.ts +13 -0
  122. package/types/lib/helpers/analyzer.d.ts.map +1 -1
  123. package/types/lib/helpers/annotationFormatter.d.ts +23 -0
  124. package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
  125. package/types/lib/helpers/bomUtils.d.ts +5 -0
  126. package/types/lib/helpers/bomUtils.d.ts.map +1 -0
  127. package/types/lib/helpers/chromextutils.d.ts +97 -0
  128. package/types/lib/helpers/chromextutils.d.ts.map +1 -0
  129. package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
  130. package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
  131. package/types/lib/helpers/containerRisk.d.ts +17 -0
  132. package/types/lib/helpers/containerRisk.d.ts.map +1 -0
  133. package/types/lib/helpers/depsUtils.d.ts.map +1 -1
  134. package/types/lib/helpers/display.d.ts +4 -1
  135. package/types/lib/helpers/display.d.ts.map +1 -1
  136. package/types/lib/helpers/exportUtils.d.ts +40 -0
  137. package/types/lib/helpers/exportUtils.d.ts.map +1 -0
  138. package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
  139. package/types/lib/helpers/gtfobins.d.ts +17 -0
  140. package/types/lib/helpers/gtfobins.d.ts.map +1 -0
  141. package/types/lib/helpers/lolbas.d.ts +16 -0
  142. package/types/lib/helpers/lolbas.d.ts.map +1 -0
  143. package/types/lib/helpers/osqueryTransform.d.ts +7 -0
  144. package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
  145. package/types/lib/helpers/provenanceUtils.d.ts +90 -0
  146. package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
  147. package/types/lib/helpers/pylockutils.d.ts +51 -0
  148. package/types/lib/helpers/pylockutils.d.ts.map +1 -0
  149. package/types/lib/helpers/registryProvenance.d.ts +17 -0
  150. package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
  151. package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
  152. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
  153. package/types/lib/helpers/source.d.ts +141 -0
  154. package/types/lib/helpers/source.d.ts.map +1 -0
  155. package/types/lib/helpers/spdxUtils.d.ts +2 -0
  156. package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
  157. package/types/lib/helpers/table.d.ts +6 -0
  158. package/types/lib/helpers/table.d.ts.map +1 -0
  159. package/types/lib/helpers/unicodeScan.d.ts +46 -0
  160. package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
  161. package/types/lib/helpers/utils.d.ts +30 -11
  162. package/types/lib/helpers/utils.d.ts.map +1 -1
  163. package/types/lib/managers/binary.d.ts.map +1 -1
  164. package/types/lib/managers/docker.d.ts.map +1 -1
  165. package/types/lib/managers/oci.d.ts.map +1 -1
  166. package/types/lib/server/server.d.ts +0 -35
  167. package/types/lib/server/server.d.ts.map +1 -1
  168. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  169. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  170. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  171. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  172. package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
  173. package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
  174. package/types/lib/validator/bomValidator.d.ts +1 -0
  175. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  176. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  177. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  178. package/types/bin/dependencies.d.ts +0 -3
  179. package/types/bin/dependencies.d.ts.map +0 -1
  180. package/types/bin/licenses.d.ts +0 -3
  181. package/types/bin/licenses.d.ts.map +0 -1
@@ -145,8 +145,8 @@ const setFileRef = (
145
145
  exportedModules,
146
146
  isExternal: true,
147
147
  fileName: fileRelativeLoc,
148
- lineNumber: sourceLoc?.line ? sourceLoc.line : undefined,
149
- columnNumber: sourceLoc?.column ? sourceLoc.column : undefined,
148
+ lineNumber: sourceLoc?.line ?? undefined,
149
+ columnNumber: sourceLoc?.column ?? undefined,
150
150
  };
151
151
  // replace relative imports with full path
152
152
  let moduleFullPath = pathway;
@@ -186,7 +186,7 @@ const setFileRef = (
186
186
  const vueCleaningRegex = /<\/*script.*>|<style[\s\S]*style>|<\/*br>/gi;
187
187
  const vueTemplateRegex = /(<template.*>)([\s\S]*)(<\/template>)/gi;
188
188
  const vueCommentRegex = /<!--[\s\S]*?-->/gi;
189
- const vueBindRegex = /(:\[)([\s\S]*?)(\])/gi;
189
+ const vueBindRegex = /(:\[)([\s\S]*?)(])/gi;
190
190
  const vuePropRegex = /\s([.:@])([a-zA-Z]*?=)/gi;
191
191
 
192
192
  const fileToParseableCode = (file) => {
@@ -216,12 +216,252 @@ const fileToParseableCode = (file) => {
216
216
  return code;
217
217
  };
218
218
 
219
+ const isWasmPath = (modulePath) =>
220
+ typeof modulePath === "string" && /\.wasm([?#].*)?$/i.test(modulePath);
221
+
222
+ const getStringValue = (astNode) => {
223
+ if (!astNode) {
224
+ return undefined;
225
+ }
226
+ if (astNode.type === "StringLiteral") {
227
+ return astNode.value;
228
+ }
229
+ if (
230
+ astNode.type === "TemplateLiteral" &&
231
+ astNode.expressions.length === 0 &&
232
+ astNode.quasis.length === 1
233
+ ) {
234
+ return astNode.quasis[0].value.cooked;
235
+ }
236
+ return undefined;
237
+ };
238
+
239
+ const unwrapAwait = (astNode) =>
240
+ astNode?.type === "AwaitExpression" ? astNode.argument : astNode;
241
+
242
+ const isImportMetaUrl = (astNode) =>
243
+ astNode?.type === "MemberExpression" &&
244
+ astNode.object?.type === "MetaProperty" &&
245
+ astNode.object.meta?.name === "import" &&
246
+ astNode.object.property?.name === "meta" &&
247
+ astNode.property?.type === "Identifier" &&
248
+ astNode.property.name === "url";
249
+
250
+ const getMemberExpressionPropertyName = (propertyNode) => {
251
+ if (!propertyNode) {
252
+ return undefined;
253
+ }
254
+ if (propertyNode.type === "Identifier") {
255
+ return propertyNode.name;
256
+ }
257
+ if (propertyNode.type === "StringLiteral") {
258
+ return propertyNode.value;
259
+ }
260
+ return undefined;
261
+ };
262
+
263
+ const resolveWasmLiteralFromNode = (astNode, wasmBufferByVarName) => {
264
+ const normalizedNode = unwrapAwait(astNode);
265
+ const directLiteral = getStringValue(normalizedNode);
266
+ if (isWasmPath(directLiteral)) {
267
+ return directLiteral;
268
+ }
269
+ if (normalizedNode?.type === "Identifier") {
270
+ return wasmBufferByVarName.get(normalizedNode.name);
271
+ }
272
+ if (normalizedNode?.type === "CallExpression") {
273
+ if (
274
+ normalizedNode.callee?.type === "Identifier" &&
275
+ normalizedNode.callee.name === "fetch" &&
276
+ normalizedNode.arguments?.length
277
+ ) {
278
+ return resolveWasmLiteralFromNode(
279
+ normalizedNode.arguments[0],
280
+ wasmBufferByVarName,
281
+ );
282
+ }
283
+ }
284
+ if (normalizedNode?.type === "NewExpression") {
285
+ if (
286
+ normalizedNode.callee?.type === "Identifier" &&
287
+ normalizedNode.callee.name === "URL" &&
288
+ normalizedNode.arguments?.length
289
+ ) {
290
+ const urlLiteral = getStringValue(normalizedNode.arguments[0]);
291
+ const baseArg = normalizedNode.arguments[1];
292
+ if (isWasmPath(urlLiteral) && (!baseArg || isImportMetaUrl(baseArg))) {
293
+ return urlLiteral;
294
+ }
295
+ }
296
+ }
297
+ return undefined;
298
+ };
299
+
300
+ const getWasmSourceFromInstantiateCall = (callNode, wasmBufferByVarName) => {
301
+ if (!callNode?.callee || callNode.callee.type !== "MemberExpression") {
302
+ return undefined;
303
+ }
304
+ const objectNode = callNode.callee.object;
305
+ const propertyNode = callNode.callee.property;
306
+ const calleeObjectName = getMemberExpressionPropertyName(objectNode);
307
+ const calleePropertyName = getMemberExpressionPropertyName(propertyNode);
308
+ if (calleeObjectName !== "WebAssembly") {
309
+ return undefined;
310
+ }
311
+ if (
312
+ calleePropertyName !== "instantiate" &&
313
+ calleePropertyName !== "instantiateStreaming" &&
314
+ calleePropertyName !== "compile" &&
315
+ calleePropertyName !== "compileStreaming"
316
+ ) {
317
+ return undefined;
318
+ }
319
+ if (!callNode.arguments?.length) {
320
+ return undefined;
321
+ }
322
+ return resolveWasmLiteralFromNode(callNode.arguments[0], wasmBufferByVarName);
323
+ };
324
+
325
+ const getWasmSourceFromCallExpression = (callNode, wasmBufferByVarName) => {
326
+ const wasmSourceFromInstantiate = getWasmSourceFromInstantiateCall(
327
+ callNode,
328
+ wasmBufferByVarName,
329
+ );
330
+ if (wasmSourceFromInstantiate) {
331
+ return wasmSourceFromInstantiate;
332
+ }
333
+ if (
334
+ callNode?.callee?.type === "Identifier" &&
335
+ ["fetch", "locateFile"].includes(callNode.callee.name) &&
336
+ callNode.arguments?.length
337
+ ) {
338
+ return resolveWasmLiteralFromNode(
339
+ callNode.arguments[0],
340
+ wasmBufferByVarName,
341
+ );
342
+ }
343
+ return undefined;
344
+ };
345
+
346
+ const getNamedImportsFromObjectPattern = (idNode) => {
347
+ const namedImports = [];
348
+ if (!idNode || idNode.type !== "ObjectPattern") {
349
+ return namedImports;
350
+ }
351
+ for (const prop of idNode.properties || []) {
352
+ if (prop.type !== "ObjectProperty") {
353
+ continue;
354
+ }
355
+ const keyName = getMemberExpressionPropertyName(prop.key);
356
+ if (keyName) {
357
+ namedImports.push(keyName);
358
+ }
359
+ }
360
+ return namedImports;
361
+ };
362
+
363
+ const setSyntheticImportRef = (
364
+ allImports,
365
+ allExports,
366
+ src,
367
+ file,
368
+ importPath,
369
+ modules,
370
+ sourceLoc,
371
+ ) => {
372
+ if (!importPath) {
373
+ return;
374
+ }
375
+ const safeModules = modules || [];
376
+ const syntheticSpecifiers = safeModules.map((moduleName) => ({
377
+ imported: { name: moduleName },
378
+ }));
379
+ setFileRef(
380
+ allImports,
381
+ allExports,
382
+ src,
383
+ file,
384
+ { value: importPath, loc: sourceLoc ? { start: sourceLoc } : undefined },
385
+ syntheticSpecifiers,
386
+ );
387
+ };
388
+
389
+ const setSyntheticExportRef = (
390
+ allImports,
391
+ allExports,
392
+ src,
393
+ file,
394
+ importPath,
395
+ modules,
396
+ sourceLoc,
397
+ ) => {
398
+ if (!importPath) {
399
+ return;
400
+ }
401
+ const safeModules = modules || [];
402
+ const syntheticSpecifiers = safeModules.map((moduleName) => ({
403
+ exported: { name: moduleName },
404
+ }));
405
+ setFileRef(
406
+ allImports,
407
+ allExports,
408
+ src,
409
+ file,
410
+ { value: importPath, loc: sourceLoc ? { start: sourceLoc } : undefined },
411
+ syntheticSpecifiers,
412
+ );
413
+ };
414
+
415
+ const getWasmExportMemberInfo = (astNode) => {
416
+ if (!astNode) {
417
+ return undefined;
418
+ }
419
+ if (astNode.type === "AssignmentExpression") {
420
+ return getWasmExportMemberInfo(astNode.right);
421
+ }
422
+ if (
423
+ astNode.type !== "MemberExpression" ||
424
+ astNode.object?.type !== "Identifier"
425
+ ) {
426
+ return undefined;
427
+ }
428
+ return {
429
+ aliasName: astNode.object.name,
430
+ exportName: getMemberExpressionPropertyName(astNode.property),
431
+ };
432
+ };
433
+
434
+ const getAssignmentTargetName = (astNode) => {
435
+ if (!astNode) {
436
+ return undefined;
437
+ }
438
+ if (astNode.type === "Identifier") {
439
+ return astNode.name;
440
+ }
441
+ if (
442
+ astNode.type === "MemberExpression" &&
443
+ astNode.object?.type === "Identifier" &&
444
+ astNode.object.name === "Module"
445
+ ) {
446
+ return getMemberExpressionPropertyName(astNode.property);
447
+ }
448
+ return undefined;
449
+ };
450
+
219
451
  /**
220
452
  * Check AST tree for any (j|tsx?) files and set a file
221
453
  * references for any import, require or dynamic import files.
222
454
  */
223
455
  const parseFileASTTree = (src, file, allImports, allExports) => {
224
456
  const ast = parse(fileToParseableCode(file), babelParserOptions);
457
+ const wasmBufferByVarName = new Map();
458
+ const wasmResultByVarName = new Map();
459
+ const wasmInstanceByVarName = new Map();
460
+ const wasiConstructorAliases = new Set(["WASI"]);
461
+ const wasiNamespaceAliases = new Set();
462
+ const wasiInstanceAliases = new Set();
463
+ const wasmPathLiterals = new Set();
464
+ const wasmExportAliases = new Set(["wasmExports"]);
225
465
  traverse.default(ast, {
226
466
  ImportDeclaration: (path) => {
227
467
  if (path?.node) {
@@ -233,6 +473,20 @@ const parseFileASTTree = (src, file, allImports, allExports) => {
233
473
  path.node.source,
234
474
  path.node.specifiers,
235
475
  );
476
+ const sourceValue = path.node.source?.value;
477
+ if (sourceValue === "node:wasi" || sourceValue === "wasi") {
478
+ for (const specifier of path.node.specifiers || []) {
479
+ if (
480
+ specifier.type === "ImportSpecifier" &&
481
+ specifier.imported?.name === "WASI"
482
+ ) {
483
+ wasiConstructorAliases.add(specifier.local?.name || "WASI");
484
+ }
485
+ if (specifier.type === "ImportNamespaceSpecifier") {
486
+ wasiNamespaceAliases.add(specifier.local?.name);
487
+ }
488
+ }
489
+ }
236
490
  }
237
491
  },
238
492
  // For require('') statements
@@ -250,6 +504,355 @@ const parseFileASTTree = (src, file, allImports, allExports) => {
250
504
  if (path?.node && path.node.callee.type === "Import") {
251
505
  setFileRef(allImports, allExports, src, file, path.node.arguments[0]);
252
506
  }
507
+ const wasmSourceLiteral = getWasmSourceFromCallExpression(
508
+ path?.node,
509
+ wasmBufferByVarName,
510
+ );
511
+ if (wasmSourceLiteral) {
512
+ wasmPathLiterals.add(wasmSourceLiteral);
513
+ setSyntheticImportRef(
514
+ allImports,
515
+ allExports,
516
+ src,
517
+ file,
518
+ wasmSourceLiteral,
519
+ [],
520
+ path.node.loc?.start,
521
+ );
522
+ }
523
+ if (
524
+ path?.node?.callee?.type === "MemberExpression" &&
525
+ path.node.callee.object?.type === "Identifier" &&
526
+ wasiInstanceAliases.has(path.node.callee.object.name)
527
+ ) {
528
+ const methodName = getMemberExpressionPropertyName(
529
+ path.node.callee.property,
530
+ );
531
+ if (methodName === "start" || methodName === "initialize") {
532
+ setSyntheticImportRef(
533
+ allImports,
534
+ allExports,
535
+ src,
536
+ file,
537
+ "node:wasi",
538
+ [methodName],
539
+ path.node.loc?.start,
540
+ );
541
+ }
542
+ }
543
+ },
544
+ ImportExpression: (path) => {
545
+ if (path?.node?.source) {
546
+ setFileRef(allImports, allExports, src, file, path.node.source);
547
+ }
548
+ },
549
+ VariableDeclarator: (path) => {
550
+ const idNode = path?.node?.id;
551
+ const initNode = unwrapAwait(path?.node?.init);
552
+ if (!idNode || !initNode) {
553
+ return;
554
+ }
555
+ if (
556
+ idNode.type === "Identifier" &&
557
+ initNode.type === "CallExpression" &&
558
+ initNode.callee?.type === "MemberExpression"
559
+ ) {
560
+ const calleePropertyName = getMemberExpressionPropertyName(
561
+ initNode.callee.property,
562
+ );
563
+ if (
564
+ calleePropertyName === "readFile" ||
565
+ calleePropertyName === "readFileSync"
566
+ ) {
567
+ const pathArg = initNode.arguments?.[0];
568
+ const wasmPath = getStringValue(pathArg);
569
+ if (isWasmPath(wasmPath)) {
570
+ wasmBufferByVarName.set(idNode.name, wasmPath);
571
+ wasmPathLiterals.add(wasmPath);
572
+ setSyntheticImportRef(
573
+ allImports,
574
+ allExports,
575
+ src,
576
+ file,
577
+ wasmPath,
578
+ [],
579
+ path.node.loc?.start,
580
+ );
581
+ }
582
+ }
583
+ const wasmSource = getWasmSourceFromInstantiateCall(
584
+ initNode,
585
+ wasmBufferByVarName,
586
+ );
587
+ if (wasmSource) {
588
+ wasmResultByVarName.set(idNode.name, wasmSource);
589
+ wasmPathLiterals.add(wasmSource);
590
+ setSyntheticImportRef(
591
+ allImports,
592
+ allExports,
593
+ src,
594
+ file,
595
+ wasmSource,
596
+ [],
597
+ path.node.loc?.start,
598
+ );
599
+ }
600
+ if (
601
+ initNode.callee?.type === "MemberExpression" &&
602
+ initNode.callee.object?.type === "Identifier" &&
603
+ wasiNamespaceAliases.has(initNode.callee.object.name) &&
604
+ getMemberExpressionPropertyName(initNode.callee.property) === "WASI"
605
+ ) {
606
+ wasiInstanceAliases.add(idNode.name);
607
+ setSyntheticImportRef(
608
+ allImports,
609
+ allExports,
610
+ src,
611
+ file,
612
+ "node:wasi",
613
+ ["WASI"],
614
+ path.node.loc?.start,
615
+ );
616
+ }
617
+ }
618
+ if (
619
+ idNode.type === "Identifier" &&
620
+ initNode.type === "CallExpression" &&
621
+ initNode.callee?.type === "Identifier" &&
622
+ wasiConstructorAliases.has(initNode.callee.name)
623
+ ) {
624
+ wasiInstanceAliases.add(idNode.name);
625
+ setSyntheticImportRef(
626
+ allImports,
627
+ allExports,
628
+ src,
629
+ file,
630
+ "node:wasi",
631
+ ["WASI"],
632
+ path.node.loc?.start,
633
+ );
634
+ }
635
+ if (idNode.type === "Identifier" && initNode.type === "NewExpression") {
636
+ if (
637
+ initNode.callee?.type === "Identifier" &&
638
+ wasiConstructorAliases.has(initNode.callee.name)
639
+ ) {
640
+ wasiInstanceAliases.add(idNode.name);
641
+ setSyntheticImportRef(
642
+ allImports,
643
+ allExports,
644
+ src,
645
+ file,
646
+ "node:wasi",
647
+ ["WASI"],
648
+ path.node.loc?.start,
649
+ );
650
+ }
651
+ if (
652
+ initNode.callee?.type === "MemberExpression" &&
653
+ initNode.callee.object?.type === "Identifier" &&
654
+ wasiNamespaceAliases.has(initNode.callee.object.name) &&
655
+ getMemberExpressionPropertyName(initNode.callee.property) === "WASI"
656
+ ) {
657
+ wasiInstanceAliases.add(idNode.name);
658
+ setSyntheticImportRef(
659
+ allImports,
660
+ allExports,
661
+ src,
662
+ file,
663
+ "node:wasi",
664
+ ["WASI"],
665
+ path.node.loc?.start,
666
+ );
667
+ }
668
+ }
669
+ if (idNode.type === "ObjectPattern") {
670
+ if (initNode.type === "CallExpression") {
671
+ const wasmSource = getWasmSourceFromInstantiateCall(
672
+ initNode,
673
+ wasmBufferByVarName,
674
+ );
675
+ if (wasmSource) {
676
+ wasmPathLiterals.add(wasmSource);
677
+ for (const prop of idNode.properties || []) {
678
+ if (
679
+ prop.type === "ObjectProperty" &&
680
+ getMemberExpressionPropertyName(prop.key) === "instance" &&
681
+ prop.value?.type === "Identifier"
682
+ ) {
683
+ wasmInstanceByVarName.set(prop.value.name, wasmSource);
684
+ }
685
+ }
686
+ setSyntheticImportRef(
687
+ allImports,
688
+ allExports,
689
+ src,
690
+ file,
691
+ wasmSource,
692
+ [],
693
+ path.node.loc?.start,
694
+ );
695
+ }
696
+ if (
697
+ initNode.callee?.type === "Identifier" &&
698
+ initNode.callee.name === "require"
699
+ ) {
700
+ const requiredModule = getStringValue(initNode.arguments?.[0]);
701
+ if (requiredModule === "node:wasi" || requiredModule === "wasi") {
702
+ for (const prop of idNode.properties || []) {
703
+ if (
704
+ prop.type === "ObjectProperty" &&
705
+ getMemberExpressionPropertyName(prop.key) === "WASI" &&
706
+ prop.value?.type === "Identifier"
707
+ ) {
708
+ wasiConstructorAliases.add(prop.value.name);
709
+ }
710
+ }
711
+ }
712
+ }
713
+ }
714
+ if (initNode.type === "MemberExpression") {
715
+ const exportNames = getNamedImportsFromObjectPattern(idNode);
716
+ if (!exportNames.length) {
717
+ return;
718
+ }
719
+ if (
720
+ initNode.object?.type === "MemberExpression" &&
721
+ initNode.object.object?.type === "Identifier" &&
722
+ getMemberExpressionPropertyName(initNode.object.property) ===
723
+ "instance" &&
724
+ getMemberExpressionPropertyName(initNode.property) === "exports"
725
+ ) {
726
+ const wasmSource = wasmResultByVarName.get(
727
+ initNode.object.object.name,
728
+ );
729
+ if (wasmSource) {
730
+ setSyntheticImportRef(
731
+ allImports,
732
+ allExports,
733
+ src,
734
+ file,
735
+ wasmSource,
736
+ exportNames,
737
+ path.node.loc?.start,
738
+ );
739
+ }
740
+ }
741
+ if (
742
+ initNode.object?.type === "Identifier" &&
743
+ getMemberExpressionPropertyName(initNode.property) === "exports"
744
+ ) {
745
+ const wasmSource = wasmInstanceByVarName.get(initNode.object.name);
746
+ if (wasmSource) {
747
+ setSyntheticImportRef(
748
+ allImports,
749
+ allExports,
750
+ src,
751
+ file,
752
+ wasmSource,
753
+ exportNames,
754
+ path.node.loc?.start,
755
+ );
756
+ }
757
+ }
758
+ }
759
+ }
760
+ if (
761
+ idNode.type === "Identifier" &&
762
+ initNode.type === "MemberExpression" &&
763
+ initNode.object?.type === "Identifier" &&
764
+ getMemberExpressionPropertyName(initNode.property) === "instance"
765
+ ) {
766
+ const wasmSource = wasmResultByVarName.get(initNode.object.name);
767
+ if (wasmSource) {
768
+ wasmInstanceByVarName.set(idNode.name, wasmSource);
769
+ }
770
+ }
771
+ if (
772
+ idNode.type === "Identifier" &&
773
+ initNode.type === "CallExpression" &&
774
+ initNode.callee?.type === "MemberExpression" &&
775
+ initNode.callee.object?.type === "Identifier" &&
776
+ initNode.callee.object.name === "WebAssembly"
777
+ ) {
778
+ const wasmSource = getWasmSourceFromInstantiateCall(
779
+ initNode,
780
+ wasmBufferByVarName,
781
+ );
782
+ if (wasmSource) {
783
+ wasmResultByVarName.set(idNode.name, wasmSource);
784
+ wasmPathLiterals.add(wasmSource);
785
+ }
786
+ }
787
+ },
788
+ AssignmentExpression: (path) => {
789
+ const wasmExportMemberInfo = getWasmExportMemberInfo(path?.node?.right);
790
+ if (!wasmExportMemberInfo?.exportName) {
791
+ return;
792
+ }
793
+ if (!wasmExportAliases.has(wasmExportMemberInfo.aliasName)) {
794
+ return;
795
+ }
796
+ if (!wasmPathLiterals.size) {
797
+ return;
798
+ }
799
+ for (const wasmPath of wasmPathLiterals) {
800
+ setSyntheticImportRef(
801
+ allImports,
802
+ allExports,
803
+ src,
804
+ file,
805
+ wasmPath,
806
+ [wasmExportMemberInfo.exportName],
807
+ path.node.loc?.start,
808
+ );
809
+ }
810
+ const targetName = getAssignmentTargetName(path?.node?.left);
811
+ if (!targetName) {
812
+ return;
813
+ }
814
+ for (const wasmPath of wasmPathLiterals) {
815
+ setSyntheticExportRef(
816
+ allImports,
817
+ allExports,
818
+ src,
819
+ file,
820
+ wasmPath,
821
+ [targetName],
822
+ path.node.loc?.start,
823
+ );
824
+ }
825
+ },
826
+ NewExpression: (path) => {
827
+ if (path?.node?.callee?.type === "Identifier") {
828
+ if (wasiConstructorAliases.has(path.node.callee.name)) {
829
+ setSyntheticImportRef(
830
+ allImports,
831
+ allExports,
832
+ src,
833
+ file,
834
+ "node:wasi",
835
+ ["WASI"],
836
+ path.node.loc?.start,
837
+ );
838
+ }
839
+ }
840
+ if (
841
+ path?.node?.callee?.type === "MemberExpression" &&
842
+ path.node.callee.object?.type === "Identifier" &&
843
+ wasiNamespaceAliases.has(path.node.callee.object.name) &&
844
+ getMemberExpressionPropertyName(path.node.callee.property) === "WASI"
845
+ ) {
846
+ setSyntheticImportRef(
847
+ allImports,
848
+ allExports,
849
+ src,
850
+ file,
851
+ "node:wasi",
852
+ ["WASI"],
853
+ path.node.loc?.start,
854
+ );
855
+ }
253
856
  },
254
857
  // Use for export barrells
255
858
  ExportAllDeclaration: (path) => {
@@ -286,13 +889,340 @@ const getAllSrcJSAndTSFiles = (src, deep) =>
286
889
  getAllFiles(deep, src, ".svelte"),
287
890
  ]);
288
891
 
892
+ export const CHROMIUM_EXTENSION_CAPABILITY_CATEGORIES = [
893
+ "fileAccess",
894
+ "deviceAccess",
895
+ "network",
896
+ "bluetooth",
897
+ "accessibility",
898
+ "codeInjection",
899
+ "fingerprinting",
900
+ ];
901
+
902
+ const EXTENSION_CAPABILITY_CHAIN_PATTERNS = {
903
+ fileAccess: [
904
+ /^(chrome|browser)\.(downloads|fileSystem|fileBrowserHandler|fileManagerPrivate)\b/i,
905
+ /^(window\.)?show(Open|Save|Directory)FilePicker$/i,
906
+ ],
907
+ deviceAccess: [
908
+ /^(chrome|browser)\.(usb|hid|serial|nfc|mediaGalleries|gcdPrivate|bluetooth|bluetoothPrivate)\b/i,
909
+ ],
910
+ network: [
911
+ /^(chrome|browser)\.(webRequest|declarativeNetRequest|proxy|webNavigation|socket)\b/i,
912
+ /^(window\.)?(fetch|WebSocket|EventSource)$/i,
913
+ /^(XMLHttpRequest)\b/i,
914
+ /^navigator\.sendBeacon$/i,
915
+ ],
916
+ bluetooth: [/^(chrome|browser)\.(bluetooth|bluetoothPrivate)\b/i],
917
+ accessibility: [
918
+ /^(chrome|browser)\.(accessibilityFeatures|accessibilityPrivate|automation)\b/i,
919
+ ],
920
+ codeInjection: [
921
+ /^(chrome|browser)\.(scripting\.executeScript|tabs\.executeScript|userScripts|debugger)\b/i,
922
+ /^(window\.)?(eval|Function)$/i,
923
+ /^document\.write$/i,
924
+ ],
925
+ fingerprinting: [
926
+ /^navigator\.(userAgent|platform|languages|language|hardwareConcurrency|deviceMemory|plugins|userAgentData)\b/i,
927
+ /^(screen\.)?(width|height|availWidth|availHeight|colorDepth|pixelDepth)$/i,
928
+ /^(window\.)?(AudioContext|OfflineAudioContext|RTCPeerConnection)$/i,
929
+ /^(canvas|[a-zA-Z_$][a-zA-Z0-9_$]*\.(getImageData|toDataURL|measureText))$/i,
930
+ ],
931
+ };
932
+
933
+ const EXTENSION_CAPABILITY_IDENTIFIER_PATTERNS = {
934
+ network: /^(fetch|WebSocket|EventSource|XMLHttpRequest)$/i,
935
+ codeInjection: /^(eval|Function)$/i,
936
+ fingerprinting: /^(AudioContext|OfflineAudioContext|RTCPeerConnection)$/i,
937
+ };
938
+
939
+ const SUSPICIOUS_JS_PROCESS_MODULES = new Set([
940
+ "child_process",
941
+ "node:child_process",
942
+ ]);
943
+
944
+ const SUSPICIOUS_JS_NETWORK_MODULES = new Set([
945
+ "axios",
946
+ "got",
947
+ "http",
948
+ "https",
949
+ "net",
950
+ "node-fetch",
951
+ "node:http",
952
+ "node:https",
953
+ "node:net",
954
+ "node:tls",
955
+ "tls",
956
+ "undici",
957
+ ]);
958
+
959
+ const SUSPICIOUS_JS_EXECUTION_MEMBERS = new Set([
960
+ "exec",
961
+ "execFile",
962
+ "execFileSync",
963
+ "execSync",
964
+ "fork",
965
+ "spawn",
966
+ "spawnSync",
967
+ ]);
968
+
969
+ const SUSPICIOUS_JS_NETWORK_MEMBERS = new Set([
970
+ "fetch",
971
+ "get",
972
+ "post",
973
+ "put",
974
+ "patch",
975
+ "request",
976
+ ]);
977
+
978
+ const SUSPICIOUS_JS_LONG_BASE64_PATTERN = /\b[A-Za-z0-9+/]{80,}={0,2}\b/;
979
+
980
+ const getLiteralStringValue = (node) => {
981
+ if (!node) {
982
+ return undefined;
983
+ }
984
+ if (node.type === "StringLiteral") {
985
+ return node.value;
986
+ }
987
+ if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
988
+ return node.quasis.map((quasi) => quasi.value.cooked || "").join("");
989
+ }
990
+ return undefined;
991
+ };
992
+
993
+ const addSuspiciousLiteralIndicators = (obfuscationIndicators, rawValue) => {
994
+ if (!rawValue || typeof rawValue !== "string") {
995
+ return;
996
+ }
997
+ if (SUSPICIOUS_JS_LONG_BASE64_PATTERN.test(rawValue)) {
998
+ obfuscationIndicators.add("long-base64-literal");
999
+ }
1000
+ };
1001
+
1002
+ const trackSuspiciousModuleReference = (
1003
+ moduleName,
1004
+ localName,
1005
+ executionIndicators,
1006
+ networkIndicators,
1007
+ processAliases,
1008
+ networkAliases,
1009
+ ) => {
1010
+ if (!moduleName || typeof moduleName !== "string") {
1011
+ return;
1012
+ }
1013
+ if (SUSPICIOUS_JS_PROCESS_MODULES.has(moduleName)) {
1014
+ executionIndicators.add("child-process-import");
1015
+ if (localName) {
1016
+ processAliases.add(localName);
1017
+ }
1018
+ }
1019
+ if (SUSPICIOUS_JS_NETWORK_MODULES.has(moduleName)) {
1020
+ networkIndicators.add("network-module-import");
1021
+ if (localName) {
1022
+ networkAliases.add(localName);
1023
+ }
1024
+ }
1025
+ };
1026
+
1027
+ const getMemberChainString = (node) => {
1028
+ if (!node) {
1029
+ return "";
1030
+ }
1031
+ if (node.type === "Identifier") {
1032
+ return node.name;
1033
+ }
1034
+ if (node.type === "ThisExpression") {
1035
+ return "this";
1036
+ }
1037
+ if (node.type === "StringLiteral") {
1038
+ return node.value;
1039
+ }
1040
+ if (node.type === "MetaProperty") {
1041
+ const metaName = node.meta?.name || "";
1042
+ const propertyName = node.property?.name || "";
1043
+ return [metaName, propertyName].filter(Boolean).join(".");
1044
+ }
1045
+ if (node.type === "CallExpression") {
1046
+ return getMemberChainString(node.callee);
1047
+ }
1048
+ if (node.type === "OptionalCallExpression") {
1049
+ return getMemberChainString(node.callee);
1050
+ }
1051
+ if (
1052
+ node.type !== "MemberExpression" &&
1053
+ node.type !== "OptionalMemberExpression"
1054
+ ) {
1055
+ return "";
1056
+ }
1057
+ const objectChain = getMemberChainString(node.object);
1058
+ const propertyChain = getMemberChainString(node.property);
1059
+ if (objectChain && propertyChain) {
1060
+ return `${objectChain}.${propertyChain}`;
1061
+ }
1062
+ return objectChain || propertyChain || "";
1063
+ };
1064
+
1065
+ function analyzeSuspiciousJsSource(source) {
1066
+ const executionIndicators = new Set();
1067
+ const networkIndicators = new Set();
1068
+ const obfuscationIndicators = new Set();
1069
+ const processAliases = new Set();
1070
+ const networkAliases = new Set();
1071
+ let ast;
1072
+ try {
1073
+ ast = parse(source, babelParserOptions);
1074
+ } catch {
1075
+ return {
1076
+ executionIndicators: [],
1077
+ indicators: [],
1078
+ networkIndicators: [],
1079
+ obfuscationIndicators: [],
1080
+ };
1081
+ }
1082
+ traverse.default(ast, {
1083
+ ImportDeclaration: (path) => {
1084
+ const moduleName = getLiteralStringValue(path?.node?.source);
1085
+ path.node.specifiers.forEach((specifier) => {
1086
+ trackSuspiciousModuleReference(
1087
+ moduleName,
1088
+ specifier?.local?.name,
1089
+ executionIndicators,
1090
+ networkIndicators,
1091
+ processAliases,
1092
+ networkAliases,
1093
+ );
1094
+ });
1095
+ if (!path.node.specifiers?.length) {
1096
+ trackSuspiciousModuleReference(
1097
+ moduleName,
1098
+ undefined,
1099
+ executionIndicators,
1100
+ networkIndicators,
1101
+ processAliases,
1102
+ networkAliases,
1103
+ );
1104
+ }
1105
+ },
1106
+ VariableDeclarator: (path) => {
1107
+ const init = path?.node?.init;
1108
+ if (
1109
+ init?.type === "CallExpression" &&
1110
+ init.callee?.type === "Identifier" &&
1111
+ init.callee.name === "require"
1112
+ ) {
1113
+ const moduleName = getLiteralStringValue(init.arguments?.[0]);
1114
+ const localName =
1115
+ path?.node?.id?.type === "Identifier" ? path.node.id.name : undefined;
1116
+ trackSuspiciousModuleReference(
1117
+ moduleName,
1118
+ localName,
1119
+ executionIndicators,
1120
+ networkIndicators,
1121
+ processAliases,
1122
+ networkAliases,
1123
+ );
1124
+ }
1125
+ },
1126
+ CallExpression: (path) => {
1127
+ const callee = path?.node?.callee;
1128
+ const calleeChain = getMemberChainString(callee);
1129
+ if (callee?.type === "Identifier") {
1130
+ if (callee.name === "eval") {
1131
+ executionIndicators.add("eval");
1132
+ }
1133
+ if (callee.name === "atob") {
1134
+ obfuscationIndicators.add("atob");
1135
+ }
1136
+ if (["fetch", "axios", "got"].includes(callee.name)) {
1137
+ networkIndicators.add("network-request");
1138
+ }
1139
+ }
1140
+ if (calleeChain === "Buffer.from") {
1141
+ const encodingValue = getLiteralStringValue(path.node.arguments?.[1]);
1142
+ if (encodingValue?.toLowerCase() === "base64") {
1143
+ obfuscationIndicators.add("buffer-base64");
1144
+ }
1145
+ }
1146
+ if (calleeChain === "String.fromCharCode") {
1147
+ obfuscationIndicators.add("string-from-char-code");
1148
+ }
1149
+ if (calleeChain === "vm.runInNewContext") {
1150
+ executionIndicators.add("vm-run-context");
1151
+ obfuscationIndicators.add("vm-run-context");
1152
+ }
1153
+ if (calleeChain === "vm.runInThisContext") {
1154
+ executionIndicators.add("vm-run-context");
1155
+ obfuscationIndicators.add("vm-run-context");
1156
+ }
1157
+ if (callee?.type === "MemberExpression") {
1158
+ const objectName = getMemberChainString(callee.object);
1159
+ const propertyName = getMemberChainString(callee.property);
1160
+ if (
1161
+ objectName &&
1162
+ processAliases.has(objectName) &&
1163
+ SUSPICIOUS_JS_EXECUTION_MEMBERS.has(propertyName)
1164
+ ) {
1165
+ executionIndicators.add("child-process");
1166
+ }
1167
+ if (
1168
+ objectName &&
1169
+ networkAliases.has(objectName) &&
1170
+ SUSPICIOUS_JS_NETWORK_MEMBERS.has(propertyName)
1171
+ ) {
1172
+ networkIndicators.add("network-request");
1173
+ }
1174
+ }
1175
+ if (
1176
+ callee?.type === "Identifier" &&
1177
+ callee.name === "require" &&
1178
+ path.node.arguments?.length
1179
+ ) {
1180
+ const moduleName = getLiteralStringValue(path.node.arguments[0]);
1181
+ trackSuspiciousModuleReference(
1182
+ moduleName,
1183
+ undefined,
1184
+ executionIndicators,
1185
+ networkIndicators,
1186
+ processAliases,
1187
+ networkAliases,
1188
+ );
1189
+ }
1190
+ },
1191
+ NewExpression: (path) => {
1192
+ const calleeChain = getMemberChainString(path?.node?.callee);
1193
+ if (calleeChain === "Function") {
1194
+ executionIndicators.add("function-constructor");
1195
+ }
1196
+ },
1197
+ StringLiteral: (path) => {
1198
+ addSuspiciousLiteralIndicators(obfuscationIndicators, path?.node?.value);
1199
+ },
1200
+ TemplateElement: (path) => {
1201
+ addSuspiciousLiteralIndicators(
1202
+ obfuscationIndicators,
1203
+ path?.node?.value?.raw,
1204
+ );
1205
+ },
1206
+ });
1207
+ const indicators = [
1208
+ ...obfuscationIndicators,
1209
+ ...executionIndicators,
1210
+ ...networkIndicators,
1211
+ ].sort();
1212
+ return {
1213
+ executionIndicators: Array.from(executionIndicators).sort(),
1214
+ indicators,
1215
+ networkIndicators: Array.from(networkIndicators).sort(),
1216
+ obfuscationIndicators: Array.from(obfuscationIndicators).sort(),
1217
+ };
1218
+ }
1219
+
289
1220
  /**
290
1221
  * Find all imports and exports
291
1222
  */
292
1223
  export const findJSImportsExports = async (src, deep) => {
293
1224
  const allImports = {};
294
1225
  const allExports = {};
295
- const errFiles = [];
296
1226
  try {
297
1227
  const promiseMap = await getAllSrcJSAndTSFiles(src, deep);
298
1228
  const srcFiles = promiseMap.flat();
@@ -300,7 +1230,7 @@ export const findJSImportsExports = async (src, deep) => {
300
1230
  try {
301
1231
  parseFileASTTree(src, file, allImports, allExports);
302
1232
  } catch (_err) {
303
- errFiles.push(file);
1233
+ // ignore parse failures
304
1234
  }
305
1235
  }
306
1236
  return { allImports, allExports };
@@ -308,3 +1238,120 @@ export const findJSImportsExports = async (src, deep) => {
308
1238
  return { allImports, allExports };
309
1239
  }
310
1240
  };
1241
+
1242
+ /**
1243
+ * Detect suspicious obfuscation, execution, and network indicators in a single
1244
+ * JavaScript/TypeScript source file using Babel AST analysis.
1245
+ *
1246
+ * @param {string} filePath Source file path
1247
+ * @returns {{executionIndicators: string[], indicators: string[], networkIndicators: string[], obfuscationIndicators: string[]}}
1248
+ */
1249
+ export const analyzeSuspiciousJsFile = (filePath) => {
1250
+ let source;
1251
+ try {
1252
+ source = fileToParseableCode(filePath);
1253
+ } catch {
1254
+ return {
1255
+ executionIndicators: [],
1256
+ indicators: [],
1257
+ networkIndicators: [],
1258
+ obfuscationIndicators: [],
1259
+ };
1260
+ }
1261
+ return analyzeSuspiciousJsSource(source);
1262
+ };
1263
+
1264
+ /**
1265
+ * Detect browser-extension capability signals from source code using Babel AST analysis.
1266
+ *
1267
+ * @param {string} src Path to the extension source directory
1268
+ * @param {boolean} deep When true, includes node_modules and nested directories
1269
+ * @returns {{capabilities: string[], indicators: Object<string, string[]>}}
1270
+ * `indicators` is keyed by capability category name and contains arrays of
1271
+ * detected signal strings (for example property chains and call names).
1272
+ */
1273
+ export const detectExtensionCapabilities = (src, deep = false) => {
1274
+ const indicators = {};
1275
+ for (const category of CHROMIUM_EXTENSION_CAPABILITY_CATEGORIES) {
1276
+ indicators[category] = new Set();
1277
+ }
1278
+ let srcFiles = [];
1279
+ try {
1280
+ srcFiles = [
1281
+ ...getAllFiles(deep, src, ".js"),
1282
+ ...getAllFiles(deep, src, ".jsx"),
1283
+ ...getAllFiles(deep, src, ".cjs"),
1284
+ ...getAllFiles(deep, src, ".mjs"),
1285
+ ...getAllFiles(deep, src, ".ts"),
1286
+ ...getAllFiles(deep, src, ".tsx"),
1287
+ ...getAllFiles(deep, src, ".vue"),
1288
+ ...getAllFiles(deep, src, ".svelte"),
1289
+ ];
1290
+ } catch (_err) {
1291
+ return { capabilities: [], indicators: {} };
1292
+ }
1293
+ const addSignalByPatterns = (rawSignal, patternMap) => {
1294
+ const signal = String(rawSignal || "").trim();
1295
+ if (!signal) {
1296
+ return;
1297
+ }
1298
+ for (const category of Object.keys(patternMap)) {
1299
+ const categoryPatterns = patternMap[category];
1300
+ const safePatterns = Array.isArray(categoryPatterns)
1301
+ ? categoryPatterns
1302
+ : [categoryPatterns];
1303
+ if (safePatterns.some((pattern) => pattern?.test(signal))) {
1304
+ indicators[category].add(signal);
1305
+ }
1306
+ }
1307
+ };
1308
+ const addSignal = (rawSignal) => {
1309
+ addSignalByPatterns(rawSignal, EXTENSION_CAPABILITY_CHAIN_PATTERNS);
1310
+ };
1311
+ const addIdentifierSignal = (rawSignal) => {
1312
+ addSignalByPatterns(rawSignal, EXTENSION_CAPABILITY_IDENTIFIER_PATTERNS);
1313
+ };
1314
+ for (const file of srcFiles) {
1315
+ try {
1316
+ const ast = parse(fileToParseableCode(file), babelParserOptions);
1317
+ traverse.default(ast, {
1318
+ MemberExpression: (path) => {
1319
+ addSignal(getMemberChainString(path?.node));
1320
+ },
1321
+ OptionalMemberExpression: (path) => {
1322
+ addSignal(getMemberChainString(path?.node));
1323
+ },
1324
+ CallExpression: (path) => {
1325
+ addSignal(getMemberChainString(path?.node?.callee));
1326
+ if (path?.node?.callee?.type === "Identifier") {
1327
+ addIdentifierSignal(path.node.callee.name);
1328
+ }
1329
+ },
1330
+ OptionalCallExpression: (path) => {
1331
+ addSignal(getMemberChainString(path?.node?.callee));
1332
+ if (path?.node?.callee?.type === "Identifier") {
1333
+ addIdentifierSignal(path.node.callee.name);
1334
+ }
1335
+ },
1336
+ NewExpression: (path) => {
1337
+ addSignal(getMemberChainString(path?.node?.callee));
1338
+ if (path?.node?.callee?.type === "Identifier") {
1339
+ addIdentifierSignal(path.node.callee.name);
1340
+ }
1341
+ },
1342
+ });
1343
+ } catch (_err) {
1344
+ // Skip parse failures and continue scanning
1345
+ }
1346
+ }
1347
+ const capabilityList = [];
1348
+ const indicatorMap = {};
1349
+ for (const category of CHROMIUM_EXTENSION_CAPABILITY_CATEGORIES) {
1350
+ const sortedSignals = Array.from(indicators[category]).sort();
1351
+ if (sortedSignals.length) {
1352
+ capabilityList.push(category);
1353
+ indicatorMap[category] = sortedSignals;
1354
+ }
1355
+ }
1356
+ return { capabilities: capabilityList, indicators: indicatorMap };
1357
+ };