@nodesecure/js-x-ray 9.0.0 → 9.1.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 (62) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +9 -209
  3. package/package.json +62 -77
  4. package/src/{AstAnalyser.js → AstAnalyser.ts} +283 -222
  5. package/src/{Deobfuscator.js → Deobfuscator.ts} +228 -195
  6. package/src/{EntryFilesAnalyser.js → EntryFilesAnalyser.ts} +206 -167
  7. package/src/JsSourceParser.ts +77 -0
  8. package/src/NodeCounter.ts +90 -0
  9. package/src/{ProbeRunner.js → ProbeRunner.ts} +167 -144
  10. package/src/SourceFile.ts +226 -0
  11. package/src/index.ts +5 -0
  12. package/src/obfuscators/freejsobfuscator.ts +17 -0
  13. package/src/obfuscators/{jjencode.js → jjencode.ts} +39 -27
  14. package/src/obfuscators/jsfuck.ts +19 -0
  15. package/src/obfuscators/{obfuscator-io.js → obfuscator-io.ts} +25 -13
  16. package/src/obfuscators/{trojan-source.js → trojan-source.ts} +3 -1
  17. package/src/probes/{isArrayExpression.js → isArrayExpression.ts} +12 -3
  18. package/src/probes/{isBinaryExpression.js → isBinaryExpression.ts} +74 -55
  19. package/src/probes/isESMExport.ts +50 -0
  20. package/src/probes/{isFetch.js → isFetch.ts} +28 -19
  21. package/src/probes/isImportDeclaration.ts +58 -0
  22. package/src/probes/{isLiteral.js → isLiteral.ts} +91 -70
  23. package/src/probes/isLiteralRegex.ts +42 -0
  24. package/src/probes/{isRegexObject.js → isRegexObject.ts} +71 -49
  25. package/src/probes/isRequire/RequireCallExpressionWalker.ts +142 -0
  26. package/src/probes/isRequire/{isRequire.js → isRequire.ts} +195 -148
  27. package/src/probes/isSerializeEnv.ts +65 -0
  28. package/src/probes/isSyncIO.ts +96 -0
  29. package/src/probes/isUnsafeCallee.ts +89 -0
  30. package/src/probes/isUnsafeCommand.ts +133 -0
  31. package/src/probes/isWeakCrypto.ts +69 -0
  32. package/src/types/estree.ts +35 -0
  33. package/src/utils/extractNode.ts +22 -0
  34. package/src/utils/index.ts +4 -0
  35. package/src/utils/{exportAssignmentHasRequireLeave.js → isOneLineExpressionExport.ts} +70 -40
  36. package/src/utils/notNullOrUndefined.ts +5 -0
  37. package/src/utils/toArrayLocation.ts +22 -0
  38. package/src/warnings.ts +146 -0
  39. package/index.d.ts +0 -46
  40. package/index.js +0 -4
  41. package/src/JsSourceParser.js +0 -57
  42. package/src/NodeCounter.js +0 -76
  43. package/src/SourceFile.js +0 -147
  44. package/src/obfuscators/freejsobfuscator.js +0 -9
  45. package/src/obfuscators/jsfuck.js +0 -11
  46. package/src/probes/isESMExport.js +0 -31
  47. package/src/probes/isImportDeclaration.js +0 -33
  48. package/src/probes/isLiteralRegex.js +0 -31
  49. package/src/probes/isRequire/RequireCallExpressionWalker.js +0 -93
  50. package/src/probes/isUnsafeCallee.js +0 -35
  51. package/src/probes/isWeakCrypto.js +0 -37
  52. package/src/utils/extractNode.js +0 -14
  53. package/src/utils/index.js +0 -8
  54. package/src/utils/isNode.js +0 -5
  55. package/src/utils/isOneLineExpressionExport.js +0 -24
  56. package/src/utils/isUnsafeCallee.js +0 -28
  57. package/src/utils/notNullOrUndefined.js +0 -3
  58. package/src/utils/rootLocation.js +0 -3
  59. package/src/utils/toArrayLocation.js +0 -11
  60. package/src/warnings.js +0 -77
  61. package/types/api.d.ts +0 -177
  62. package/types/warnings.d.ts +0 -36
