@nodesecure/js-x-ray 4.3.0 → 4.4.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 +32 -0
- package/index.d.ts +124 -99
- package/index.js +125 -98
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -73,6 +73,38 @@ The analysis will return: `http` (in try), `crypto`, `util` and `fs`.
|
|
|
73
73
|
|
|
74
74
|
> ⚠️ There is also a lot of suspicious code example in the root cases directory. Feel free to try the tool on these files.
|
|
75
75
|
|
|
76
|
+
## Warnings
|
|
77
|
+
|
|
78
|
+
This section describes how use `warnings` export.
|
|
79
|
+
|
|
80
|
+
The structure of the `warnings` is as follows:
|
|
81
|
+
```
|
|
82
|
+
/**
|
|
83
|
+
* @property {object} warnings - The default values for Constants.
|
|
84
|
+
* @property {string} warnings[name] - The default warning name (parsingError, unsafeImport etc...).
|
|
85
|
+
* @property {string} warnings[name].i18n - i18n token.
|
|
86
|
+
* @property {string} warnings[name].code - Used to perform unit tests.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
export const warnings = Object.freeze({
|
|
90
|
+
parsingError: {
|
|
91
|
+
i18n: "sast_warnings.ast_error"
|
|
92
|
+
code: "ast-error",
|
|
93
|
+
},
|
|
94
|
+
...otherWarnings
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
We make a call to `i18n` through the package `NodeSecure/i18n` to get the translation.
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
import * as jsxray from "@nodesecure/js-x-ray";
|
|
102
|
+
import * as i18n from "@nodesecure/i18n";
|
|
103
|
+
|
|
104
|
+
console.log(i18n.getToken(jsxray.warnings.parsingError.i18n));
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
|
|
76
108
|
## Warnings Legends (v2.0+)
|
|
77
109
|
|
|
78
110
|
> Node-secure versions equal or lower than 0.7.0 are no longer compatible with the warnings table below.
|
package/index.d.ts
CHANGED
|
@@ -1,99 +1,124 @@
|
|
|
1
|
-
declare class ASTDeps {
|
|
2
|
-
constructor();
|
|
3
|
-
removeByName(name: string): void;
|
|
4
|
-
add(depName: string): void;
|
|
5
|
-
getDependenciesInTryStatement(): IterableIterator<string>;
|
|
6
|
-
|
|
7
|
-
public isInTryStmt: boolean;
|
|
8
|
-
public dependencies: Record<string, JSXRay.Dependency>;
|
|
9
|
-
public readonly size: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
declare namespace JSXRay {
|
|
13
|
-
type kindWithValue = "parsing-error"
|
|
14
|
-
| "encoded-literal"
|
|
15
|
-
| "unsafe-regex"
|
|
16
|
-
| "unsafe-stmt"
|
|
17
|
-
| "unsafe-assign"
|
|
18
|
-
| "short-identifiers"
|
|
19
|
-
| "suspicious-literal"
|
|
20
|
-
| "obfuscated-code";
|
|
21
|
-
|
|
22
|
-
type WarningLocation = [[number, number], [number, number]];
|
|
23
|
-
interface BaseWarning {
|
|
24
|
-
kind: "unsafe-import" | kindWithValue;
|
|
25
|
-
file?: string;
|
|
26
|
-
value: string;
|
|
27
|
-
location: WarningLocation | WarningLocation[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Warning<T extends BaseWarning> = T extends { kind: kindWithValue } ? T : Omit<T, "value">;
|
|
31
|
-
|
|
32
|
-
interface Report {
|
|
33
|
-
dependencies: ASTDeps;
|
|
34
|
-
warnings: Warning<BaseWarning>[];
|
|
35
|
-
idsLengthAvg: number;
|
|
36
|
-
stringScore: number;
|
|
37
|
-
isOneLineRequire: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface SourceLocation {
|
|
41
|
-
start: {
|
|
42
|
-
line: number;
|
|
43
|
-
column: number;
|
|
44
|
-
};
|
|
45
|
-
end: {
|
|
46
|
-
line: number;
|
|
47
|
-
column: number;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface Dependency {
|
|
52
|
-
unsafe: boolean;
|
|
53
|
-
inTry: boolean;
|
|
54
|
-
location?: SourceLocation;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface WarningsNames {
|
|
58
|
-
parsingError:
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
1
|
+
declare class ASTDeps {
|
|
2
|
+
constructor();
|
|
3
|
+
removeByName(name: string): void;
|
|
4
|
+
add(depName: string): void;
|
|
5
|
+
getDependenciesInTryStatement(): IterableIterator<string>;
|
|
6
|
+
|
|
7
|
+
public isInTryStmt: boolean;
|
|
8
|
+
public dependencies: Record<string, JSXRay.Dependency>;
|
|
9
|
+
public readonly size: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare namespace JSXRay {
|
|
13
|
+
type kindWithValue = "parsing-error"
|
|
14
|
+
| "encoded-literal"
|
|
15
|
+
| "unsafe-regex"
|
|
16
|
+
| "unsafe-stmt"
|
|
17
|
+
| "unsafe-assign"
|
|
18
|
+
| "short-identifiers"
|
|
19
|
+
| "suspicious-literal"
|
|
20
|
+
| "obfuscated-code";
|
|
21
|
+
|
|
22
|
+
type WarningLocation = [[number, number], [number, number]];
|
|
23
|
+
interface BaseWarning {
|
|
24
|
+
kind: "unsafe-import" | kindWithValue;
|
|
25
|
+
file?: string;
|
|
26
|
+
value: string;
|
|
27
|
+
location: WarningLocation | WarningLocation[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Warning<T extends BaseWarning> = T extends { kind: kindWithValue } ? T : Omit<T, "value">;
|
|
31
|
+
|
|
32
|
+
interface Report {
|
|
33
|
+
dependencies: ASTDeps;
|
|
34
|
+
warnings: Warning<BaseWarning>[];
|
|
35
|
+
idsLengthAvg: number;
|
|
36
|
+
stringScore: number;
|
|
37
|
+
isOneLineRequire: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SourceLocation {
|
|
41
|
+
start: {
|
|
42
|
+
line: number;
|
|
43
|
+
column: number;
|
|
44
|
+
};
|
|
45
|
+
end: {
|
|
46
|
+
line: number;
|
|
47
|
+
column: number;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface Dependency {
|
|
52
|
+
unsafe: boolean;
|
|
53
|
+
inTry: boolean;
|
|
54
|
+
location?: SourceLocation;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface WarningsNames {
|
|
58
|
+
parsingError: {
|
|
59
|
+
code: "ast-error",
|
|
60
|
+
i18n: "sast_warnings.ast_error"
|
|
61
|
+
},
|
|
62
|
+
unsafeImport: {
|
|
63
|
+
code: "unsafe-import",
|
|
64
|
+
i18n: "sast_warnings.unsafe_import"
|
|
65
|
+
},
|
|
66
|
+
unsafeRegex: {
|
|
67
|
+
code: "unsafe-regex",
|
|
68
|
+
i18n: "sast_warnings.unsafe_regex"
|
|
69
|
+
},
|
|
70
|
+
unsafeStmt: {
|
|
71
|
+
code: "unsafe-stmt",
|
|
72
|
+
i18n: "sast_warnings.unsafe_stmt"
|
|
73
|
+
},
|
|
74
|
+
unsafeAssign: {
|
|
75
|
+
code: "unsafe-assign",
|
|
76
|
+
i18n: "sast_warnings.unsafe_assign"
|
|
77
|
+
},
|
|
78
|
+
encodedLiteral: {
|
|
79
|
+
code: "encoded-literal",
|
|
80
|
+
i18n: "sast_warnings.encoded_literal"
|
|
81
|
+
},
|
|
82
|
+
shortIdentifiers: {
|
|
83
|
+
code: "short-identifiers",
|
|
84
|
+
i18n: "sast_warnings.short_identifiers"
|
|
85
|
+
},
|
|
86
|
+
suspiciousLiteral: {
|
|
87
|
+
code: "suspicious-literal",
|
|
88
|
+
i18n: "sast_warnings.suspicious_literal"
|
|
89
|
+
},
|
|
90
|
+
obfuscatedCode: {
|
|
91
|
+
code: "obfuscated-code",
|
|
92
|
+
i18n: "sast_warnings.obfuscated_code"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface RuntimeOptions {
|
|
97
|
+
module?: boolean;
|
|
98
|
+
isMinified?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function runASTAnalysis(str: string, options?: RuntimeOptions): Report;
|
|
102
|
+
|
|
103
|
+
export type ReportOnFile = {
|
|
104
|
+
ok: true,
|
|
105
|
+
warnings: Warning<BaseWarning>[];
|
|
106
|
+
dependencies: ASTDeps;
|
|
107
|
+
isMinified: boolean;
|
|
108
|
+
} | {
|
|
109
|
+
ok: false,
|
|
110
|
+
warnings: Warning<BaseWarning>[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface RuntimeFileOptions {
|
|
114
|
+
packageName?: string;
|
|
115
|
+
module?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
|
|
119
|
+
|
|
120
|
+
export const warnings: WarningsNames;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export = JSXRay;
|
|
124
|
+
export as namespace JSXRay;
|
package/index.js
CHANGED
|
@@ -1,98 +1,125 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import fs from "fs/promises";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
// Import Third-party Dependencies
|
|
6
|
-
import { walk } from "estree-walker";
|
|
7
|
-
import * as meriyah from "meriyah";
|
|
8
|
-
import isMinified from "is-minified-code";
|
|
9
|
-
|
|
10
|
-
// Import Internal Dependencies
|
|
11
|
-
import Analysis from "./src/Analysis.js";
|
|
12
|
-
|
|
13
|
-
export function runASTAnalysis(str, options = Object.create(null)) {
|
|
14
|
-
const { module = true, isMinified = false } = options;
|
|
15
|
-
|
|
16
|
-
// Note: if the file start with a shebang then we remove it because 'parseScript' may fail to parse it.
|
|
17
|
-
// Example: #!/usr/bin/env node
|
|
18
|
-
const strToAnalyze = str.charAt(0) === "#" ? str.slice(str.indexOf("\n")) : str;
|
|
19
|
-
const isEcmaScriptModule = Boolean(module);
|
|
20
|
-
const { body } = meriyah.parseScript(strToAnalyze, {
|
|
21
|
-
next: true,
|
|
22
|
-
loc: true,
|
|
23
|
-
raw: true,
|
|
24
|
-
module: isEcmaScriptModule,
|
|
25
|
-
globalReturn: !isEcmaScriptModule
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const sastAnalysis = new Analysis();
|
|
29
|
-
sastAnalysis.analyzeSourceString(str);
|
|
30
|
-
|
|
31
|
-
// we walk each AST Nodes, this is a purely synchronous I/O
|
|
32
|
-
walk(body, {
|
|
33
|
-
enter(node) {
|
|
34
|
-
// Skip the root of the AST.
|
|
35
|
-
if (Array.isArray(node)) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const action = sastAnalysis.walk(node);
|
|
40
|
-
if (action === "skip") {
|
|
41
|
-
this.skip();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const dependencies = sastAnalysis.dependencies;
|
|
47
|
-
const { idsLengthAvg, stringScore, warnings } = sastAnalysis.getResult(isMinified);
|
|
48
|
-
const isOneLineRequire = body.length <= 1 && dependencies.size <= 1;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
dependencies, warnings, idsLengthAvg, stringScore, isOneLineRequire
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function runASTAnalysisOnFile(pathToFile, options = {}) {
|
|
56
|
-
try {
|
|
57
|
-
const { packageName = null, module = true } = options;
|
|
58
|
-
const str = await fs.readFile(pathToFile, "utf-8");
|
|
59
|
-
|
|
60
|
-
const isMin = pathToFile.includes(".min") || isMinified(str);
|
|
61
|
-
const data = runASTAnalysis(str, {
|
|
62
|
-
isMinified: isMin,
|
|
63
|
-
module: path.extname(pathToFile) === ".mjs" ? true : module
|
|
64
|
-
});
|
|
65
|
-
if (packageName !== null) {
|
|
66
|
-
data.dependencies.removeByName(packageName);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
ok: true,
|
|
71
|
-
dependencies: data.dependencies,
|
|
72
|
-
warnings: data.warnings,
|
|
73
|
-
isMinified: !data.isOneLineRequire && isMin
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
return {
|
|
78
|
-
ok: false,
|
|
79
|
-
warnings: [
|
|
80
|
-
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
|
|
81
|
-
]
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// Import Third-party Dependencies
|
|
6
|
+
import { walk } from "estree-walker";
|
|
7
|
+
import * as meriyah from "meriyah";
|
|
8
|
+
import isMinified from "is-minified-code";
|
|
9
|
+
|
|
10
|
+
// Import Internal Dependencies
|
|
11
|
+
import Analysis from "./src/Analysis.js";
|
|
12
|
+
|
|
13
|
+
export function runASTAnalysis(str, options = Object.create(null)) {
|
|
14
|
+
const { module = true, isMinified = false } = options;
|
|
15
|
+
|
|
16
|
+
// Note: if the file start with a shebang then we remove it because 'parseScript' may fail to parse it.
|
|
17
|
+
// Example: #!/usr/bin/env node
|
|
18
|
+
const strToAnalyze = str.charAt(0) === "#" ? str.slice(str.indexOf("\n")) : str;
|
|
19
|
+
const isEcmaScriptModule = Boolean(module);
|
|
20
|
+
const { body } = meriyah.parseScript(strToAnalyze, {
|
|
21
|
+
next: true,
|
|
22
|
+
loc: true,
|
|
23
|
+
raw: true,
|
|
24
|
+
module: isEcmaScriptModule,
|
|
25
|
+
globalReturn: !isEcmaScriptModule
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const sastAnalysis = new Analysis();
|
|
29
|
+
sastAnalysis.analyzeSourceString(str);
|
|
30
|
+
|
|
31
|
+
// we walk each AST Nodes, this is a purely synchronous I/O
|
|
32
|
+
walk(body, {
|
|
33
|
+
enter(node) {
|
|
34
|
+
// Skip the root of the AST.
|
|
35
|
+
if (Array.isArray(node)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const action = sastAnalysis.walk(node);
|
|
40
|
+
if (action === "skip") {
|
|
41
|
+
this.skip();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const dependencies = sastAnalysis.dependencies;
|
|
47
|
+
const { idsLengthAvg, stringScore, warnings } = sastAnalysis.getResult(isMinified);
|
|
48
|
+
const isOneLineRequire = body.length <= 1 && dependencies.size <= 1;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
dependencies, warnings, idsLengthAvg, stringScore, isOneLineRequire
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runASTAnalysisOnFile(pathToFile, options = {}) {
|
|
56
|
+
try {
|
|
57
|
+
const { packageName = null, module = true } = options;
|
|
58
|
+
const str = await fs.readFile(pathToFile, "utf-8");
|
|
59
|
+
|
|
60
|
+
const isMin = pathToFile.includes(".min") || isMinified(str);
|
|
61
|
+
const data = runASTAnalysis(str, {
|
|
62
|
+
isMinified: isMin,
|
|
63
|
+
module: path.extname(pathToFile) === ".mjs" ? true : module
|
|
64
|
+
});
|
|
65
|
+
if (packageName !== null) {
|
|
66
|
+
data.dependencies.removeByName(packageName);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
dependencies: data.dependencies,
|
|
72
|
+
warnings: data.warnings,
|
|
73
|
+
isMinified: !data.isOneLineRequire && isMin
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
warnings: [
|
|
80
|
+
{ kind: "parsing-error", value: error.message, location: [[0, 0], [0, 0]] }
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const warnings = Object.freeze({
|
|
87
|
+
parsingError: {
|
|
88
|
+
code: "ast-error",
|
|
89
|
+
i18n: "sast_warnings.ast_error"
|
|
90
|
+
},
|
|
91
|
+
unsafeImport: {
|
|
92
|
+
code: "unsafe-import",
|
|
93
|
+
i18n: "sast_warnings.unsafe_import"
|
|
94
|
+
},
|
|
95
|
+
unsafeRegex: {
|
|
96
|
+
code: "unsafe-regex",
|
|
97
|
+
i18n: "sast_warnings.unsafe_regex"
|
|
98
|
+
},
|
|
99
|
+
unsafeStmt: {
|
|
100
|
+
code: "unsafe-stmt",
|
|
101
|
+
i18n: "sast_warnings.unsafe_stmt"
|
|
102
|
+
},
|
|
103
|
+
unsafeAssign: {
|
|
104
|
+
code: "unsafe-assign",
|
|
105
|
+
i18n: "sast_warnings.unsafe_assign"
|
|
106
|
+
},
|
|
107
|
+
encodedLiteral: {
|
|
108
|
+
code: "encoded-literal",
|
|
109
|
+
i18n: "sast_warnings.encoded_literal"
|
|
110
|
+
},
|
|
111
|
+
shortIdentifiers: {
|
|
112
|
+
code: "short-identifiers",
|
|
113
|
+
i18n: "sast_warnings.short_identifiers"
|
|
114
|
+
},
|
|
115
|
+
suspiciousLiteral: {
|
|
116
|
+
code: "suspicious-literal",
|
|
117
|
+
i18n: "sast_warnings.suspicious_literal"
|
|
118
|
+
},
|
|
119
|
+
obfuscatedCode: {
|
|
120
|
+
code: "obfuscated-code",
|
|
121
|
+
i18n: "sast_warnings.obfuscated_code"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodesecure/js-x-ray",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "JavaScript AST XRay analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./index.js",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"@slimio/is": "^1.5.1",
|
|
49
49
|
"@small-tech/esm-tape-runner": "^2.0.0",
|
|
50
50
|
"@small-tech/tap-monkey": "^1.3.0",
|
|
51
|
-
"@types/node": "^17.0.
|
|
51
|
+
"@types/node": "^17.0.31",
|
|
52
52
|
"cross-env": "^7.0.3",
|
|
53
|
-
"eslint": "^8.
|
|
53
|
+
"eslint": "^8.15.0",
|
|
54
54
|
"pkg-ok": "^3.0.0",
|
|
55
|
-
"tape": "^5.5.
|
|
55
|
+
"tape": "^5.5.3"
|
|
56
56
|
}
|
|
57
57
|
}
|