@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.
- package/README.md +242 -90
- package/bin/audit.js +191 -0
- package/bin/cdxgen.js +532 -168
- package/bin/convert.js +99 -0
- package/bin/evinse.js +23 -0
- package/bin/repl.js +339 -8
- package/bin/sign.js +8 -0
- package/bin/validate.js +8 -0
- package/bin/verify.js +8 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/lolbas-index.json +150 -0
- package/data/queries-darwin.json +63 -3
- package/data/queries-win.json +45 -3
- package/data/queries.json +74 -2
- package/data/rules/chrome-extensions.yaml +240 -0
- package/data/rules/ci-permissions.yaml +478 -18
- package/data/rules/container-risk.yaml +270 -0
- package/data/rules/obom-runtime.yaml +891 -0
- package/data/rules/package-integrity.yaml +49 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-model-v3.0.1.jsonld +15999 -0
- package/lib/audit/index.js +1924 -0
- package/lib/audit/index.poku.js +1488 -0
- package/lib/audit/progress.js +137 -0
- package/lib/audit/progress.poku.js +188 -0
- package/lib/audit/reporters.js +618 -0
- package/lib/audit/scoring.js +310 -0
- package/lib/audit/scoring.poku.js +341 -0
- package/lib/audit/targets.js +260 -0
- package/lib/audit/targets.poku.js +331 -0
- package/lib/cli/index.js +276 -68
- package/lib/cli/index.poku.js +368 -0
- package/lib/helpers/analyzer.js +1052 -5
- package/lib/helpers/analyzer.poku.js +301 -0
- package/lib/helpers/annotationFormatter.js +49 -0
- package/lib/helpers/annotationFormatter.poku.js +44 -0
- package/lib/helpers/bomUtils.js +36 -0
- package/lib/helpers/bomUtils.poku.js +51 -0
- package/lib/helpers/caxa.js +2 -2
- package/lib/helpers/chromextutils.js +1153 -0
- package/lib/helpers/chromextutils.poku.js +493 -0
- package/lib/helpers/ciParsers/githubActions.js +1632 -45
- package/lib/helpers/ciParsers/githubActions.poku.js +853 -1
- package/lib/helpers/containerRisk.js +186 -0
- package/lib/helpers/containerRisk.poku.js +52 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +245 -61
- package/lib/helpers/display.poku.js +162 -2
- package/lib/helpers/exportUtils.js +123 -0
- package/lib/helpers/exportUtils.poku.js +60 -0
- package/lib/helpers/formulationParsers.js +69 -0
- package/lib/helpers/formulationParsers.poku.js +44 -0
- package/lib/helpers/gtfobins.js +189 -0
- package/lib/helpers/gtfobins.poku.js +49 -0
- package/lib/helpers/lolbas.js +267 -0
- package/lib/helpers/lolbas.poku.js +39 -0
- package/lib/helpers/osqueryTransform.js +84 -0
- package/lib/helpers/osqueryTransform.poku.js +49 -0
- package/lib/helpers/provenanceUtils.js +193 -0
- package/lib/helpers/provenanceUtils.poku.js +145 -0
- package/lib/helpers/pylockutils.js +281 -0
- package/lib/helpers/pylockutils.poku.js +48 -0
- package/lib/helpers/registryProvenance.js +793 -0
- package/lib/helpers/registryProvenance.poku.js +452 -0
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/source.js +1267 -0
- package/lib/helpers/source.poku.js +771 -0
- package/lib/helpers/spdxUtils.js +97 -0
- package/lib/helpers/spdxUtils.poku.js +70 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/unicodeScan.js +147 -0
- package/lib/helpers/unicodeScan.poku.js +45 -0
- package/lib/helpers/utils.js +882 -136
- package/lib/helpers/utils.poku.js +995 -91
- package/lib/managers/binary.js +29 -5
- package/lib/managers/docker.js +179 -52
- package/lib/managers/docker.poku.js +327 -28
- package/lib/managers/oci.js +107 -23
- package/lib/managers/oci.poku.js +132 -0
- package/lib/server/openapi.yaml +50 -0
- package/lib/server/server.js +228 -331
- package/lib/server/server.poku.js +220 -5
- package/lib/stages/postgen/annotator.js +7 -0
- package/lib/stages/postgen/annotator.poku.js +40 -0
- package/lib/stages/postgen/auditBom.js +20 -5
- package/lib/stages/postgen/auditBom.poku.js +1729 -67
- package/lib/stages/postgen/postgen.js +40 -0
- package/lib/stages/postgen/postgen.poku.js +47 -0
- package/lib/stages/postgen/ruleEngine.js +80 -2
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/spdxConverter.poku.js +341 -0
- package/lib/validator/bomValidator.js +232 -0
- package/lib/validator/bomValidator.poku.js +70 -0
- package/lib/validator/complianceRules.js +70 -7
- package/lib/validator/complianceRules.poku.js +30 -0
- package/lib/validator/reporters/annotations.js +2 -2
- package/lib/validator/reporters/console.js +13 -2
- package/lib/validator/reporters.poku.js +13 -0
- package/package.json +10 -8
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +115 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +27 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +35 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +35 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +63 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts +13 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomUtils.d.ts +5 -0
- package/types/lib/helpers/bomUtils.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts +97 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +3 -8
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/containerRisk.d.ts +17 -0
- package/types/lib/helpers/containerRisk.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +4 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/exportUtils.d.ts +40 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/gtfobins.d.ts +17 -0
- package/types/lib/helpers/gtfobins.d.ts.map +1 -0
- package/types/lib/helpers/lolbas.d.ts +16 -0
- package/types/lib/helpers/lolbas.d.ts.map +1 -0
- package/types/lib/helpers/osqueryTransform.d.ts +7 -0
- package/types/lib/helpers/osqueryTransform.d.ts.map +1 -0
- package/types/lib/helpers/provenanceUtils.d.ts +90 -0
- package/types/lib/helpers/provenanceUtils.d.ts.map +1 -0
- package/types/lib/helpers/pylockutils.d.ts +51 -0
- package/types/lib/helpers/pylockutils.d.ts.map +1 -0
- package/types/lib/helpers/registryProvenance.d.ts +17 -0
- package/types/lib/helpers/registryProvenance.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/source.d.ts +141 -0
- package/types/lib/helpers/source.d.ts.map +1 -0
- package/types/lib/helpers/spdxUtils.d.ts +2 -0
- package/types/lib/helpers/spdxUtils.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/unicodeScan.d.ts +46 -0
- package/types/lib/helpers/unicodeScan.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +30 -11
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +0 -35
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +1 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/bin/dependencies.d.ts +0 -3
- package/types/bin/dependencies.d.ts.map +0 -1
- package/types/bin/licenses.d.ts +0 -3
- package/types/bin/licenses.d.ts.map +0 -1
package/lib/helpers/analyzer.js
CHANGED
|
@@ -145,8 +145,8 @@ const setFileRef = (
|
|
|
145
145
|
exportedModules,
|
|
146
146
|
isExternal: true,
|
|
147
147
|
fileName: fileRelativeLoc,
|
|
148
|
-
lineNumber: sourceLoc?.line
|
|
149
|
-
columnNumber: sourceLoc?.column
|
|
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]*?)(
|
|
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
|
-
|
|
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
|
+
};
|