@nodesecure/js-x-ray 7.3.0 → 8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021-2024 NodeSecure
3
+ Copyright (c) 2021-2025 NodeSecure
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
 
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
- // 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
- 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,15 +1,14 @@
1
1
  {
2
2
  "name": "@nodesecure/js-x-ray",
3
- "version": "7.3.0",
3
+ "version": "8.1.0",
4
4
  "description": "JavaScript AST XRay analysis",
5
5
  "type": "module",
6
6
  "exports": "./index.js",
7
7
  "engines": {
8
- "node": ">=18.0.0"
8
+ "node": ">=20.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": "^6.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",
57
+ "@openally/config.eslint": "^2.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
  }
@@ -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
+ }
@@ -11,6 +11,7 @@ import isImportDeclaration from "./probes/isImportDeclaration.js";
11
11
  import isWeakCrypto from "./probes/isWeakCrypto.js";
12
12
  import isBinaryExpression from "./probes/isBinaryExpression.js";
13
13
  import isArrayExpression from "./probes/isArrayExpression.js";
14
+ import isESMExport from "./probes/isESMExport.js";
14
15
 
15
16
  // Import Internal Dependencies
16
17
  import { SourceFile } from "./SourceFile.js";
@@ -39,6 +40,7 @@ export class ProbeRunner {
39
40
  */
40
41
  static Defaults = [
41
42
  isRequire,
43
+ isESMExport,
42
44
  isUnsafeCallee,
43
45
  isLiteral,
44
46
  isLiteralRegex,
package/src/SourceFile.js CHANGED
@@ -129,7 +129,7 @@ export class SourceFile {
129
129
  this.deobfuscator.walk(node);
130
130
 
131
131
  // Detect TryStatement and CatchClause to known which dependency is required in a Try {} clause
132
- if (node.type === "TryStatement" && typeof node.handler !== "undefined") {
132
+ if (node.type === "TryStatement" && node.handler) {
133
133
  this.inTryStatement = true;
134
134
  }
135
135
  else if (node.type === "CatchClause") {
@@ -0,0 +1,31 @@
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
+ };
@@ -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
  };
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
+ }