@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.
- package/LICENSE +21 -21
- package/README.md +9 -209
- package/package.json +62 -77
- package/src/{AstAnalyser.js → AstAnalyser.ts} +283 -222
- package/src/{Deobfuscator.js → Deobfuscator.ts} +228 -195
- package/src/{EntryFilesAnalyser.js → EntryFilesAnalyser.ts} +206 -167
- package/src/JsSourceParser.ts +77 -0
- package/src/NodeCounter.ts +90 -0
- package/src/{ProbeRunner.js → ProbeRunner.ts} +167 -144
- package/src/SourceFile.ts +226 -0
- package/src/index.ts +5 -0
- package/src/obfuscators/freejsobfuscator.ts +17 -0
- package/src/obfuscators/{jjencode.js → jjencode.ts} +39 -27
- package/src/obfuscators/jsfuck.ts +19 -0
- package/src/obfuscators/{obfuscator-io.js → obfuscator-io.ts} +25 -13
- package/src/obfuscators/{trojan-source.js → trojan-source.ts} +3 -1
- package/src/probes/{isArrayExpression.js → isArrayExpression.ts} +12 -3
- package/src/probes/{isBinaryExpression.js → isBinaryExpression.ts} +74 -55
- package/src/probes/isESMExport.ts +50 -0
- package/src/probes/{isFetch.js → isFetch.ts} +28 -19
- package/src/probes/isImportDeclaration.ts +58 -0
- package/src/probes/{isLiteral.js → isLiteral.ts} +91 -70
- package/src/probes/isLiteralRegex.ts +42 -0
- package/src/probes/{isRegexObject.js → isRegexObject.ts} +71 -49
- package/src/probes/isRequire/RequireCallExpressionWalker.ts +142 -0
- package/src/probes/isRequire/{isRequire.js → isRequire.ts} +195 -148
- package/src/probes/isSerializeEnv.ts +65 -0
- package/src/probes/isSyncIO.ts +96 -0
- package/src/probes/isUnsafeCallee.ts +89 -0
- package/src/probes/isUnsafeCommand.ts +133 -0
- package/src/probes/isWeakCrypto.ts +69 -0
- package/src/types/estree.ts +35 -0
- package/src/utils/extractNode.ts +22 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/{exportAssignmentHasRequireLeave.js → isOneLineExpressionExport.ts} +70 -40
- package/src/utils/notNullOrUndefined.ts +5 -0
- package/src/utils/toArrayLocation.ts +22 -0
- package/src/warnings.ts +146 -0
- package/index.d.ts +0 -46
- package/index.js +0 -4
- package/src/JsSourceParser.js +0 -57
- package/src/NodeCounter.js +0 -76
- package/src/SourceFile.js +0 -147
- package/src/obfuscators/freejsobfuscator.js +0 -9
- package/src/obfuscators/jsfuck.js +0 -11
- package/src/probes/isESMExport.js +0 -31
- package/src/probes/isImportDeclaration.js +0 -33
- package/src/probes/isLiteralRegex.js +0 -31
- package/src/probes/isRequire/RequireCallExpressionWalker.js +0 -93
- package/src/probes/isUnsafeCallee.js +0 -35
- package/src/probes/isWeakCrypto.js +0 -37
- package/src/utils/extractNode.js +0 -14
- package/src/utils/index.js +0 -8
- package/src/utils/isNode.js +0 -5
- package/src/utils/isOneLineExpressionExport.js +0 -24
- package/src/utils/isUnsafeCallee.js +0 -28
- package/src/utils/notNullOrUndefined.js +0 -3
- package/src/utils/rootLocation.js +0 -3
- package/src/utils/toArrayLocation.js +0 -11
- package/src/warnings.js +0 -77
- package/types/api.d.ts +0 -177
- package/types/warnings.d.ts +0 -36
|
@@ -1,222 +1,283 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import fsSync from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
// Import Third-party Dependencies
|
|
7
|
-
import { walk } from "estree-walker";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
options = {}
|
|
93
|
-
) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
initialize
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
module
|
|
161
|
-
removeHTMLComments,
|
|
162
|
-
initialize,
|
|
163
|
-
finalize
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import fsSync from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
// Import Third-party Dependencies
|
|
7
|
+
import { walk } from "estree-walker";
|
|
8
|
+
import type { ESTree } from "meriyah";
|
|
9
|
+
import isMinified from "is-minified-code";
|
|
10
|
+
|
|
11
|
+
// Import Internal Dependencies
|
|
12
|
+
import { generateWarning, type Warning } from "./warnings.js";
|
|
13
|
+
import {
|
|
14
|
+
SourceFile,
|
|
15
|
+
type ProbesOptions,
|
|
16
|
+
type SourceFlags
|
|
17
|
+
} from "./SourceFile.js";
|
|
18
|
+
import { isOneLineExpressionExport } from "./utils/index.js";
|
|
19
|
+
import { JsSourceParser, type SourceParser } from "./JsSourceParser.js";
|
|
20
|
+
|
|
21
|
+
export interface Dependency {
|
|
22
|
+
unsafe: boolean;
|
|
23
|
+
inTry: boolean;
|
|
24
|
+
location?: null | ESTree.SourceLocation;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RuntimeOptions {
|
|
28
|
+
/**
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
module?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
removeHTMLComments?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
isMinified?: boolean;
|
|
40
|
+
initialize?: (sourceFile: SourceFile) => void;
|
|
41
|
+
finalize?: (sourceFile: SourceFile) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RuntimeFileOptions extends Omit<RuntimeOptions, "isMinified"> {
|
|
45
|
+
packageName?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Report {
|
|
49
|
+
dependencies: Map<string, Dependency>;
|
|
50
|
+
warnings: Warning[];
|
|
51
|
+
flags: Set<SourceFlags>;
|
|
52
|
+
idsLengthAvg: number;
|
|
53
|
+
stringScore: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type ReportOnFile = {
|
|
57
|
+
ok: true;
|
|
58
|
+
warnings: Warning[];
|
|
59
|
+
dependencies: Map<string, Dependency>;
|
|
60
|
+
flags: Set<SourceFlags>;
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
warnings: Warning[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export interface AstAnalyserOptions extends ProbesOptions {
|
|
67
|
+
/**
|
|
68
|
+
* @default JsSourceParser
|
|
69
|
+
*/
|
|
70
|
+
customParser?: SourceParser;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface PrepareSourceOptions {
|
|
74
|
+
removeHTMLComments?: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class AstAnalyser {
|
|
78
|
+
parser: SourceParser;
|
|
79
|
+
probesOptions: ProbesOptions;
|
|
80
|
+
|
|
81
|
+
constructor(options: AstAnalyserOptions = {}) {
|
|
82
|
+
this.parser = options.customParser ?? new JsSourceParser();
|
|
83
|
+
this.probesOptions = {
|
|
84
|
+
customProbes: options.customProbes ?? [],
|
|
85
|
+
skipDefaultProbes: options.skipDefaultProbes ?? false,
|
|
86
|
+
optionalWarnings: options.optionalWarnings ?? false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
analyse(
|
|
91
|
+
str: string,
|
|
92
|
+
options: RuntimeOptions = {}
|
|
93
|
+
): Report {
|
|
94
|
+
const {
|
|
95
|
+
isMinified = false,
|
|
96
|
+
module = true,
|
|
97
|
+
removeHTMLComments = false,
|
|
98
|
+
initialize,
|
|
99
|
+
finalize
|
|
100
|
+
} = options;
|
|
101
|
+
|
|
102
|
+
const body = this.parser.parse(this.prepareSource(str, { removeHTMLComments }), {
|
|
103
|
+
isEcmaScriptModule: Boolean(module)
|
|
104
|
+
});
|
|
105
|
+
const source = new SourceFile(str, this.probesOptions);
|
|
106
|
+
|
|
107
|
+
// TODO: this check should be factorized in a way that we reuse it
|
|
108
|
+
// on analyze and anlyseFile
|
|
109
|
+
if (initialize) {
|
|
110
|
+
if (typeof initialize !== "function") {
|
|
111
|
+
throw new TypeError("options.initialize must be a function");
|
|
112
|
+
}
|
|
113
|
+
initialize(source);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// we walk each AST Nodes, this is a purely synchronous I/O
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
walk(body, {
|
|
119
|
+
enter(node) {
|
|
120
|
+
// Skip the root of the AST.
|
|
121
|
+
if (Array.isArray(node)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const action = source.walk(node as ESTree.Node);
|
|
126
|
+
if (action === "skip") {
|
|
127
|
+
this.skip();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// TODO: this check should be factorized in a way that we reuse it
|
|
133
|
+
// on analyze and anlyseFile
|
|
134
|
+
if (finalize) {
|
|
135
|
+
if (typeof finalize !== "function") {
|
|
136
|
+
throw new TypeError("options.finalize must be a function");
|
|
137
|
+
}
|
|
138
|
+
finalize(source);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Add oneline-require flag if this is a one-line require expression
|
|
142
|
+
if (isOneLineExpressionExport(body)) {
|
|
143
|
+
source.flags.add("oneline-require");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...source.getResult(isMinified),
|
|
148
|
+
dependencies: source.dependencies,
|
|
149
|
+
flags: source.flags
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async analyseFile(
|
|
154
|
+
pathToFile: string | URL,
|
|
155
|
+
options: RuntimeFileOptions = {}
|
|
156
|
+
): Promise<ReportOnFile> {
|
|
157
|
+
try {
|
|
158
|
+
const {
|
|
159
|
+
packageName = null,
|
|
160
|
+
module = true,
|
|
161
|
+
removeHTMLComments = false,
|
|
162
|
+
initialize,
|
|
163
|
+
finalize
|
|
164
|
+
} = options;
|
|
165
|
+
|
|
166
|
+
const str = await fs.readFile(pathToFile, "utf-8");
|
|
167
|
+
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
|
|
168
|
+
|
|
169
|
+
const isMin = filePathString.includes(".min") || isMinified(str);
|
|
170
|
+
const data = this.analyse(str, {
|
|
171
|
+
isMinified: isMin,
|
|
172
|
+
module: path.extname(filePathString) === ".mjs" ? true : module,
|
|
173
|
+
removeHTMLComments,
|
|
174
|
+
initialize,
|
|
175
|
+
finalize
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (packageName !== null) {
|
|
179
|
+
data.dependencies.delete(packageName);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add is-minified flag if the file is minified and not a one-line require
|
|
183
|
+
if (!data.flags.has("oneline-require") && isMin) {
|
|
184
|
+
data.flags.add("is-minified");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
ok: true,
|
|
189
|
+
dependencies: data.dependencies,
|
|
190
|
+
warnings: data.warnings,
|
|
191
|
+
flags: data.flags
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (error: any) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
warnings: [
|
|
198
|
+
generateWarning("parsing-error", {
|
|
199
|
+
value: error.message
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
analyseFileSync(
|
|
207
|
+
pathToFile: string | URL,
|
|
208
|
+
options: RuntimeFileOptions = {}
|
|
209
|
+
): ReportOnFile {
|
|
210
|
+
try {
|
|
211
|
+
const {
|
|
212
|
+
packageName = null,
|
|
213
|
+
module = true,
|
|
214
|
+
removeHTMLComments = false,
|
|
215
|
+
initialize,
|
|
216
|
+
finalize
|
|
217
|
+
} = options;
|
|
218
|
+
|
|
219
|
+
const str = fsSync.readFileSync(pathToFile, "utf-8");
|
|
220
|
+
const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
|
|
221
|
+
|
|
222
|
+
const isMin = filePathString.includes(".min") || isMinified(str);
|
|
223
|
+
const data = this.analyse(str, {
|
|
224
|
+
isMinified: isMin,
|
|
225
|
+
module: path.extname(filePathString) === ".mjs" ? true : module,
|
|
226
|
+
removeHTMLComments,
|
|
227
|
+
initialize,
|
|
228
|
+
finalize
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (packageName !== null) {
|
|
232
|
+
data.dependencies.delete(packageName);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Add is-minified flag if the file is minified and not a one-line require
|
|
236
|
+
if (!data.flags.has("oneline-require") && isMin) {
|
|
237
|
+
data.flags.add("is-minified");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
dependencies: data.dependencies,
|
|
243
|
+
warnings: data.warnings,
|
|
244
|
+
flags: data.flags
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error: any) {
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
warnings: [
|
|
251
|
+
generateWarning("parsing-error", {
|
|
252
|
+
value: error.message
|
|
253
|
+
})
|
|
254
|
+
]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
prepareSource(
|
|
260
|
+
source: string,
|
|
261
|
+
options: PrepareSourceOptions = {}
|
|
262
|
+
): string {
|
|
263
|
+
if (typeof source !== "string") {
|
|
264
|
+
throw new TypeError("source must be a string");
|
|
265
|
+
}
|
|
266
|
+
const { removeHTMLComments = false } = options;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* if the file start with a shebang then we remove it because meriyah.parseScript fail to parse it.
|
|
270
|
+
* @example
|
|
271
|
+
* #!/usr/bin/env node
|
|
272
|
+
*/
|
|
273
|
+
const rawNoShebang = source.startsWith("#") ?
|
|
274
|
+
source.slice(source.indexOf("\n") + 1) : source;
|
|
275
|
+
|
|
276
|
+
return removeHTMLComments ?
|
|
277
|
+
this.#removeHTMLComment(rawNoShebang) : rawNoShebang;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#removeHTMLComment(str: string): string {
|
|
281
|
+
return str.replaceAll(/<!--[\s\S]*?(?:-->)/g, "");
|
|
282
|
+
}
|
|
283
|
+
}
|