@nodesecure/js-x-ray 7.2.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 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
- ## Warnings Legends
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) | ✔️ | A suspicious file with more than ten encoded-literal in it |
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) | ✔️ | The code probably contains a weak crypto algorithm (md5, sha1...) |
137
- | [shady-link](./docs/shady-link.md) | ✔️ | The code contains shady/unsafe link |
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
 
@@ -307,7 +161,7 @@ $ yarn add @nodesecure/estree-ast-util
307
161
  ## Contributors ✨
308
162
 
309
163
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
310
- [![All Contributors](https://img.shields.io/badge/all_contributors-17-orange.svg?style=flat-square)](#contributors-)
164
+ [![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-)
311
165
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
312
166
 
313
167
  Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -339,6 +193,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
339
193
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/FredGuiou"><img src="https://avatars.githubusercontent.com/u/99122562?v=4?s=100" width="100px;" alt="FredGuiou"/><br /><sub><b>FredGuiou</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=FredGuiou" title="Documentation">📖</a> <a href="https://github.com/NodeSecure/js-x-ray/commits?author=FredGuiou" title="Code">💻</a></td>
340
194
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/madina0801"><img src="https://avatars.githubusercontent.com/u/101329759?v=4?s=100" width="100px;" alt="Madina"/><br /><sub><b>Madina</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=madina0801" title="Code">💻</a></td>
341
195
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/sairuss7"><img src="https://avatars.githubusercontent.com/u/87803528?v=4?s=100" width="100px;" alt="SairussDev"/><br /><sub><b>SairussDev</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=sairuss7" title="Code">💻</a></td>
196
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/fless-lab"><img src="https://avatars.githubusercontent.com/u/71844440?v=4?s=100" width="100px;" alt="Abdou-Raouf ATARMLA"/><br /><sub><b>Abdou-Raouf ATARMLA</b></sub></a><br /><a href="https://github.com/NodeSecure/js-x-ray/commits?author=fless-lab" title="Code">💻</a></td>
342
197
  </tr>
343
198
  </tbody>
344
199
  </table>
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,54 +1,4 @@
1
- // Import Internal Dependencies
2
- import { warnings } from "./src/warnings.js";
3
- import { JsSourceParser } from "./src/JsSourceParser.js";
4
- import { AstAnalyser } from "./src/AstAnalyser.js";
5
- import { EntryFilesAnalyser } from "./src/EntryFilesAnalyser.js";
6
-
7
- function runASTAnalysis(
8
- str,
9
- options = Object.create(null)
10
- ) {
11
- const {
12
- customParser = new JsSourceParser(),
13
- customProbes = [],
14
- skipDefaultProbes = false,
15
- ...opts
16
- } = options;
17
-
18
- const analyser = new AstAnalyser({
19
- customParser,
20
- customProbes,
21
- skipDefaultProbes
22
- });
23
-
24
- return analyser.analyse(str, opts);
25
- }
26
-
27
- async function runASTAnalysisOnFile(
28
- pathToFile,
29
- options = {}
30
- ) {
31
- const {
32
- customProbes = [],
33
- customParser = new JsSourceParser(),
34
- skipDefaultProbes = false,
35
- ...opts
36
- } = options;
37
-
38
- const analyser = new AstAnalyser({
39
- customParser,
40
- customProbes,
41
- skipDefaultProbes
42
- });
43
-
44
- return analyser.analyseFile(pathToFile, opts);
45
- }
46
-
47
- export {
48
- warnings,
49
- AstAnalyser,
50
- EntryFilesAnalyser,
51
- JsSourceParser,
52
- runASTAnalysis,
53
- runASTAnalysisOnFile
54
- };
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": "7.2.0",
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.3.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": "^4.3.3",
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
- "@nodesecure/eslint-config": "^1.6.0",
58
- "@types/node": "^20.6.2",
59
- "c8": "^9.0.0",
60
- "cross-env": "^7.0.3",
61
- "eslint": "^9.0.0",
62
- "glob": "^10.3.4",
63
- "iterator-matcher": "^2.1.0",
64
- "pkg-ok": "^3.0.0"
57
+ "@openally/config.eslint": "^1.0.0",
58
+ "@types/node": "^22.0.0",
59
+ "c8": "^10.1.2",
60
+ "glob": "^11.0.0",
61
+ "iterator-matcher": "^2.1.0"
65
62
  }
66
63
  }
@@ -1,5 +1,6 @@
1
1
  // Import Node.js Dependencies
2
2
  import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
3
4
  import path from "node:path";
4
5
 
5
6
  // Import Third-party Dependencies
@@ -127,6 +128,52 @@ export class AstAnalyser {
127
128
  }
128
129
  }
129
130
 