@@ -0,0 +1,146 @@
1
+ // Import Third-party Dependencies
2
+ import type { ESTree } from "meriyah";
3
+
4
+ // Import Internal Dependencies
5
+ import {
6
+ toArrayLocation,
7
+ rootLocation,
8
+ type SourceArrayLocation
9
+ } from "./utils/toArrayLocation.js";
10
+ import { notNullOrUndefined } from "./utils/notNullOrUndefined.js";
11
+
12
+ export type OptionalWarningName =
13
+ | "synchronous-io";
14
+
15
+ export type WarningName =
16
+ | "parsing-error"
17
+ | "encoded-literal"
18
+ | "unsafe-regex"
19
+ | "unsafe-stmt"
20
+ | "short-identifiers"
21
+ | "suspicious-literal"
22
+ | "suspicious-file"
23
+ | "obfuscated-code"
24
+ | "weak-crypto"
25
+ | "shady-link"
26
+ | "unsafe-command"
27
+ | "unsafe-import"
28
+ | "serialize-environment"
29
+ | OptionalWarningName;
30
+
31
+ export interface Warning<T = WarningName> {
32
+ kind: T | (string & {});
33
+ file?: string;
34
+ value: string | null;
35
+ source: string;
36
+ location: null | SourceArrayLocation | SourceArrayLocation[];
37
+ i18n: string;
38
+ severity: "Information" | "Warning" | "Critical";
39
+ experimental?: boolean;
40
+ }
41
+
42
+ export const warnings = Object.freeze({
43
+ "parsing-error": {
44
+ i18n: "sast_warnings.parsing_error",
45
+ severity: "Information"
46
+ },
47
+ "unsafe-import": {
48
+ i18n: "sast_warnings.unsafe_import",
49
+ severity: "Warning"
50
+ },
51
+ "unsafe-regex": {
52
+ i18n: "sast_warnings.unsafe_regex",
53
+ severity: "Warning"
54
+ },
55
+ "unsafe-stmt": {
56
+ code: "unsafe-stmt",
57
+ i18n: "sast_warnings.unsafe_stmt",
58
+ severity: "Warning"
59
+ },
60
+ "encoded-literal": {
61
+ i18n: "sast_warnings.encoded_literal",
62
+ severity: "Information"
63
+ },
64
+ "short-identifiers": {
65
+ i18n: "sast_warnings.short_identifiers",
66
+ severity: "Warning"
67
+ },
68
+ "suspicious-literal": {
69
+ i18n: "sast_warnings.suspicious_literal",
70
+ severity: "Warning"
71
+ },
72
+ "suspicious-file": {
73
+ i18n: "sast_warnings.suspicious_file",
74
+ severity: "Critical",
75
+ experimental: false
76
+ },
77
+ "obfuscated-code": {
78
+ i18n: "sast_warnings.obfuscated_code",
79
+ severity: "Critical",
80
+ experimental: true
81
+ },
82
+ "weak-crypto": {
83
+ i18n: "sast_warnings.weak_crypto",
84
+ severity: "Information",
85
+ experimental: false
86
+ },
87
+ "shady-link": {
88
+ i18n: "sast_warnings.shady_link",
89
+ severity: "Warning",
90
+ experimental: false
91
+ },
92
+ "unsafe-command": {
93
+ i18n: "sast_warnings.unsafe-command",
94
+ severity: "Warning",
95
+ experimental: true
96
+ },
97
+ "synchronous-io": {
98
+ i18n: "sast_warnings.synchronous-io",
99
+ severity: "Warning",
100
+ experimental: true
101
+ },
102
+ "serialize-environment": {
103
+ i18n: "sast_warnings.serialize-environment",
104
+ severity: "Warning",
105
+ experimental: false
106
+ }
107
+ }) satisfies Record<WarningName, Pick<Warning, "experimental" | "i18n" | "severity">>;
108
+
109
+ export interface GenerateWarningOptions {
110
+ location?: ESTree.SourceLocation | null;
111
+ file?: string | null;
112
+ value: string | null;
113
+ source?: string;
114
+ }
115
+
116
+ export function generateWarning<T extends WarningName>(
117
+ kind: T,
118
+ options: GenerateWarningOptions
119
+ ): Warning<T> {
120
+ const {
121
+ file = null,
122
+ value,
123
+ source = "JS-X-Ray"
124
+ } = options;
125
+ const location = options.location ?? rootLocation();
126
+
127
+ if (kind === "encoded-literal") {
128
+ return {
129
+ kind,
130
+ value,
131
+ location: [toArrayLocation(location)],
132
+ source,
133
+ ...warnings[kind]
134
+ };
135
+ }
136
+
137
+ return {
138
+ kind,
139
+ location: toArrayLocation(location),
140
+ source,
141
+ ...warnings[kind],
142
+ ...(notNullOrUndefined(file) ? { file } : {}),
143
+ ...(notNullOrUndefined(value) ? { value } : { value: null })
144
+ };
145
+ }
146
+
package/index.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import {
2
- AstAnalyser,
3
- AstAnalyserOptions,
4
-
5
- EntryFilesAnalyser,
6
- EntryFilesAnalyserOptions,
7
-
8
- SourceParser,
9
- JsSourceParser,
10
- Report,
11
- ReportOnFile,
12
- RuntimeFileOptions,
13
- RuntimeOptions,
14
- SourceLocation,
15
- Dependency
16
- } from "./types/api.js";
17
- import {
18
- Warning,
19
- WarningDefault,
20
- WarningLocation,
21
- WarningName,
22
- WarningNameWithValue
23
- } from "./types/warnings.js";
24
-
25
- declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental" | "i18n" | "severity">>;
26
-
27
- export {
28
- warnings,
29
- AstAnalyser,
30
- AstAnalyserOptions,
31
- EntryFilesAnalyser,
32
- EntryFilesAnalyserOptions,
33
- JsSourceParser,
34
- SourceParser,
35
- Report,
36
- ReportOnFile,
37
- RuntimeFileOptions,
38
- RuntimeOptions,
39
- SourceLocation,
40
- Dependency,
41
- Warning,
42
- WarningDefault,
43
- WarningLocation,
44
- WarningName,
45
- WarningNameWithValue
46
- }
package/index.js DELETED
@@ -1,4 +0,0 @@
1
- export { warnings } from "./src/warnings.js";
2
- export { JsSourceParser } from "./src/JsSourceParser.js";
3
- export { AstAnalyser } from "./src/AstAnalyser.js";
4
- export { EntryFilesAnalyser } from "./src/EntryFilesAnalyser.js";
@@ -1,57 +0,0 @@
1
- // Import Third-party Dependencies
2
- import * as meriyah from "meriyah";
3
-
4
- // CONSTANTS
5
- const kParsingOptions = {
6
- next: true,
7
- loc: true,
8
- raw: true,
9
- jsx: true
10
- };
11
-
12
- export class JsSourceParser {
13
- /**
14
- * @param {object} options
15
- * @param {boolean} options.isEcmaScriptModule
16
- */
17
- parse(source, options = {}) {
18
- const {
19
- isEcmaScriptModule
20
- } = options;
21
-
22
- try {
23
- const { body } = meriyah.parseScript(
24
- source,
25
- {
26
- ...kParsingOptions,
27
- module: isEcmaScriptModule,
28
- globalReturn: !isEcmaScriptModule
29
- }
30
- );
31
-
32
- return body;
33
- }
34
- catch (error) {
35
- const isIllegalReturn = error.description.includes("Illegal return statement");
36
-
37
- if (error.name === "SyntaxError" && (
38
- error.description.includes("The import keyword") ||
39
- error.description.includes("The export keyword") ||
40
- isIllegalReturn
41
- )) {
42
- const { body } = meriyah.parseScript(
43
- source,
44
- {
45
- ...kParsingOptions,
46
- module: true,
47
- globalReturn: isIllegalReturn
48
- }
49
- );
50
-
51
- return body;
52
- }
53
-
54
- throw error;
55
- }
56
- }
57
- }
@@ -1,76 +0,0 @@
1
- // Import Third-party Dependencies
2
- import FrequencySet from "frequency-set";
3
-
4
- // Import Internal Dependencies
5
- import { isNode } from "./utils/index.js";
6
-
7
- // eslint-disable-next-line func-style
8
- const noop = (node) => true;
9
-
10
- export class NodeCounter {
11
- lookup = null;
12
-
13
- #count = 0;
14
- #properties = null;
15
- #filterFn = noop;
16
- #matchFn = noop;
17
-
18
- /**
19
- * @param {!string} type
20
- * @param {Object} [options]
21
- * @param {string} [options.name]
22
- * @param {(node: any) => boolean} [options.filter]
23
- * @param {(node: any, nc: NodeCounter) => void} [options.match]
24
- *
25
- * @example
26
- * new NodeCounter("FunctionDeclaration");
27
- * new NodeCounter("VariableDeclaration[kind]");
28
- */
29
- constructor(type, options = {}) {
30
- if (typeof type !== "string") {
31
- throw new TypeError("type must be a string");
32
- }
33
-
34
- const typeResult = /([A-Za-z]+)(\[[a-zA-Z]+\])?/g.exec(type);
35
- if (typeResult === null) {
36
- throw new Error("invalid type argument syntax");
37
- }
38
- this.type = typeResult[1];
39
- this.lookup = typeResult[2]?.slice(1, -1) ?? null;
40
- this.name = options?.name ?? this.type;
41
- if (this.lookup) {
42
- this.#properties = new FrequencySet();
43
- }
44
-
45
- this.#filterFn = options.filter ?? noop;
46
- this.#matchFn = options.match ?? noop;
47
- }
48
-
49
- get count() {
50
- return this.#count;
51
- }
52
-
53
- get properties() {
54
- return Object.fromEntries(
55
- this.#properties?.entries() ?? []
56
- );
57
- }
58
-
59
- walk(node) {
60
- if (!isNode(node) || node.type !== this.type) {
61
- return;
62
- }
63
- if (!this.#filterFn(node)) {
64
- return;
65
- }
66
-
67
- this.#count++;
68
- if (this.lookup === null) {
69
- this.#matchFn(node, this);
70
- }
71
- else if (this.lookup in node) {
72
- this.#properties.add(node[this.lookup]);
73
- this.#matchFn(node, this);
74
- }
75
- }
76
- }
package/src/SourceFile.js DELETED
@@ -1,147 +0,0 @@
1
- // Import Third-party Dependencies
2
- import { Utils, Literal } from "@nodesecure/sec-literal";
3
- import { VariableTracer } from "@nodesecure/estree-ast-utils";
4
-
5
- // Import Internal Dependencies
6
- import { rootLocation, toArrayLocation } from "./utils/index.js";
7
- import { generateWarning } from "./warnings.js";
8
- import { ProbeRunner } from "./ProbeRunner.js";
9
- import { Deobfuscator } from "./Deobfuscator.js";
10
- import * as trojan from "./obfuscators/trojan-source.js";
11
-
12
- // CONSTANTS
13
- const kMaximumEncodedLiterals = 10;
14
-
15
- export class SourceFile {
16
- inTryStatement = false;
17
- dependencyAutoWarning = false;
18
- deobfuscator = new Deobfuscator();
19
- dependencies = new Map();
20
- encodedLiterals = new Map();
21
- warnings = [];
22
- /** @type {Set<string>} */
23
- flags = new Set();
24
-
25
- constructor(sourceCodeString, probesOptions = {}) {
26
- this.tracer = new VariableTracer()
27
- .enableDefaultTracing()
28
- .trace("crypto.createHash", {
29
- followConsecutiveAssignment: true, moduleName: "crypto"
30
- });
31
-
32
- let probes = ProbeRunner.Defaults;
33
- if (Array.isArray(probesOptions.customProbes) && probesOptions.customProbes.length > 0) {
34
- probes = probesOptions.skipDefaultProbes === true ? probesOptions.customProbes : [...probes, ...probesOptions.customProbes];
35
- }
36
- this.probesRunner = new ProbeRunner(this, probes);
37
-
38
- if (trojan.verify(sourceCodeString)) {
39
- this.addWarning("obfuscated-code", "trojan-source");
40
- }
41
- }
42
-
43
- addDependency(name, location = null, unsafe = this.dependencyAutoWarning) {
44
- if (typeof name !== "string" || name.trim() === "") {
45
- return;
46
- }
47
-
48
- const dependencyName = name.charAt(name.length - 1) === "/" ?
49
- name.slice(0, -1) : name;
50
- this.dependencies.set(dependencyName, {
51
- unsafe,
52
- inTry: this.inTryStatement,
53
- ...(location === null ? {} : { location })
54
- });
55
-
56
- if (this.dependencyAutoWarning) {
57
- this.addWarning("unsafe-import", dependencyName, location);
58
- }
59
- }
60
-
61
- addWarning(name, value, location = rootLocation()) {
62
- const isEncodedLiteral = name === "encoded-literal";
63
- if (isEncodedLiteral) {
64
- if (this.encodedLiterals.size > kMaximumEncodedLiterals) {
65
- return;
66
- }
67
-
68
- if (this.encodedLiterals.has(value)) {
69
- const index = this.encodedLiterals.get(value);
70
- this.warnings[index].location.push(toArrayLocation(location));
71
-
72
- return;
73
- }
74
- }
75
-
76
- this.warnings.push(generateWarning(name, { value, location }));
77
- if (isEncodedLiteral) {
78
- this.encodedLiterals.set(value, this.warnings.length - 1);
79
- }
80
- }
81
-
82
- analyzeLiteral(node, inArrayExpr = false) {
83
- if (typeof node.value !== "string" || Utils.isSvg(node)) {
84
- return;
85
- }
86
- this.deobfuscator.analyzeString(node.value);
87
-
88
- const { hasHexadecimalSequence, hasUnicodeSequence, isBase64 } = Literal.defaultAnalysis(node);
89
- if ((hasHexadecimalSequence || hasUnicodeSequence) && isBase64) {
90
- if (inArrayExpr) {
91
- this.deobfuscator.encodedArrayValue++;
92
- }
93
- else {
94
- this.addWarning("encoded-literal", node.value, node.loc);
95
- }
96
- }
97
- }
98
-
99
- getResult(isMinified) {
100
- const obfuscatorName = this.deobfuscator.assertObfuscation(this);
101
- if (obfuscatorName !== null) {
102
- this.addWarning("obfuscated-code", obfuscatorName);
103
- }
104
-
105
- const identifiersLengthArr = this.deobfuscator.identifiers
106
- .filter((value) => value.type !== "Property" && typeof value.name === "string")
107
- .map((value) => value.name.length);
108
-
109
- const [idsLengthAvg, stringScore] = [
110
- sum(identifiersLengthArr),
111
- sum(this.deobfuscator.literalScores)
112
- ];
113
- if (!isMinified && identifiersLengthArr.length > 5 && idsLengthAvg <= 1.5) {
114
- this.addWarning("short-identifiers", idsLengthAvg);
115
- }
116
- if (stringScore >= 3) {
117
- this.addWarning("suspicious-literal", stringScore);
118
- }
119
-
120
- if (this.encodedLiterals.size > kMaximumEncodedLiterals) {
121
- this.addWarning("suspicious-file", null);
122
- this.warnings = this.warnings
123
- .filter((warning) => warning.kind !== "encoded-literal");
124
- }
125
-
126
- return { idsLengthAvg, stringScore, warnings: this.warnings };
127
- }
128
-
129
- walk(node) {
130
- this.tracer.walk(node);
131
- this.deobfuscator.walk(node);
132
-
133
- // Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause
134
- if (node.type === "TryStatement" && node.handler) {
135
- this.inTryStatement = true;
136
- }
137
- else if (node.type === "CatchClause") {
138
- this.inTryStatement = false;
139
- }
140
-
141
- return this.probesRunner.walk(node);
142
- }
143
- }
144
-
145
- function sum(arr = []) {
146
- return arr.length === 0 ? 0 : (arr.reduce((prev, curr) => prev + curr, 0) / arr.length);
147
- }
@@ -1,9 +0,0 @@
1
- // Import Third-party Dependencies
2
- import { Utils } from "@nodesecure/sec-literal";
3
-
4
- export function verify(identifiers, prefix) {
5
- const pValue = Object.keys(prefix).pop();
6
- const regexStr = `^${Utils.escapeRegExp(pValue)}[a-zA-Z]{1,2}[0-9]{0,2}$`;
7
-
8
- return identifiers.every(({ name }) => new RegExp(regexStr).test(name));
9
- }
@@ -1,11 +0,0 @@
1
- // CONSTANTS
2
- const kJSFuckMinimumDoubleUnaryExpr = 5;
3
-
4
- export function verify(counters) {
5
- const hasZeroAssign = counters.AssignmentExpression === 0
6
- && counters.FunctionDeclaration === 0
7
- && counters.Property === 0
8
- && counters.VariableDeclarator === 0;
9
-
10
- return hasZeroAssign && counters.DoubleUnaryExpression >= kJSFuckMinimumDoubleUnaryExpr;
11
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * @description Search for ESM Export
3
- *
4
- * @example
5
- * export { bar } from "./foo.js";
6
- * export * from "./bar.js";
7
- */
8
- function validateNode(node) {
9
- return [
10
- /**
11
- * We must be sure that the source property is a Literal to not fall in a trap
12
- * export const foo = "bar";
13
- */
14
- (node.type === "ExportNamedDeclaration" && node.source?.type === "Literal") ||
15
- node.type === "ExportAllDeclaration"
16
- ];
17
- }
18
-
19
- function main(node, { sourceFile }) {
20
- sourceFile.addDependency(
21
- node.source.value,
22
- node.loc
23
- );
24
- }
25
-
26
- export default {
27
- name: "isESMExport",
28
- validateNode,
29
- main,
30
- breakOnMatch: true
31
- };
@@ -1,33 +0,0 @@
1
- /**
2
- * @description Search for ESM ImportDeclaration
3
- * @see https://github.com/estree/estree/blob/master/es2015.md#importdeclaration
4
- * @example
5
- * import * as foo from "bar";
6
- * import fs from "fs";
7
- * import "make-promises-safe";
8
- */
9
- function validateNode(node) {
10
- return [
11
- // Note: the source property is the right-side Literal part of the Import
12
- ["ImportDeclaration", "ImportExpression"].includes(node.type) && node.source.type === "Literal"
13
- ];
14
- }
15
-
16
- function main(node, options) {
17
- const { sourceFile } = options;
18
-
19
- // Searching for dangerous import "data:text/javascript;..." statement.
20
- // see: https://2ality.com/2019/10/eval-via-import.html
21
- if (node.source.value.startsWith("data:text/javascript")) {
22
- sourceFile.addWarning("unsafe-import", node.source.value, node.loc);
23
- }
24
- sourceFile.addDependency(node.source.value, node.loc);
25
- }
26
-
27
- export default {
28
- name: "isImportDeclaration",
29
- validateNode,
30
- main,
31
- breakOnMatch: true,
32
- breakGroup: "import"
33
- };
@@ -1,31 +0,0 @@
1
- // Require Third-party Dependencies
2
- import { isLiteralRegex } from "@nodesecure/estree-ast-utils";
3
- import safeRegex from "safe-regex";
4
-
5
- /**
6
- * @description Search for RegExpLiteral AST Node
7
- * @see https://github.com/estree/estree/blob/master/es5.md#regexpliteral
8
- * @example
9
- * /hello/
10
- */
11
- function validateNode(node) {
12
- return [
13
- isLiteralRegex(node)
14
- ];
15
- }
16
-
17
- function main(node, options) {
18
- const { sourceFile } = options;
19
-
20
- // We use the safe-regex package to detect whether or not regex is safe!
21
- if (!safeRegex(node.regex.pattern)) {
22
- sourceFile.addWarning("unsafe-regex", node.regex.pattern, node.loc);
23
- }
24
- }
25
-
26
- export default {
27
- name: "isLiteralRegex",
28
- validateNode,
29
- main,
30
- breakOnMatch: false
31
- };
@@ -1,93 +0,0 @@
1
- // Import Node.js Dependencies
2
- import path from "node:path";
3
-
4
- // Import Third-party Dependencies
5
- import { Hex } from "@nodesecure/sec-literal";
6
- import { walk as doWalk } from "estree-walker";
7
- import {
8
- arrayExpressionToString,
9
- getMemberExpressionIdentifier,
10
- getCallExpressionArguments
11
- } from "@nodesecure/estree-ast-utils";
12
-
13
- export class RequireCallExpressionWalker {
14
- constructor(tracer) {
15
- this.tracer = tracer;
16
- this.dependencies = new Set();
17
- this.triggerWarning = true;
18
- }
19
-
20
- walk(nodeToWalk) {
21
- this.dependencies = new Set();
22
- this.triggerWarning = true;
23
-
24
- // we need the `this` context of doWalk.enter
25
- const self = this;
26
- doWalk(nodeToWalk, {
27
- enter(node) {
28
- if (node.type !== "CallExpression" || node.arguments.length === 0) {
29
- return;
30
- }
31
-
32
- const rootArgument = node.arguments.at(0);
33
- if (rootArgument.type === "Literal" && Hex.isHex(rootArgument.value)) {
34
- self.dependencies.add(Buffer.from(rootArgument.value, "hex").toString());
35
- this.skip();
36
-
37
- return;
38
- }
39
-
40
- const fullName = node.callee.type === "MemberExpression" ?
41
- [...getMemberExpressionIdentifier(node.callee)].join(".") :
42
- node.callee.name;
43
- const tracedFullName = self.tracer.getDataFromIdentifier(fullName)?.identifierOrMemberExpr ?? fullName;
44
- switch (tracedFullName) {
45
- case "atob":
46
- self.#handleAtob(node);
47
- break;
48
- case "Buffer.from":
49
- self.#handleBufferFrom(node);
50
- break;
51
- case "require.resolve":
52
- self.#handleRequireResolve(rootArgument);
53
- break;
54
- case "path.join":
55
- self.#handlePathJoin(node);
56
- break;
57
- }
58
- }
59
- });
60
-
61
- return { dependencies: this.dependencies, triggerWarning: this.triggerWarning };
62
- }
63
-
64
- #handleAtob(node) {
65
- const nodeArguments = getCallExpressionArguments(node, { tracer: this.tracer });
66
- if (nodeArguments !== null) {
67
- this.dependencies.add(Buffer.from(nodeArguments.at(0), "base64").toString());
68
- }
69
- }
70
-
71
- #handleBufferFrom(node) {
72
- const [element] = node.arguments;
73
- if (element.type === "ArrayExpression") {
74
- const depName = [...arrayExpressionToString(element)].join("").trim();
75
- this.dependencies.add(depName);
76
- }
77
- }
78
-
79
- #handleRequireResolve(rootArgument) {
80
- if (rootArgument.type === "Literal") {
81
- this.dependencies.add(rootArgument.value);
82
- }
83
- }
84
-
85
- #handlePathJoin(node) {
86
- if (!node.arguments.every((arg) => arg.type === "Literal" && typeof arg.value === "string")) {
87
- return;
88
- }
89
- const constructedPath = path.posix.join(...node.arguments.map((arg) => arg.value));
90
- this.dependencies.add(constructedPath);
91
- this.triggerWarning = false;
92
- }
93
- }