@nodesecure/js-x-ray 7.3.0 → 8.0.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 +9 -155
- package/index.d.ts +0 -4
- package/index.js +4 -70
- package/package.json +8 -11
- package/src/Deobfuscator.js +3 -0
- package/src/EntryFilesAnalyser.js +96 -47
- package/src/probes/isLiteral.js +1 -0
- package/src/probes/isRequire/isRequire.js +8 -2
- package/src/warnings.js +3 -3
- package/types/api.d.ts +50 -11
package/README.md
CHANGED
|
@@ -85,6 +85,11 @@ The analysis will return: `http` (in try), `crypto`, `util` and `fs`.
|
|
|
85
85
|
> [!TIP]
|
|
86
86
|
> There is also a lot of suspicious code example in the `./examples` cases directory. Feel free to try the tool on these files.
|
|
87
87
|
|
|
88
|
+
## API
|
|
89
|
+
|
|
90
|
+
- [AstAnalyser](./docs/api/AstAnalyser.md)
|
|
91
|
+
- [EntryFilesAnalyser](./docs/api/EntryFilesAnalyser.md)
|
|
92
|
+
|
|
88
93
|
## Warnings
|
|
89
94
|
|
|
90
95
|
This section describes how use `warnings` export.
|
|
@@ -118,7 +123,7 @@ import * as i18n from "@nodesecure/i18n";
|
|
|
118
123
|
console.log(i18n.getTokenSync(jsxray.warnings["parsing-error"].i18n));
|
|
119
124
|
```
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
### Legends
|
|
122
127
|
|
|
123
128
|
This section describe all the possible warnings returned by JSXRay. Click on the warning **name** for additional information and examples.
|
|
124
129
|
|
|
@@ -131,161 +136,10 @@ This section describe all the possible warnings returned by JSXRay. Click on the
|
|
|
131
136
|
| [encoded-literal](./docs/encoded-literal.md) | ❌ | An encoded literal has been detected (it can be an hexa value, unicode sequence or a base64 string) |
|
|
132
137
|
| [short-identifiers](./docs/short-identifiers.md) | ❌ | This mean that all identifiers has an average length below 1.5. |
|
|
133
138
|
| [suspicious-literal](./docs/suspicious-literal.md) | ❌ | A suspicious literal has been found in the source code. |
|
|
134
|
-
| [suspicious-file](./docs/suspicious-file.md) |
|
|
139
|
+
| [suspicious-file](./docs/suspicious-file.md) | ❌ | A suspicious file with more than ten encoded-literal in it |
|
|
135
140
|
| [obfuscated-code](./docs/obfuscated-code.md) | ✔️ | There's a very high probability that the code is obfuscated. |
|
|
136
|
-
| [weak-crypto](./docs/weak-crypto.md) |
|
|
137
|
-
| [shady-link](./docs/shady-link.md) |
|
|
138
|
-
|
|
139
|
-
## Custom Probes
|
|
140
|
-
|
|
141
|
-
You can also create custom probes to detect specific pattern in the code you are analyzing.
|
|
142
|
-
|
|
143
|
-
A probe is a pair of two functions (`validateNode` and `main`) that will be called on each node of the AST. It will return a warning if the pattern is detected.
|
|
144
|
-
Below a basic probe that detect a string assignation to `danger`:
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
export const customProbes = [
|
|
148
|
-
{
|
|
149
|
-
name: "customProbeUnsafeDanger",
|
|
150
|
-
validateNode: (node, sourceFile) => [
|
|
151
|
-
node.type === "VariableDeclaration" && node.declarations[0].init.value === "danger"
|
|
152
|
-
],
|
|
153
|
-
main: (node, options) => {
|
|
154
|
-
const { sourceFile, data: calleeName } = options;
|
|
155
|
-
if (node.declarations[0].init.value === "danger") {
|
|
156
|
-
sourceFile.addWarning("unsafe-danger", calleeName, node.loc);
|
|
157
|
-
|
|
158
|
-
return ProbeSignals.Skip;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
];
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
You can pass an array of probes to the `runASTAnalysis/runASTAnalysisOnFile` functions as `options`, or directly to the `AstAnalyser` constructor.
|
|
168
|
-
|
|
169
|
-
| Name | Type | Description | Default Value |
|
|
170
|
-
|------------------|----------------------------------|-----------------------------------------------------------------------|-----------------|
|
|
171
|
-
| `customParser` | `SourceParser \| undefined` | An optional custom parser to be used for parsing the source code. | `JsSourceParser` |
|
|
172
|
-
| `customProbes` | `Probe[] \| undefined` | An array of custom probes to be used during AST analysis. | `[]` |
|
|
173
|
-
| `skipDefaultProbes` | `boolean \| undefined` | If `true`, default probes will be skipped and only custom probes will be used. | `false` |
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Here using the example probe upper:
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
import { AstAnalyser } from "@nodesecure/js-x-ray";
|
|
180
|
-
|
|
181
|
-
// add your customProbes here (see example above)
|
|
182
|
-
|
|
183
|
-
const scanner = new AstAnalyser({
|
|
184
|
-
customProbes,
|
|
185
|
-
skipDefaultProbes: true
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const result = scanner.analyse("const danger = 'danger';");
|
|
189
|
-
|
|
190
|
-
console.log(result);
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Result:
|
|
194
|
-
|
|
195
|
-
```sh
|
|
196
|
-
✗ node example.js
|
|
197
|
-
{
|
|
198
|
-
idsLengthAvg: 0,
|
|
199
|
-
stringScore: 0,
|
|
200
|
-
warnings: [ { kind: 'unsafe-danger', location: [Array], source: 'JS-X-Ray' } ],
|
|
201
|
-
dependencies: Map(0) {},
|
|
202
|
-
isOneLineRequire: false
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Congrats, you have created your first custom probe! 🎉
|
|
207
|
-
|
|
208
|
-
> [!TIP]
|
|
209
|
-
> Check the types in [index.d.ts](index.d.ts) and [types/api.d.ts](types/api.d.ts) for more details about the `options`
|
|
210
|
-
|
|
211
|
-
## API
|
|
212
|
-
|
|
213
|
-
- [AstAnalyser](./docs/api/AstAnalyser.md)
|
|
214
|
-
- [EntryFilesAnalyser](./docs/api/EntryFilesAnalyser.md)
|
|
215
|
-
|
|
216
|
-
Legacy APIs waiting to be deprecated;
|
|
217
|
-
|
|
218
|
-
<details>
|
|
219
|
-
<summary>runASTAnalysis(str: string, options?: RuntimeOptions & AstAnalyserOptions): Report</summary>
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
interface RuntimeOptions {
|
|
223
|
-
module?: boolean;
|
|
224
|
-
removeHTMLComments?: boolean;
|
|
225
|
-
isMinified?: boolean;
|
|
226
|
-
initialize?: (sourceFile: SourceFile) => void;
|
|
227
|
-
finalize?: (sourceFile: SourceFile) => void;
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
interface AstAnalyserOptions {
|
|
233
|
-
customParser?: SourceParser;
|
|
234
|
-
customProbes?: Probe[];
|
|
235
|
-
skipDefaultProbes?: boolean;
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
The method take a first argument which is the code you want to analyse. It will return a Report Object:
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
interface Report {
|
|
243
|
-
dependencies: ASTDeps;
|
|
244
|
-
warnings: Warning[];
|
|
245
|
-
idsLengthAvg: number;
|
|
246
|
-
stringScore: number;
|
|
247
|
-
isOneLineRequire: boolean;
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
</details>
|
|
252
|
-
|
|
253
|
-
<details>
|
|
254
|
-
<summary>runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions & AstAnalyserOptions): Promise< ReportOnFile ></summary>
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
interface RuntimeFileOptions {
|
|
258
|
-
module?: boolean;
|
|
259
|
-
removeHTMLComments?: boolean;
|
|
260
|
-
packageName?: string;
|
|
261
|
-
initialize?: (sourceFile: SourceFile) => void;
|
|
262
|
-
finalize?: (sourceFile: SourceFile) => void;
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
interface AstAnalyserOptions {
|
|
268
|
-
customParser?: SourceParser;
|
|
269
|
-
customProbes?: Probe[];
|
|
270
|
-
skipDefaultProbes?: boolean;
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
Run the SAST scanner on a given JavaScript file.
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
export type ReportOnFile = {
|
|
278
|
-
ok: true,
|
|
279
|
-
warnings: Warning[];
|
|
280
|
-
dependencies: ASTDeps;
|
|
281
|
-
isMinified: boolean;
|
|
282
|
-
} | {
|
|
283
|
-
ok: false,
|
|
284
|
-
warnings: Warning[];
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
</details>
|
|
141
|
+
| [weak-crypto](./docs/weak-crypto.md) | ❌ | The code probably contains a weak crypto algorithm (md5, sha1...) |
|
|
142
|
+
| [shady-link](./docs/shady-link.md) | ❌ | The code contains shady/unsafe link |
|
|
289
143
|
|
|
290
144
|
## Workspaces
|
|
291
145
|
|
package/index.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
|
|
8
8
|
SourceParser,
|
|
9
9
|
JsSourceParser,
|
|
10
|
-
runASTAnalysis,
|
|
11
|
-
runASTAnalysisOnFile,
|
|
12
10
|
Report,
|
|
13
11
|
ReportOnFile,
|
|
14
12
|
RuntimeFileOptions,
|
|
@@ -34,8 +32,6 @@ export {
|
|
|
34
32
|
EntryFilesAnalyserOptions,
|
|
35
33
|
JsSourceParser,
|
|
36
34
|
SourceParser,
|
|
37
|
-
runASTAnalysis,
|
|
38
|
-
runASTAnalysisOnFile,
|
|
39
35
|
Report,
|
|
40
36
|
ReportOnFile,
|
|
41
37
|
RuntimeFileOptions,
|
package/index.js
CHANGED
|
@@ -1,70 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { EntryFilesAnalyser } from "./src/EntryFilesAnalyser.js";
|
|
6
|
-
|
|
7
|
-
function runASTAnalysis(
|
|
8
|
-
str,
|
|
9
|
-
options = Object.create(null)
|
|
10
|
-
) {
|
|
11
|
-
process.emitWarning(
|
|
12
|
-
'The runASTAnalysis API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.',
|
|
13
|
-
{
|
|
14
|
-
code: 'DeprecationWarning',
|
|
15
|
-
detail: 'The runASTAnalysis API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.'
|
|
16
|
-
}
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
customParser = new JsSourceParser(),
|
|
21
|
-
customProbes = [],
|
|
22
|
-
skipDefaultProbes = false,
|
|
23
|
-
...opts
|
|
24
|
-
} = options;
|
|
25
|
-
|
|
26
|
-
const analyser = new AstAnalyser({
|
|
27
|
-
customParser,
|
|
28
|
-
customProbes,
|
|
29
|
-
skipDefaultProbes
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return analyser.analyse(str, opts);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function runASTAnalysisOnFile(
|
|
36
|
-
pathToFile,
|
|
37
|
-
options = {}
|
|
38
|
-
) {
|
|
39
|
-
process.emitWarning(
|
|
40
|
-
'The runASTAnalysisOnFile API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.',
|
|
41
|
-
{
|
|
42
|
-
code: 'DeprecationWarning',
|
|
43
|
-
detail: 'The runASTAnalysisOnFile API is deprecated and will be removed in v8. Please use the AstAnalyser class instead.'
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const {
|
|
48
|
-
customProbes = [],
|
|
49
|
-
customParser = new JsSourceParser(),
|
|
50
|
-
skipDefaultProbes = false,
|
|
51
|
-
...opts
|
|
52
|
-
} = options;
|
|
53
|
-
|
|
54
|
-
const analyser = new AstAnalyser({
|
|
55
|
-
customParser,
|
|
56
|
-
customProbes,
|
|
57
|
-
skipDefaultProbes
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return analyser.analyseFile(pathToFile, opts);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export {
|
|
64
|
-
warnings,
|
|
65
|
-
AstAnalyser,
|
|
66
|
-
EntryFilesAnalyser,
|
|
67
|
-
JsSourceParser,
|
|
68
|
-
runASTAnalysis,
|
|
69
|
-
runASTAnalysisOnFile
|
|
70
|
-
};
|
|
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";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodesecure/js-x-ray",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "JavaScript AST XRay analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./index.js",
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"node": ">=18.0.0"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"lint": "eslint src test",
|
|
12
|
-
"prepublishOnly": "pkg-ok",
|
|
11
|
+
"lint": "eslint src workspaces test",
|
|
13
12
|
"test-only": "glob -c \"node --test-reporter=spec --test\" \"./test/**/*.spec.js\"",
|
|
14
13
|
"test": "c8 --all --src ./src -r html npm run test-only",
|
|
15
14
|
"check": "npm run lint && npm run test-only"
|
|
@@ -44,23 +43,21 @@
|
|
|
44
43
|
},
|
|
45
44
|
"homepage": "https://github.com/NodeSecure/js-x-ray#readme",
|
|
46
45
|
"dependencies": {
|
|
47
|
-
"@nodesecure/estree-ast-utils": "^1.
|
|
46
|
+
"@nodesecure/estree-ast-utils": "^1.5.0",
|
|
48
47
|
"@nodesecure/sec-literal": "^1.2.0",
|
|
48
|
+
"digraph-js": "^2.2.3",
|
|
49
49
|
"estree-walker": "^3.0.1",
|
|
50
50
|
"frequency-set": "^1.0.2",
|
|
51
51
|
"is-minified-code": "^2.0.0",
|
|
52
|
-
"meriyah": "^
|
|
52
|
+
"meriyah": "^5.0.0",
|
|
53
53
|
"safe-regex": "^2.1.1",
|
|
54
54
|
"ts-pattern": "^5.0.6"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"@types/node": "^
|
|
57
|
+
"@openally/config.eslint": "^1.0.0",
|
|
58
|
+
"@types/node": "^22.0.0",
|
|
59
59
|
"c8": "^10.1.2",
|
|
60
|
-
"cross-env": "^7.0.3",
|
|
61
|
-
"eslint": "^9.0.0",
|
|
62
60
|
"glob": "^11.0.0",
|
|
63
|
-
"iterator-matcher": "^2.1.0"
|
|
64
|
-
"pkg-ok": "^3.0.0"
|
|
61
|
+
"iterator-matcher": "^2.1.0"
|
|
65
62
|
}
|
|
66
63
|
}
|
package/src/Deobfuscator.js
CHANGED
|
@@ -3,6 +3,9 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
// Import Third-party Dependencies
|
|
7
|
+
import { DiGraph } from "digraph-js";
|
|
8
|
+
|
|
6
9
|
// Import Internal Dependencies
|
|
7
10
|
import { AstAnalyser } from "./AstAnalyser.js";
|
|
8
11
|
|
|
@@ -10,70 +13,106 @@ import { AstAnalyser } from "./AstAnalyser.js";
|
|
|
10
13
|
const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
|
|
11
14
|
|
|
12
15
|
export class EntryFilesAnalyser {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* @param {object} [options={}]
|
|
16
|
-
* @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()]
|
|
17
|
-
* @param {function} [options.loadExtensions]
|
|
18
|
-
*/
|
|
16
|
+
#rootPath = null;
|
|
17
|
+
|
|
19
18
|
constructor(options = {}) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const {
|
|
20
|
+
astAnalyzer = new AstAnalyser(),
|
|
21
|
+
loadExtensions,
|
|
22
|
+
rootPath = null
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
this.astAnalyzer = astAnalyzer;
|
|
26
|
+
const rawAllowedExtensions = loadExtensions
|
|
27
|
+
? loadExtensions(kDefaultExtensions)
|
|
23
28
|
: kDefaultExtensions;
|
|
24
29
|
|
|
25
30
|
this.allowedExtensions = new Set(rawAllowedExtensions);
|
|
31
|
+
this.#rootPath = options.rootPath === null ?
|
|
32
|
+
null : fileURLToPathExtended(rootPath);
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
async* analyse(
|
|
36
|
+
entryFiles,
|
|
37
|
+
options = {}
|
|
38
|
+
) {
|
|
39
|
+
this.dependencies = new DiGraph();
|
|
40
|
+
|
|
41
|
+
for (const entryFile of new Set(entryFiles)) {
|
|
42
|
+
const normalizedEntryFile = path.normalize(
|
|
43
|
+
fileURLToPathExtended(entryFile)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
yield* this.#analyseFile(
|
|
47
|
+
normalizedEntryFile,
|
|
48
|
+
this.#getRelativeFilePath(normalizedEntryFile),
|
|
49
|
+
options
|
|
50
|
+
);
|
|
39
51
|
}
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
#getRelativeFilePath(file) {
|
|
55
|
+
return this.#rootPath ? path.relative(this.#rootPath, file) : file;
|
|
56
|
+
}
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
async* #analyseFile(
|
|
59
|
+
file,
|
|
60
|
+
relativeFile,
|
|
61
|
+
options
|
|
62
|
+
) {
|
|
63
|
+
this.dependencies.addVertex({
|
|
64
|
+
id: relativeFile,
|
|
65
|
+
adjacentTo: [],
|
|
66
|
+
body: {}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const report = await this.astAnalyzer.analyseFile(
|
|
70
|
+
file,
|
|
71
|
+
options
|
|
72
|
+
);
|
|
73
|
+
yield { file: relativeFile, ...report };
|
|
47
74
|
|
|
48
75
|
if (!report.ok) {
|
|
49
76
|
return;
|
|
50
77
|
}
|
|
51
78
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const [name] of deps) {
|
|
60
|
-
const depPath = await this.#getInternalDepPath(name, basePath);
|
|
61
|
-
|
|
62
|
-
if (depPath && !this.analyzedDeps.has(depPath)) {
|
|
63
|
-
this.analyzedDeps.add(depPath);
|
|
79
|
+
for (const [name] of report.dependencies) {
|
|
80
|
+
const depFile = await this.#getInternalDepPath(
|
|
81
|
+
path.join(path.dirname(file), name)
|
|
82
|
+
);
|
|
83
|
+
if (depFile === null) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
64
86
|
|
|
65
|
-
|
|
87
|
+
const depRelativeFile = this.#getRelativeFilePath(depFile);
|
|
88
|
+
if (!this.dependencies.hasVertex(depRelativeFile)) {
|
|
89
|
+
this.dependencies.addVertex({
|
|
90
|
+
id: depRelativeFile,
|
|
91
|
+
adjacentTo: [],
|
|
92
|
+
body: {}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
yield* this.#analyseFile(
|
|
96
|
+
depFile,
|
|
97
|
+
depRelativeFile,
|
|
98
|
+
options
|
|
99
|
+
);
|
|
66
100
|
}
|
|
101
|
+
|
|
102
|
+
this.dependencies.addEdge({
|
|
103
|
+
from: relativeFile, to: depRelativeFile
|
|
104
|
+
});
|
|
67
105
|
}
|
|
68
106
|
}
|
|
69
107
|
|
|
70
|
-
async #getInternalDepPath(
|
|
71
|
-
|
|
72
|
-
|
|
108
|
+
async #getInternalDepPath(
|
|
109
|
+
filePath
|
|
110
|
+
) {
|
|
111
|
+
const fileExtension = path.extname(filePath);
|
|
73
112
|
|
|
74
|
-
if (
|
|
113
|
+
if (fileExtension === "") {
|
|
75
114
|
for (const ext of this.allowedExtensions) {
|
|
76
|
-
const depPathWithExt = `${
|
|
115
|
+
const depPathWithExt = `${filePath}.${ext}`;
|
|
77
116
|
|
|
78
117
|
const fileExist = await this.#fileExists(depPathWithExt);
|
|
79
118
|
if (fileExist) {
|
|
@@ -82,22 +121,24 @@ export class EntryFilesAnalyser {
|
|
|
82
121
|
}
|
|
83
122
|
}
|
|
84
123
|
else {
|
|
85
|
-
if (!this.allowedExtensions.has(
|
|
124
|
+
if (!this.allowedExtensions.has(fileExtension.slice(1))) {
|
|
86
125
|
return null;
|
|
87
126
|
}
|
|
88
127
|
|
|
89
|
-
const fileExist = await this.#fileExists(
|
|
128
|
+
const fileExist = await this.#fileExists(filePath);
|
|
90
129
|
if (fileExist) {
|
|
91
|
-
return
|
|
130
|
+
return filePath;
|
|
92
131
|
}
|
|
93
132
|
}
|
|
94
133
|
|
|
95
134
|
return null;
|
|
96
135
|
}
|
|
97
136
|
|
|
98
|
-
async #fileExists(
|
|
137
|
+
async #fileExists(
|
|
138
|
+
filePath
|
|
139
|
+
) {
|
|
99
140
|
try {
|
|
100
|
-
await fs.access(
|
|
141
|
+
await fs.access(filePath, fs.constants.R_OK);
|
|
101
142
|
|
|
102
143
|
return true;
|
|
103
144
|
}
|
|
@@ -110,3 +151,11 @@ export class EntryFilesAnalyser {
|
|
|
110
151
|
}
|
|
111
152
|
}
|
|
112
153
|
}
|
|
154
|
+
|
|
155
|
+
function fileURLToPathExtended(
|
|
156
|
+
file
|
|
157
|
+
) {
|
|
158
|
+
return file instanceof URL ?
|
|
159
|
+
fileURLToPath(file) :
|
|
160
|
+
file;
|
|
161
|
+
}
|
package/src/probes/isLiteral.js
CHANGED
|
@@ -5,6 +5,7 @@ import { builtinModules } from "repl";
|
|
|
5
5
|
import { Hex } from "@nodesecure/sec-literal";
|
|
6
6
|
|
|
7
7
|
const kMapRegexIps = Object.freeze({
|
|
8
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
8
9
|
regexIPv4: /^(https?:\/\/)(?!127\.)(?!.*:(?:0{1,3}|25[6-9])\.)(?!.*:(?:25[6-9])\.(?:0{1,3}|25[6-9])\.)(?!.*:(?:25[6-9])\.(?:25[6-9])\.(?:0{1,3}|25[6-9])\.)(?!.*:(?:25[6-9])\.(?:25[6-9])\.(?:25[6-9])\.(?:0{1,3}|25[6-9]))((?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])(?::\d{1,5})?(\/[^\s]*)?$/,
|
|
9
10
|
regexIPv6: /^(https?:\/\/)(\[[0-9A-Fa-f:]+\])(?::\d{1,5})?(\/[^\s]*)?$/
|
|
10
11
|
});
|
|
@@ -17,7 +17,9 @@ function validateNodeRequire(node, { tracer }) {
|
|
|
17
17
|
return [false];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const data = tracer.getDataFromIdentifier(id
|
|
20
|
+
const data = tracer.getDataFromIdentifier(id, {
|
|
21
|
+
removeGlobalIdentifier: true
|
|
22
|
+
});
|
|
21
23
|
|
|
22
24
|
return [
|
|
23
25
|
data !== null && data.name === "require",
|
|
@@ -135,8 +137,12 @@ function main(node, options) {
|
|
|
135
137
|
|
|
136
138
|
export default {
|
|
137
139
|
name: "isRequire",
|
|
138
|
-
validateNode: [
|
|
140
|
+
validateNode: [
|
|
141
|
+
validateNodeRequire,
|
|
142
|
+
validateNodeEvalRequire
|
|
143
|
+
],
|
|
139
144
|
main,
|
|
145
|
+
teardown,
|
|
140
146
|
breakOnMatch: true,
|
|
141
147
|
breakGroup: "import"
|
|
142
148
|
};
|
package/src/warnings.js
CHANGED
|
@@ -34,7 +34,7 @@ export const warnings = Object.freeze({
|
|
|
34
34
|
"suspicious-file": {
|
|
35
35
|
i18n: "sast_warnings.suspicious_file",
|
|
36
36
|
severity: "Critical",
|
|
37
|
-
experimental:
|
|
37
|
+
experimental: false
|
|
38
38
|
},
|
|
39
39
|
"obfuscated-code": {
|
|
40
40
|
i18n: "sast_warnings.obfuscated_code",
|
|
@@ -44,12 +44,12 @@ export const warnings = Object.freeze({
|
|
|
44
44
|
"weak-crypto": {
|
|
45
45
|
i18n: "sast_warnings.weak_crypto",
|
|
46
46
|
severity: "Information",
|
|
47
|
-
experimental:
|
|
47
|
+
experimental: false
|
|
48
48
|
},
|
|
49
49
|
"shady-link": {
|
|
50
50
|
i18n: "sast_warnings.shady_link",
|
|
51
51
|
severity: "Warning",
|
|
52
|
-
experimental:
|
|
52
|
+
experimental: false
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
|
package/types/api.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
// Third-party
|
|
2
|
+
import type { DiGraph, VertexDefinition, VertexBody } from "digraph-js";
|
|
3
|
+
import { Statement } from "meriyah";
|
|
4
|
+
|
|
5
|
+
// Internal
|
|
6
|
+
import {
|
|
7
|
+
Warning,
|
|
8
|
+
WarningName
|
|
9
|
+
} from "./warnings.js";
|
|
3
10
|
|
|
4
11
|
export {
|
|
5
12
|
AstAnalyser,
|
|
@@ -10,8 +17,6 @@ export {
|
|
|
10
17
|
|
|
11
18
|
JsSourceParser,
|
|
12
19
|
SourceParser,
|
|
13
|
-
runASTAnalysis,
|
|
14
|
-
runASTAnalysisOnFile,
|
|
15
20
|
|
|
16
21
|
RuntimeOptions,
|
|
17
22
|
RuntimeFileOptions,
|
|
@@ -105,25 +110,59 @@ interface SourceParser {
|
|
|
105
110
|
|
|
106
111
|
declare class AstAnalyser {
|
|
107
112
|
constructor(options?: AstAnalyserOptions);
|
|
108
|
-
analyse: (
|
|
109
|
-
|
|
113
|
+
analyse: (
|
|
114
|
+
str: string,
|
|
115
|
+
options?: RuntimeOptions
|
|
116
|
+
) => Report;
|
|
117
|
+
analyseFile(
|
|
118
|
+
pathToFile: string,
|
|
119
|
+
options?: RuntimeFileOptions
|
|
120
|
+
): Promise<ReportOnFile>;
|
|
121
|
+
analyseFileSync(
|
|
122
|
+
pathToFile: string,
|
|
123
|
+
options?: RuntimeFileOptions
|
|
124
|
+
): ReportOnFile;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare class SourceFile {
|
|
128
|
+
constructor(source: string, options: any);
|
|
129
|
+
addDependency(
|
|
130
|
+
name: string,
|
|
131
|
+
location?: string | null,
|
|
132
|
+
unsafe?: boolean
|
|
133
|
+
): void;
|
|
134
|
+
addWarning(
|
|
135
|
+
name: WarningName,
|
|
136
|
+
value: string,
|
|
137
|
+
location?: any
|
|
138
|
+
): void;
|
|
139
|
+
analyzeLiteral(node: any, inArrayExpr?: boolean): void;
|
|
140
|
+
getResult(isMinified: boolean): any;
|
|
141
|
+
walk(node: any): "skip" | null;
|
|
110
142
|
}
|
|
111
143
|
|
|
112
144
|
interface EntryFilesAnalyserOptions {
|
|
113
145
|
astAnalyzer?: AstAnalyser;
|
|
114
146
|
loadExtensions?: (defaults: string[]) => string[];
|
|
147
|
+
rootPath?: string | URL;
|
|
115
148
|
}
|
|
116
149
|
|
|
117
150
|
declare class EntryFilesAnalyser {
|
|
151
|
+
public astAnalyzer: AstAnalyser;
|
|
152
|
+
public allowedExtensions: Set<string>;
|
|
153
|
+
public dependencies: DiGraph<VertexDefinition<VertexBody>>;
|
|
154
|
+
|
|
118
155
|
constructor(options?: EntryFilesAnalyserOptions);
|
|
119
156
|
|
|
120
157
|
/**
|
|
121
158
|
* Asynchronously analyze a set of entry files yielding analysis reports.
|
|
122
159
|
*/
|
|
123
|
-
analyse(
|
|
160
|
+
analyse(
|
|
161
|
+
entryFiles: Iterable<string | URL>,
|
|
162
|
+
options?: RuntimeFileOptions
|
|
163
|
+
): AsyncGenerator<ReportOnFile & { file: string }>;
|
|
124
164
|
}
|
|
125
165
|
|
|
126
|
-
declare class JsSourceParser implements SourceParser {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
declare function runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions & AstAnalyserOptions): Promise<ReportOnFile>;
|
|
166
|
+
declare class JsSourceParser implements SourceParser {
|
|
167
|
+
parse(source: string, options: unknown): Statement[];
|
|
168
|
+
}
|