131
+ analyseFileSync(
132
+ pathToFile,
133
+ options = {}
134
+ ) {
135
+ try {
136
+ const {
137
+ packageName = null,
138
+ module = true,
139
+ removeHTMLComments = false,
140
+ initialize,
141
+ finalize
142
+ } = options;
143
+
144
+ const str = fsSync.readFileSync(pathToFile, "utf-8");
145
+ const filePathString = pathToFile instanceof URL ? pathToFile.href : pathToFile;
146
+
147
+ const isMin = filePathString.includes(".min") || isMinified(str);
148
+ const data = this.analyse(str, {
149
+ isMinified: isMin,
150
+ module: path.extname(filePathString) === ".mjs" ? true : module,
151
+ removeHTMLComments,
152
+ initialize,
153
+ finalize
154
+ });
155
+
156
+ if (packageName !== null) {
157
+ data.dependencies.delete(packageName);
158
+ }
159
+
160
+ return {
161
+ ok: true,
162
+ dependencies: data.dependencies,
163
+ warnings: data.warnings,
164
+ isMinified: !data.isOneLineRequire && isMin
165
+ };
166
+ }
167
+ catch (error) {
168
+ return {
169
+ ok: false,
170
+ warnings: [
171
+ { kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
172
+ ]
173
+ };
174
+ }
175
+ }
176
+
130
177
  /**
131
178
  * @param {!string} source
132
179
  * @param {object} options
@@ -63,6 +63,9 @@ export class Deobfuscator {
63
63
  * @param {*} node
64
64
  */
65
65
  #extractCounterIdentifiers(nc, node) {
66
+ if (node === null) {
67
+ return;
68
+ }
66
69
  const { type } = nc;
67
70
 
68
71
  switch (type) {
@@ -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
- * @constructor
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
- this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
21
- const rawAllowedExtensions = options.loadExtensions
22
- ? options.loadExtensions(kDefaultExtensions)
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
- * Asynchronously analyze a set of entry files yielding analysis reports.
30
- *
31
- * @param {(string | URL)[]} entryFiles
32
- * @yields {Object} - Yields an object containing the analysis report for each file.
33
- */
34
- async* analyse(entryFiles) {
35
- this.analyzedDeps = new Set();
36
-
37
- for (const file of entryFiles) {
38
- yield* this.#analyzeFile(file);
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
- async* #analyzeFile(file) {
43
- const filePath = file instanceof URL ? fileURLToPath(file) : file;
44
- const report = await this.astAnalyzer.analyseFile(file);
54
+ #getRelativeFilePath(file) {
55
+ return this.#rootPath ? path.relative(this.#rootPath, file) : file;
56
+ }
45
57
 
46
- yield { url: filePath, ...report };
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
- yield* this.#analyzeDeps(
53
- report.dependencies,
54
- path.dirname(filePath)
55
- );
56
- }
57
-
58
- async* #analyzeDeps(deps, basePath) {
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
- yield* this.#analyzeFile(depPath);
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(name, basePath) {
71
- const depPath = path.join(basePath, name);
72
- const existingExt = path.extname(name);
108
+ async #getInternalDepPath(
109
+ filePath
110
+ ) {
111
+ const fileExtension = path.extname(filePath);
73
112
 
74
- if (existingExt === "") {
113
+ if (fileExtension === "") {
75
114
  for (const ext of this.allowedExtensions) {
76
- const depPathWithExt = `${depPath}.${ext}`;
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(existingExt.slice(1))) {
124
+ if (!this.allowedExtensions.has(fileExtension.slice(1))) {
86
125
  return null;
87
126
  }
88
127
 
89
- const fileExist = await this.#fileExists(depPath);
128
+ const fileExist = await this.#fileExists(filePath);
90
129
  if (fileExist) {
91
- return depPath;
130
+ return filePath;
92
131
  }
93
132
  }
94
133
 
95
134
  return null;
96
135
  }
97
136
 
98
- async #fileExists(path) {
137
+ async #fileExists(
138
+ filePath
139
+ ) {
99
140
  try {
100
- await fs.access(path, fs.constants.R_OK);
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
+ }
@@ -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: [validateNodeRequire, validateNodeEvalRequire],
140
+ validateNode: [
141
+ validateNodeRequire,
142
+ validateNodeEvalRequire
143
+ ],
139
144
  main,
145
+ teardown,
140
146
  breakOnMatch: true,
141
147
  breakGroup: "import"
142
148
  };
@@ -2,17 +2,23 @@
2
2
  import { exportAssignmentHasRequireLeave } from "./exportAssignmentHasRequireLeave.js";
3
3
 
4
4
  export function isOneLineExpressionExport(body) {
5
- if (body.length > 1) {
5
+ if (body.length === 0 || body.length > 1) {
6
6
  return false;
7
7
  }
8
8
 
9
- if (body[0].type !== "ExpressionStatement") {
9
+ const [firstNode] = body;
10
+ if (firstNode.type !== "ExpressionStatement") {
10
11
  return false;
11
12
  }
12
13
 
13
- if (body[0].expression.type !== "AssignmentExpression") {
14
- return false;
14
+ switch (firstNode.expression.type) {
15
+ // module.exports = require('...');
16
+ case "AssignmentExpression":
17
+ return exportAssignmentHasRequireLeave(firstNode.expression.right);
18
+ // require('...');
19
+ case "CallExpression":
20
+ return exportAssignmentHasRequireLeave(firstNode.expression);
21
+ default:
22
+ return false;
15
23
  }
16
-
17
- return exportAssignmentHasRequireLeave(body[0].expression.right);
18
24
  }
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: true
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: true
47
+ experimental: false
48
48
  },
49
49
  "shady-link": {
50
50
  i18n: "sast_warnings.shady_link",
51
51
  severity: "Warning",
52
- experimental: true
52
+ experimental: false
53
53
  }
54
54
  });
55
55
 
package/types/api.d.ts CHANGED
@@ -1,5 +1,12 @@
1
- import { Warning } from "./warnings.js";
2
- import { Statement } from "meriyah/dist/src/estree.js";
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: (str: string, options?: RuntimeOptions) => Report;
109
- analyseFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
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(entryFiles: (string | URL)[]): AsyncGenerator<ReportOnFile & { url: string }>;
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
- declare function runASTAnalysis(str: string, options?: RuntimeOptions & AstAnalyserOptions): Report;
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
+ }