@nodesecure/js-x-ray 4.5.0 → 5.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 +21 -22
- package/index.d.ts +34 -140
- package/index.js +2 -56
- package/package.json +1 -1
- package/src/Analysis.js +14 -27
- package/src/constants.js +0 -13
- package/src/probes/index.js +68 -68
- package/src/probes/isImportDeclaration.js +1 -4
- package/src/probes/isLiteral.js +3 -3
- package/src/probes/isLiteralRegex.js +1 -2
- package/src/probes/isRegexObject.js +1 -2
- package/src/probes/isRequire.js +159 -160
- package/src/probes/isUnsafeCallee.js +1 -2
- package/src/probes/isVariableDeclaration.js +104 -104
- package/src/probes/isWeakCrypto.js +4 -7
- package/src/utils.js +0 -22
- package/src/warnings.js +70 -0
package/README.md
CHANGED
|
@@ -77,24 +77,23 @@ The analysis will return: `http` (in try), `crypto`, `util` and `fs`.
|
|
|
77
77
|
|
|
78
78
|
This section describes how use `warnings` export.
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
80
|
+
```ts
|
|
81
|
+
type WarningName = "parsing-error"
|
|
82
|
+
| "encoded-literal"
|
|
83
|
+
| "unsafe-regex"
|
|
84
|
+
| "unsafe-stmt"
|
|
85
|
+
| "unsafe-assign"
|
|
86
|
+
| "short-identifiers"
|
|
87
|
+
| "suspicious-literal"
|
|
88
|
+
| "obfuscated-code"
|
|
89
|
+
| "weak-crypto"
|
|
90
|
+
| "unsafe-import";
|
|
91
|
+
|
|
92
|
+
declare const warnings: Record<WarningName, {
|
|
93
|
+
i18n: string;
|
|
94
|
+
severity: "Information" | "Warning" | "Critical";
|
|
95
|
+
experimental?: boolean;
|
|
96
|
+
}>;
|
|
98
97
|
```
|
|
99
98
|
|
|
100
99
|
We make a call to `i18n` through the package `NodeSecure/i18n` to get the translation.
|
|
@@ -103,7 +102,7 @@ We make a call to `i18n` through the package `NodeSecure/i18n` to get the transl
|
|
|
103
102
|
import * as jsxray from "@nodesecure/js-x-ray";
|
|
104
103
|
import * as i18n from "@nodesecure/i18n";
|
|
105
104
|
|
|
106
|
-
console.log(i18n.getToken(jsxray.warnings.
|
|
105
|
+
console.log(i18n.getToken(jsxray.warnings["parsing-error"].i18n));
|
|
107
106
|
```
|
|
108
107
|
|
|
109
108
|
## Warnings Legends
|
|
@@ -142,7 +141,7 @@ The method take a first argument which is the code you want to analyse. It will
|
|
|
142
141
|
```ts
|
|
143
142
|
interface Report {
|
|
144
143
|
dependencies: ASTDeps;
|
|
145
|
-
warnings: Warning
|
|
144
|
+
warnings: Warning[];
|
|
146
145
|
idsLengthAvg: number;
|
|
147
146
|
stringScore: number;
|
|
148
147
|
isOneLineRequire: boolean;
|
|
@@ -166,12 +165,12 @@ Run the SAST scanner on a given JavaScript file.
|
|
|
166
165
|
```ts
|
|
167
166
|
export type ReportOnFile = {
|
|
168
167
|
ok: true,
|
|
169
|
-
warnings: Warning
|
|
168
|
+
warnings: Warning[];
|
|
170
169
|
dependencies: ASTDeps;
|
|
171
170
|
isMinified: boolean;
|
|
172
171
|
} | {
|
|
173
172
|
ok: false,
|
|
174
|
-
warnings: Warning
|
|
173
|
+
warnings: Warning[];
|
|
175
174
|
}
|
|
176
175
|
```
|
|
177
176
|
|
package/index.d.ts
CHANGED
|
@@ -1,140 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
warnings: Warning<BaseWarning>[];
|
|
36
|
-
idsLengthAvg: number;
|
|
37
|
-
stringScore: number;
|
|
38
|
-
isOneLineRequire: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface SourceLocation {
|
|
42
|
-
start: {
|
|
43
|
-
line: number;
|
|
44
|
-
column: number;
|
|
45
|
-
};
|
|
46
|
-
end: {
|
|
47
|
-
line: number;
|
|
48
|
-
column: number;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface Dependency {
|
|
53
|
-
unsafe: boolean;
|
|
54
|
-
inTry: boolean;
|
|
55
|
-
location?: SourceLocation;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface WarningsNames {
|
|
59
|
-
parsingError: {
|
|
60
|
-
code: "ast-error",
|
|
61
|
-
i18n: "sast_warnings.ast_error",
|
|
62
|
-
severity: "Information"
|
|
63
|
-
},
|
|
64
|
-
unsafeImport: {
|
|
65
|
-
code: "unsafe-import",
|
|
66
|
-
i18n: "sast_warnings.unsafe_import",
|
|
67
|
-
severity: "Warning"
|
|
68
|
-
},
|
|
69
|
-
unsafeRegex: {
|
|
70
|
-
code: "unsafe-regex",
|
|
71
|
-
i18n: "sast_warnings.unsafe_regex",
|
|
72
|
-
severity: "Warning"
|
|
73
|
-
},
|
|
74
|
-
unsafeStmt: {
|
|
75
|
-
code: "unsafe-stmt",
|
|
76
|
-
i18n: "sast_warnings.unsafe_stmt",
|
|
77
|
-
severity: "Warning"
|
|
78
|
-
},
|
|
79
|
-
unsafeAssign: {
|
|
80
|
-
code: "unsafe-assign",
|
|
81
|
-
i18n: "sast_warnings.unsafe_assign",
|
|
82
|
-
severity: "Warning"
|
|
83
|
-
},
|
|
84
|
-
encodedLiteral: {
|
|
85
|
-
code: "encoded-literal",
|
|
86
|
-
i18n: "sast_warnings.encoded_literal",
|
|
87
|
-
severity: "Information"
|
|
88
|
-
},
|
|
89
|
-
shortIdentifiers: {
|
|
90
|
-
code: "short-identifiers",
|
|
91
|
-
i18n: "sast_warnings.short_identifiers",
|
|
92
|
-
severity: "Warning"
|
|
93
|
-
},
|
|
94
|
-
suspiciousLiteral: {
|
|
95
|
-
code: "suspicious-literal",
|
|
96
|
-
i18n: "sast_warnings.suspicious_literal",
|
|
97
|
-
severity: "Warning"
|
|
98
|
-
},
|
|
99
|
-
obfuscatedCode: {
|
|
100
|
-
code: "obfuscated-code",
|
|
101
|
-
i18n: "sast_warnings.obfuscated_code",
|
|
102
|
-
severity: "Critical"
|
|
103
|
-
},
|
|
104
|
-
weakCrypto: {
|
|
105
|
-
code: "weak-crypto",
|
|
106
|
-
i18n: "sast_warnings.weak_crypto",
|
|
107
|
-
severity: "Information",
|
|
108
|
-
experimental: true
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
interface RuntimeOptions {
|
|
113
|
-
module?: boolean;
|
|
114
|
-
isMinified?: boolean;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function runASTAnalysis(str: string, options?: RuntimeOptions): Report;
|
|
118
|
-
|
|
119
|
-
export type ReportOnFile = {
|
|
120
|
-
ok: true,
|
|
121
|
-
warnings: Warning<BaseWarning>[];
|
|
122
|
-
dependencies: ASTDeps;
|
|
123
|
-
isMinified: boolean;
|
|
124
|
-
} | {
|
|
125
|
-
ok: false,
|
|
126
|
-
warnings: Warning<BaseWarning>[];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface RuntimeFileOptions {
|
|
130
|
-
packageName?: string;
|
|
131
|
-
module?: boolean;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function runASTAnalysisOnFile(pathToFile: string, options?: RuntimeFileOptions): Promise<ReportOnFile>;
|
|
135
|
-
|
|
136
|
-
export const warnings: WarningsNames;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export = JSXRay;
|
|
140
|
-
export as namespace JSXRay;
|
|
1
|
+
import {
|
|
2
|
+
runASTAnalysis,
|
|
3
|
+
runASTAnalysisOnFile,
|
|
4
|
+
Report,
|
|
5
|
+
ReportOnFile,
|
|
6
|
+
RuntimeFileOptions,
|
|
7
|
+
RuntimeOptions
|
|
8
|
+
} from "./types/api";
|
|
9
|
+
import {
|
|
10
|
+
Warning,
|
|
11
|
+
WarningDefault,
|
|
12
|
+
WarningLocation,
|
|
13
|
+
WarningName,
|
|
14
|
+
WarningNameWithValue
|
|
15
|
+
} from "./types/warnings";
|
|
16
|
+
import { ASTDeps } from "./types/astdeps";
|
|
17
|
+
|
|
18
|
+
declare const warnings: Record<WarningName, Pick<WarningDefault, "experimental" | "i18n" | "severity">>;
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
warnings,
|
|
22
|
+
runASTAnalysis,
|
|
23
|
+
runASTAnalysisOnFile,
|
|
24
|
+
Report,
|
|
25
|
+
ReportOnFile,
|
|
26
|
+
RuntimeFileOptions,
|
|
27
|
+
RuntimeOptions,
|
|
28
|
+
ASTDeps,
|
|
29
|
+
Warning,
|
|
30
|
+
WarningDefault,
|
|
31
|
+
WarningLocation,
|
|
32
|
+
WarningName,
|
|
33
|
+
WarningNameWithValue
|
|
34
|
+
}
|
package/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import isMinified from "is-minified-code";
|
|
|
9
9
|
|
|
10
10
|
// Import Internal Dependencies
|
|
11
11
|
import Analysis from "./src/Analysis.js";
|
|
12
|
+
import { warnings } from "./src/warnings.js";
|
|
12
13
|
|
|
13
14
|
export function runASTAnalysis(str, options = Object.create(null)) {
|
|
14
15
|
const { module = true, isMinified = false } = options;
|
|
@@ -83,59 +84,4 @@ export async function runASTAnalysisOnFile(pathToFile, options = {}) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
export
|
|
87
|
-
parsingError: {
|
|
88
|
-
code: "ast-error",
|
|
89
|
-
i18n: "sast_warnings.ast_error",
|
|
90
|
-
severity: "Information"
|
|
91
|
-
},
|
|
92
|
-
unsafeImport: {
|
|
93
|
-
code: "unsafe-import",
|
|
94
|
-
i18n: "sast_warnings.unsafe_import",
|
|
95
|
-
severity: "Warning"
|
|
96
|
-
},
|
|
97
|
-
unsafeRegex: {
|
|
98
|
-
code: "unsafe-regex",
|
|
99
|
-
i18n: "sast_warnings.unsafe_regex",
|
|
100
|
-
severity: "Warning"
|
|
101
|
-
},
|
|
102
|
-
unsafeStmt: {
|
|
103
|
-
code: "unsafe-stmt",
|
|
104
|
-
i18n: "sast_warnings.unsafe_stmt",
|
|
105
|
-
severity: "Warning"
|
|
106
|
-
},
|
|
107
|
-
unsafeAssign: {
|
|
108
|
-
code: "unsafe-assign",
|
|
109
|
-
i18n: "sast_warnings.unsafe_assign",
|
|
110
|
-
severity: "Warning"
|
|
111
|
-
},
|
|
112
|
-
encodedLiteral: {
|
|
113
|
-
code: "encoded-literal",
|
|
114
|
-
i18n: "sast_warnings.encoded_literal",
|
|
115
|
-
severity: "Information"
|
|
116
|
-
},
|
|
117
|
-
shortIdentifiers: {
|
|
118
|
-
code: "short-identifiers",
|
|
119
|
-
i18n: "sast_warnings.short_identifiers",
|
|
120
|
-
severity: "Warning"
|
|
121
|
-
},
|
|
122
|
-
suspiciousLiteral: {
|
|
123
|
-
code: "suspicious-literal",
|
|
124
|
-
i18n: "sast_warnings.suspicious_literal",
|
|
125
|
-
severity: "Warning"
|
|
126
|
-
},
|
|
127
|
-
obfuscatedCode: {
|
|
128
|
-
code: "obfuscated-code",
|
|
129
|
-
i18n: "sast_warnings.obfuscated_code",
|
|
130
|
-
severity: "Critical",
|
|
131
|
-
experimental: true
|
|
132
|
-
},
|
|
133
|
-
weakCrypto: {
|
|
134
|
-
code: "weak-crypto",
|
|
135
|
-
i18n: "sast_warnings.weak_crypto",
|
|
136
|
-
severity: "Information",
|
|
137
|
-
experimental: true
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
|
|
87
|
+
export { warnings };
|
package/package.json
CHANGED
package/src/Analysis.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { Utils, Literal } from "@nodesecure/sec-literal";
|
|
3
3
|
|
|
4
4
|
// Import Internal Dependencies
|
|
5
|
-
import { rootLocation, toArrayLocation
|
|
6
|
-
import {
|
|
5
|
+
import { rootLocation, toArrayLocation } from "./utils.js";
|
|
6
|
+
import { generateWarning } from "./warnings.js";
|
|
7
|
+
import { processMainModuleRequire } from "./constants.js";
|
|
7
8
|
import ASTDeps from "./ASTDeps.js";
|
|
8
9
|
import { isObfuscatedCode, hasTrojanSource } from "./obfuscators/index.js";
|
|
9
10
|
import { runOnProbes } from "./probes/index.js";
|
|
@@ -15,19 +16,6 @@ const kDictionaryStrParts = [
|
|
|
15
16
|
"0123456789"
|
|
16
17
|
];
|
|
17
18
|
|
|
18
|
-
const kWarningsNameStr = Object.freeze({
|
|
19
|
-
[_warnings.parsingError]: "parsing-error",
|
|
20
|
-
[_warnings.unsafeImport]: "unsafe-import",
|
|
21
|
-
[_warnings.unsafeRegex]: "unsafe-regex",
|
|
22
|
-
[_warnings.unsafeStmt]: "unsafe-stmt",
|
|
23
|
-
[_warnings.unsafeAssign]: "unsafe-assign",
|
|
24
|
-
[_warnings.encodedLiteral]: "encoded-literal",
|
|
25
|
-
[_warnings.shortIdentifiers]: "short-identifiers",
|
|
26
|
-
[_warnings.suspiciousLiteral]: "suspicious-literal",
|
|
27
|
-
[_warnings.obfuscatedCode]: "obfuscated-code",
|
|
28
|
-
[_warnings.weakCrypto]: "weak-crypto"
|
|
29
|
-
});
|
|
30
|
-
|
|
31
19
|
export default class Analysis {
|
|
32
20
|
hasDictionaryString = false;
|
|
33
21
|
hasPrefixedIdentifiers = false;
|
|
@@ -56,23 +44,24 @@ export default class Analysis {
|
|
|
56
44
|
this.literalScores = [];
|
|
57
45
|
}
|
|
58
46
|
|
|
59
|
-
addWarning(
|
|
60
|
-
|
|
47
|
+
addWarning(name, value, location = rootLocation()) {
|
|
48
|
+
const isEncodedLiteral = name === "encoded-literal";
|
|
49
|
+
if (isEncodedLiteral && this.handledEncodedLiteralValues.has(value)) {
|
|
61
50
|
const index = this.handledEncodedLiteralValues.get(value);
|
|
62
51
|
this.warnings[index].location.push(toArrayLocation(location));
|
|
63
52
|
|
|
64
53
|
return;
|
|
65
54
|
}
|
|
66
|
-
|
|
67
|
-
this.warnings.push(generateWarning(
|
|
68
|
-
if (
|
|
55
|
+
|
|
56
|
+
this.warnings.push(generateWarning(name, { value, location }));
|
|
57
|
+
if (isEncodedLiteral) {
|
|
69
58
|
this.handledEncodedLiteralValues.set(value, this.warnings.length - 1);
|
|
70
59
|
}
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
analyzeSourceString(sourceString) {
|
|
74
63
|
if (hasTrojanSource(sourceString)) {
|
|
75
|
-
this.addWarning(
|
|
64
|
+
this.addWarning("obfuscated-code", "trojan-source");
|
|
76
65
|
}
|
|
77
66
|
}
|
|
78
67
|
|
|
@@ -107,7 +96,7 @@ export default class Analysis {
|
|
|
107
96
|
this.counter.encodedArrayValue++;
|
|
108
97
|
}
|
|
109
98
|
else {
|
|
110
|
-
this.addWarning(
|
|
99
|
+
this.addWarning("encoded-literal", node.value, node.loc);
|
|
111
100
|
}
|
|
112
101
|
}
|
|
113
102
|
}
|
|
@@ -116,7 +105,7 @@ export default class Analysis {
|
|
|
116
105
|
this.counter.identifiers = this.identifiersName.length;
|
|
117
106
|
const [isObfuscated, kind] = isObfuscatedCode(this);
|
|
118
107
|
if (isObfuscated) {
|
|
119
|
-
this.addWarning(
|
|
108
|
+
this.addWarning("obfuscated-code", kind || "unknown");
|
|
120
109
|
}
|
|
121
110
|
|
|
122
111
|
const identifiersLengthArr = this.identifiersName
|
|
@@ -124,10 +113,10 @@ export default class Analysis {
|
|
|
124
113
|
|
|
125
114
|
const [idsLengthAvg, stringScore] = [sum(identifiersLengthArr), sum(this.literalScores)];
|
|
126
115
|
if (!isMinified && identifiersLengthArr.length > 5 && idsLengthAvg <= 1.5) {
|
|
127
|
-
this.addWarning(
|
|
116
|
+
this.addWarning("short-identifiers", idsLengthAvg);
|
|
128
117
|
}
|
|
129
118
|
if (stringScore >= 3) {
|
|
130
|
-
this.addWarning(
|
|
119
|
+
this.addWarning("suspicious-literal", stringScore);
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
return { idsLengthAvg, stringScore, warnings: this.warnings };
|
|
@@ -149,5 +138,3 @@ export default class Analysis {
|
|
|
149
138
|
function sum(arr = []) {
|
|
150
139
|
return arr.length === 0 ? 0 : (arr.reduce((prev, curr) => prev + curr, 0) / arr.length);
|
|
151
140
|
}
|
|
152
|
-
|
|
153
|
-
Analysis.Warnings = _warnings;
|
package/src/constants.js
CHANGED
|
@@ -33,16 +33,3 @@ export const unsafeUnicodeControlCharacters = [
|
|
|
33
33
|
"\u200F",
|
|
34
34
|
"\u061C"
|
|
35
35
|
];
|
|
36
|
-
|
|
37
|
-
export const warnings = Object.freeze({
|
|
38
|
-
parsingError: Symbol("ParsingError"),
|
|
39
|
-
unsafeImport: Symbol("UnsafeImport"),
|
|
40
|
-
unsafeRegex: Symbol("UnsafeRegex"),
|
|
41
|
-
unsafeStmt: Symbol("UnsafeStmt"),
|
|
42
|
-
unsafeAssign: Symbol("UnsafeAssign"),
|
|
43
|
-
encodedLiteral: Symbol("EncodedLiteral"),
|
|
44
|
-
shortIdentifiers: Symbol("ShortIdentifiers"),
|
|
45
|
-
suspiciousLiteral: Symbol("SuspiciousLiteral"),
|
|
46
|
-
obfuscatedCode: Symbol("ObfuscatedCode"),
|
|
47
|
-
weakCrypto: Symbol("WeakCrypto")
|
|
48
|
-
});
|
package/src/probes/index.js
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
// Import all the probes
|
|
2
|
-
import isUnsafeCallee from "./isUnsafeCallee.js";
|
|
3
|
-
import isLiteral from "./isLiteral.js";
|
|
4
|
-
import isLiteralRegex from "./isLiteralRegex.js";
|
|
5
|
-
import isRegexObject from "./isRegexObject.js";
|
|
6
|
-
import isVariableDeclaration from "./isVariableDeclaration.js";
|
|
7
|
-
import isAssignmentExprOrMemberExpr from "./isAssignmentExprOrMemberExpr.js";
|
|
8
|
-
import isRequire from "./isRequire.js";
|
|
9
|
-
import isImportDeclaration from "./isImportDeclaration.js";
|
|
10
|
-
import isMemberExpression from "./isMemberExpression.js";
|
|
11
|
-
import isArrayExpression from "./isArrayExpression.js";
|
|
12
|
-
import isFunctionDeclaration from "./isFunctionDeclaration.js";
|
|
13
|
-
import isAssignmentExpression from "./isAssignmentExpression.js";
|
|
14
|
-
import isObjectExpression from "./isObjectExpression.js";
|
|
15
|
-
import isUnaryExpression from "./isUnaryExpression.js";
|
|
16
|
-
import isWeakCrypto from "./isWeakCrypto.js";
|
|
17
|
-
|
|
18
|
-
// CONSTANTS
|
|
19
|
-
const kListOfProbes = [
|
|
20
|
-
isUnsafeCallee,
|
|
21
|
-
isLiteral,
|
|
22
|
-
isLiteralRegex,
|
|
23
|
-
isRegexObject,
|
|
24
|
-
isVariableDeclaration,
|
|
25
|
-
isAssignmentExprOrMemberExpr,
|
|
26
|
-
isRequire,
|
|
27
|
-
isImportDeclaration,
|
|
28
|
-
isMemberExpression,
|
|
29
|
-
isAssignmentExpression,
|
|
30
|
-
isObjectExpression,
|
|
31
|
-
isArrayExpression,
|
|
32
|
-
isFunctionDeclaration,
|
|
33
|
-
isUnaryExpression,
|
|
34
|
-
isWeakCrypto
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const kSymBreak = Symbol.for("breakWalk");
|
|
38
|
-
const kSymSkip = Symbol.for("skipWalk");
|
|
39
|
-
|
|
40
|
-
export function runOnProbes(node, analysis) {
|
|
41
|
-
const breakedGroups = new Set();
|
|
42
|
-
|
|
43
|
-
for (const probe of kListOfProbes) {
|
|
44
|
-
if (breakedGroups.has(probe.breakGroup)) {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const [isMatching, data = null] = probe.validateNode(node, analysis);
|
|
49
|
-
if (isMatching) {
|
|
50
|
-
const result = probe.main(node, { analysis, data });
|
|
51
|
-
|
|
52
|
-
if (result === kSymSkip) {
|
|
53
|
-
return "skip";
|
|
54
|
-
}
|
|
55
|
-
if (result === kSymBreak || probe.breakOnMatch) {
|
|
56
|
-
const breakGroup = probe.breakGroup || null;
|
|
57
|
-
if (breakGroup === null) {
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
breakedGroups.add(breakGroup);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
1
|
+
// Import all the probes
|
|
2
|
+
import isUnsafeCallee from "./isUnsafeCallee.js";
|
|
3
|
+
import isLiteral from "./isLiteral.js";
|
|
4
|
+
import isLiteralRegex from "./isLiteralRegex.js";
|
|
5
|
+
import isRegexObject from "./isRegexObject.js";
|
|
6
|
+
import isVariableDeclaration from "./isVariableDeclaration.js";
|
|
7
|
+
import isAssignmentExprOrMemberExpr from "./isAssignmentExprOrMemberExpr.js";
|
|
8
|
+
import isRequire from "./isRequire.js";
|
|
9
|
+
import isImportDeclaration from "./isImportDeclaration.js";
|
|
10
|
+
import isMemberExpression from "./isMemberExpression.js";
|
|
11
|
+
import isArrayExpression from "./isArrayExpression.js";
|
|
12
|
+
import isFunctionDeclaration from "./isFunctionDeclaration.js";
|
|
13
|
+
import isAssignmentExpression from "./isAssignmentExpression.js";
|
|
14
|
+
import isObjectExpression from "./isObjectExpression.js";
|
|
15
|
+
import isUnaryExpression from "./isUnaryExpression.js";
|
|
16
|
+
import isWeakCrypto from "./isWeakCrypto.js";
|
|
17
|
+
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
const kListOfProbes = [
|
|
20
|
+
isUnsafeCallee,
|
|
21
|
+
isLiteral,
|
|
22
|
+
isLiteralRegex,
|
|
23
|
+
isRegexObject,
|
|
24
|
+
isVariableDeclaration,
|
|
25
|
+
isAssignmentExprOrMemberExpr,
|
|
26
|
+
isRequire,
|
|
27
|
+
isImportDeclaration,
|
|
28
|
+
isMemberExpression,
|
|
29
|
+
isAssignmentExpression,
|
|
30
|
+
isObjectExpression,
|
|
31
|
+
isArrayExpression,
|
|
32
|
+
isFunctionDeclaration,
|
|
33
|
+
isUnaryExpression,
|
|
34
|
+
isWeakCrypto
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const kSymBreak = Symbol.for("breakWalk");
|
|
38
|
+
const kSymSkip = Symbol.for("skipWalk");
|
|
39
|
+
|
|
40
|
+
export function runOnProbes(node, analysis) {
|
|
41
|
+
const breakedGroups = new Set();
|
|
42
|
+
|
|
43
|
+
for (const probe of kListOfProbes) {
|
|
44
|
+
if (breakedGroups.has(probe.breakGroup)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const [isMatching, data = null] = probe.validateNode(node, analysis);
|
|
49
|
+
if (isMatching) {
|
|
50
|
+
const result = probe.main(node, { analysis, data });
|
|
51
|
+
|
|
52
|
+
if (result === kSymSkip) {
|
|
53
|
+
return "skip";
|
|
54
|
+
}
|
|
55
|
+
if (result === kSymBreak || probe.breakOnMatch) {
|
|
56
|
+
const breakGroup = probe.breakGroup || null;
|
|
57
|
+
if (breakGroup === null) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
breakedGroups.add(breakGroup);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Require Internal Dependencies
|
|
2
|
-
import { warnings } from "../constants.js";
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
2
|
* @description Search for ESM ImportDeclaration
|
|
6
3
|
* @see https://github.com/estree/estree/blob/master/es2015.md#importdeclaration
|
|
@@ -22,7 +19,7 @@ function main(node, options) {
|
|
|
22
19
|
// Searching for dangerous import "data:text/javascript;..." statement.
|
|
23
20
|
// see: https://2ality.com/2019/10/eval-via-import.html
|
|
24
21
|
if (node.source.value.startsWith("data:text/javascript")) {
|
|
25
|
-
analysis.addWarning(
|
|
22
|
+
analysis.addWarning("unsafe-import", node.source.value, node.loc);
|
|
26
23
|
}
|
|
27
24
|
analysis.dependencies.add(node.source.value, node.loc);
|
|
28
25
|
}
|
package/src/probes/isLiteral.js
CHANGED
|
@@ -5,7 +5,7 @@ import { builtinModules } from "repl";
|
|
|
5
5
|
import { Hex } from "@nodesecure/sec-literal";
|
|
6
6
|
|
|
7
7
|
// Import Internal Dependencies
|
|
8
|
-
import { globalParts
|
|
8
|
+
import { globalParts } from "../constants.js";
|
|
9
9
|
|
|
10
10
|
// CONSTANTS
|
|
11
11
|
const kNodeDeps = new Set(builtinModules);
|
|
@@ -34,10 +34,10 @@ function main(node, options) {
|
|
|
34
34
|
// then we add it to the dependencies list and we throw an unsafe-import at the current location.
|
|
35
35
|
if (kNodeDeps.has(value)) {
|
|
36
36
|
analysis.dependencies.add(value, node.loc);
|
|
37
|
-
analysis.addWarning(
|
|
37
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
38
38
|
}
|
|
39
39
|
else if (globalParts.has(value) || !Hex.isSafe(node.value)) {
|
|
40
|
-
analysis.addWarning(
|
|
40
|
+
analysis.addWarning("encoded-literal", node.value, node.loc);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
// Else we are checking all other string with our suspect method
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Require Internal Dependencies
|
|
2
2
|
import { isLiteralRegex } from "../utils.js";
|
|
3
|
-
import { warnings } from "../constants.js";
|
|
4
3
|
|
|
5
4
|
// Require Third-party Dependencies
|
|
6
5
|
import safeRegex from "safe-regex";
|
|
@@ -22,7 +21,7 @@ function main(node, options) {
|
|
|
22
21
|
|
|
23
22
|
// We use the safe-regex package to detect whether or not regex is safe!
|
|
24
23
|
if (!safeRegex(node.regex.pattern)) {
|
|
25
|
-
analysis.addWarning(
|
|
24
|
+
analysis.addWarning("unsafe-regex", node.regex.pattern, node.loc);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Import Internal Dependencies
|
|
2
2
|
import { isLiteralRegex } from "../utils.js";
|
|
3
|
-
import { warnings } from "../constants.js";
|
|
4
3
|
|
|
5
4
|
// Import Third-party Dependencies
|
|
6
5
|
import safeRegex from "safe-regex";
|
|
@@ -25,7 +24,7 @@ function main(node, options) {
|
|
|
25
24
|
|
|
26
25
|
// We use the safe-regex package to detect whether or not regex is safe!
|
|
27
26
|
if (!safeRegex(pattern)) {
|
|
28
|
-
analysis.addWarning(
|
|
27
|
+
analysis.addWarning("unsafe-regex", pattern, node.loc);
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
package/src/probes/isRequire.js
CHANGED
|
@@ -1,160 +1,159 @@
|
|
|
1
|
-
/* eslint-disable consistent-return */
|
|
2
|
-
|
|
3
|
-
// Import Internal Dependencies
|
|
4
|
-
import { isRequireGlobalMemberExpr, getMemberExprName, arrExprToString, concatBinaryExpr } from "../utils.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
1
|
+
/* eslint-disable consistent-return */
|
|
2
|
+
|
|
3
|
+
// Import Internal Dependencies
|
|
4
|
+
import { isRequireGlobalMemberExpr, getMemberExprName, arrExprToString, concatBinaryExpr } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
// Import Third-party Dependencies
|
|
7
|
+
import { Hex } from "@nodesecure/sec-literal";
|
|
8
|
+
import { walk } from "estree-walker";
|
|
9
|
+
|
|
10
|
+
function validateNode(node, analysis) {
|
|
11
|
+
return [
|
|
12
|
+
isRequireIdentifiers(node, analysis) ||
|
|
13
|
+
isRequireResolve(node) ||
|
|
14
|
+
isRequireMemberExpr(node)
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isRequireResolve(node) {
|
|
19
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return node.callee.object.name === "require" && node.callee.property.name === "resolve";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isRequireMemberExpr(node) {
|
|
27
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return isRequireGlobalMemberExpr(getMemberExprName(node.callee));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isRequireIdentifiers(node, analysis) {
|
|
35
|
+
if (node.type !== "CallExpression") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
|
|
39
|
+
|
|
40
|
+
return analysis.requireIdentifiers.has(fullName);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function main(node, options) {
|
|
44
|
+
const { analysis } = options;
|
|
45
|
+
|
|
46
|
+
const arg = node.arguments[0];
|
|
47
|
+
switch (arg.type) {
|
|
48
|
+
// const foo = "http"; require(foo);
|
|
49
|
+
case "Identifier":
|
|
50
|
+
if (analysis.identifiers.has(arg.name)) {
|
|
51
|
+
analysis.dependencies.add(analysis.identifiers.get(arg.name), node.loc);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
// require("http")
|
|
59
|
+
case "Literal":
|
|
60
|
+
analysis.dependencies.add(arg.value, node.loc);
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
// require(["ht" + "tp"])
|
|
64
|
+
case "ArrayExpression": {
|
|
65
|
+
const value = arrExprToString(arg.elements, analysis.identifiers).trim();
|
|
66
|
+
if (value === "") {
|
|
67
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
analysis.dependencies.add(value, node.loc);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// require("ht" + "tp");
|
|
76
|
+
case "BinaryExpression": {
|
|
77
|
+
if (arg.operator !== "+") {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const value = concatBinaryExpr(arg, analysis.identifiers);
|
|
82
|
+
if (value === null) {
|
|
83
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
analysis.dependencies.add(value, node.loc);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// require(Buffer.from("...", "hex").toString());
|
|
92
|
+
case "CallExpression": {
|
|
93
|
+
const { dependencies } = parseRequireCallExpression(arg);
|
|
94
|
+
dependencies.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
|
|
95
|
+
|
|
96
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
97
|
+
|
|
98
|
+
// We skip walking the tree to avoid anymore warnings...
|
|
99
|
+
return Symbol.for("skipWalk");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
analysis.addWarning("unsafe-import", null, node.loc);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseRequireCallExpression(nodeToWalk) {
|
|
108
|
+
const dependencies = new Set();
|
|
109
|
+
|
|
110
|
+
walk(nodeToWalk, {
|
|
111
|
+
enter(node) {
|
|
112
|
+
if (node.type !== "CallExpression" || node.arguments.length === 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (node.arguments[0].type === "Literal" && Hex.isHex(node.arguments[0].value)) {
|
|
117
|
+
dependencies.add(Buffer.from(node.arguments[0].value, "hex").toString());
|
|
118
|
+
|
|
119
|
+
return this.skip();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
|
|
123
|
+
switch (fullName) {
|
|
124
|
+
case "Buffer.from": {
|
|
125
|
+
const [element, convert] = node.arguments;
|
|
126
|
+
|
|
127
|
+
if (element.type === "ArrayExpression") {
|
|
128
|
+
const depName = arrExprToString(element);
|
|
129
|
+
if (depName.trim() !== "") {
|
|
130
|
+
dependencies.add(depName);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (element.type === "Literal" && convert.type === "Literal" && convert.value === "hex") {
|
|
134
|
+
const value = Buffer.from(element.value, "hex").toString();
|
|
135
|
+
dependencies.add(value);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "require.resolve": {
|
|
140
|
+
const [element] = node.arguments;
|
|
141
|
+
|
|
142
|
+
if (element.type === "Literal") {
|
|
143
|
+
dependencies.add(element.value);
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
dependencies: [...dependencies]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default {
|
|
157
|
+
name: "isRequire",
|
|
158
|
+
validateNode, main, breakOnMatch: true, breakGroup: "import"
|
|
159
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Require Internal Dependencies
|
|
2
2
|
import { isUnsafeCallee } from "../utils.js";
|
|
3
|
-
import { warnings } from "../constants.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* @description Detect unsafe statement
|
|
@@ -15,7 +14,7 @@ function validateNode(node) {
|
|
|
15
14
|
function main(node, options) {
|
|
16
15
|
const { analysis, data: calleeName } = options;
|
|
17
16
|
|
|
18
|
-
analysis.addWarning(
|
|
17
|
+
analysis.addWarning("unsafe-stmt", calleeName, node.loc);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export default {
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
// Require Internal Dependencies
|
|
2
|
-
import { getIdName, getMemberExprName, isUnsafeCallee, isRequireGlobalMemberExpr } from "../utils.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
// CONSTANTS
|
|
6
|
-
const kUnsafeCallee = new Set(["eval", "Function"]);
|
|
7
|
-
|
|
8
|
-
// In case we are matching a Variable declaration, we have to save the identifier
|
|
9
|
-
// This allow the AST Analysis to retrieve required dependency when the stmt is mixed with variables.
|
|
10
|
-
function validateNode(node) {
|
|
11
|
-
return [
|
|
12
|
-
node.type === "VariableDeclaration"
|
|
13
|
-
];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function main(mainNode, options) {
|
|
17
|
-
const { analysis } = options;
|
|
18
|
-
|
|
19
|
-
analysis.varkinds[mainNode.kind]++;
|
|
20
|
-
|
|
21
|
-
for (const node of mainNode.declarations) {
|
|
22
|
-
analysis.idtypes.variableDeclarator++;
|
|
23
|
-
for (const name of getIdName(node.id)) {
|
|
24
|
-
analysis.identifiersName.push({ name, type: "variableDeclarator" });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (node.init === null || node.id.type !== "Identifier") {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (node.init.type === "Literal") {
|
|
32
|
-
analysis.identifiers.set(node.id.name, String(node.init.value));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Searching for someone who assign require to a variable, ex:
|
|
36
|
-
// const r = require
|
|
37
|
-
else if (node.init.type === "Identifier") {
|
|
38
|
-
if (kUnsafeCallee.has(node.init.name)) {
|
|
39
|
-
analysis.addWarning(
|
|
40
|
-
}
|
|
41
|
-
else if (analysis.requireIdentifiers.has(node.init.name)) {
|
|
42
|
-
analysis.requireIdentifiers.add(node.id.name);
|
|
43
|
-
analysis.addWarning(
|
|
44
|
-
}
|
|
45
|
-
else if (globalParts.has(node.init.name)) {
|
|
46
|
-
analysis.globalParts.set(node.id.name, node.init.name);
|
|
47
|
-
getRequirablePatterns(analysis.globalParts)
|
|
48
|
-
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Same as before but for pattern like process.mainModule and require.resolve
|
|
53
|
-
else if (node.init.type === "MemberExpression") {
|
|
54
|
-
const value = getMemberExprName(node.init);
|
|
55
|
-
const members = value.split(".");
|
|
56
|
-
|
|
57
|
-
if (analysis.globalParts.has(members[0]) || members.every((part) => globalParts.has(part))) {
|
|
58
|
-
analysis.globalParts.set(node.id.name, members.slice(1).join("."));
|
|
59
|
-
analysis.addWarning(
|
|
60
|
-
}
|
|
61
|
-
getRequirablePatterns(analysis.globalParts)
|
|
62
|
-
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
63
|
-
|
|
64
|
-
if (isRequireStatement(value)) {
|
|
65
|
-
analysis.requireIdentifiers.add(node.id.name);
|
|
66
|
-
analysis.addWarning(
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else if (isUnsafeCallee(node.init)[0]) {
|
|
70
|
-
analysis.globalParts.set(node.id.name, "global");
|
|
71
|
-
globalParts.add(node.id.name);
|
|
72
|
-
analysis.requireIdentifiers.add(`${node.id.name}.${processMainModuleRequire}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isRequireStatement(value) {
|
|
78
|
-
return value.startsWith("require") ||
|
|
79
|
-
value.startsWith(processMainModuleRequire) ||
|
|
80
|
-
isRequireGlobalMemberExpr(value);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function getRequirablePatterns(parts) {
|
|
84
|
-
const result = new Set();
|
|
85
|
-
|
|
86
|
-
for (const [id, path] of parts.entries()) {
|
|
87
|
-
if (path === "process") {
|
|
88
|
-
result.add(`${id}.mainModule.require`);
|
|
89
|
-
}
|
|
90
|
-
else if (path === "mainModule") {
|
|
91
|
-
result.add(`${id}.require`);
|
|
92
|
-
}
|
|
93
|
-
else if (path.includes("require")) {
|
|
94
|
-
result.add(id);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [...result];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export default {
|
|
102
|
-
name: "isVariableDeclaration",
|
|
103
|
-
validateNode, main, breakOnMatch: false
|
|
104
|
-
};
|
|
1
|
+
// Require Internal Dependencies
|
|
2
|
+
import { getIdName, getMemberExprName, isUnsafeCallee, isRequireGlobalMemberExpr } from "../utils.js";
|
|
3
|
+
import { globalParts, processMainModuleRequire } from "../constants.js";
|
|
4
|
+
|
|
5
|
+
// CONSTANTS
|
|
6
|
+
const kUnsafeCallee = new Set(["eval", "Function"]);
|
|
7
|
+
|
|
8
|
+
// In case we are matching a Variable declaration, we have to save the identifier
|
|
9
|
+
// This allow the AST Analysis to retrieve required dependency when the stmt is mixed with variables.
|
|
10
|
+
function validateNode(node) {
|
|
11
|
+
return [
|
|
12
|
+
node.type === "VariableDeclaration"
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function main(mainNode, options) {
|
|
17
|
+
const { analysis } = options;
|
|
18
|
+
|
|
19
|
+
analysis.varkinds[mainNode.kind]++;
|
|
20
|
+
|
|
21
|
+
for (const node of mainNode.declarations) {
|
|
22
|
+
analysis.idtypes.variableDeclarator++;
|
|
23
|
+
for (const name of getIdName(node.id)) {
|
|
24
|
+
analysis.identifiersName.push({ name, type: "variableDeclarator" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (node.init === null || node.id.type !== "Identifier") {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (node.init.type === "Literal") {
|
|
32
|
+
analysis.identifiers.set(node.id.name, String(node.init.value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Searching for someone who assign require to a variable, ex:
|
|
36
|
+
// const r = require
|
|
37
|
+
else if (node.init.type === "Identifier") {
|
|
38
|
+
if (kUnsafeCallee.has(node.init.name)) {
|
|
39
|
+
analysis.addWarning("unsafe-assign", node.init.name, node.loc);
|
|
40
|
+
}
|
|
41
|
+
else if (analysis.requireIdentifiers.has(node.init.name)) {
|
|
42
|
+
analysis.requireIdentifiers.add(node.id.name);
|
|
43
|
+
analysis.addWarning("unsafe-assign", node.init.name, node.loc);
|
|
44
|
+
}
|
|
45
|
+
else if (globalParts.has(node.init.name)) {
|
|
46
|
+
analysis.globalParts.set(node.id.name, node.init.name);
|
|
47
|
+
getRequirablePatterns(analysis.globalParts)
|
|
48
|
+
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Same as before but for pattern like process.mainModule and require.resolve
|
|
53
|
+
else if (node.init.type === "MemberExpression") {
|
|
54
|
+
const value = getMemberExprName(node.init);
|
|
55
|
+
const members = value.split(".");
|
|
56
|
+
|
|
57
|
+
if (analysis.globalParts.has(members[0]) || members.every((part) => globalParts.has(part))) {
|
|
58
|
+
analysis.globalParts.set(node.id.name, members.slice(1).join("."));
|
|
59
|
+
analysis.addWarning("unsafe-assign", value, node.loc);
|
|
60
|
+
}
|
|
61
|
+
getRequirablePatterns(analysis.globalParts)
|
|
62
|
+
.forEach((name) => analysis.requireIdentifiers.add(name));
|
|
63
|
+
|
|
64
|
+
if (isRequireStatement(value)) {
|
|
65
|
+
analysis.requireIdentifiers.add(node.id.name);
|
|
66
|
+
analysis.addWarning("unsafe-assign", value, node.loc);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (isUnsafeCallee(node.init)[0]) {
|
|
70
|
+
analysis.globalParts.set(node.id.name, "global");
|
|
71
|
+
globalParts.add(node.id.name);
|
|
72
|
+
analysis.requireIdentifiers.add(`${node.id.name}.${processMainModuleRequire}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isRequireStatement(value) {
|
|
78
|
+
return value.startsWith("require") ||
|
|
79
|
+
value.startsWith(processMainModuleRequire) ||
|
|
80
|
+
isRequireGlobalMemberExpr(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getRequirablePatterns(parts) {
|
|
84
|
+
const result = new Set();
|
|
85
|
+
|
|
86
|
+
for (const [id, path] of parts.entries()) {
|
|
87
|
+
if (path === "process") {
|
|
88
|
+
result.add(`${id}.mainModule.require`);
|
|
89
|
+
}
|
|
90
|
+
else if (path === "mainModule") {
|
|
91
|
+
result.add(`${id}.require`);
|
|
92
|
+
}
|
|
93
|
+
else if (path.includes("require")) {
|
|
94
|
+
result.add(id);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [...result];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default {
|
|
102
|
+
name: "isVariableDeclaration",
|
|
103
|
+
validateNode, main, breakOnMatch: false
|
|
104
|
+
};
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// Constants
|
|
5
|
-
const weakAlgorithms = new Set(["md5", "sha1", "ripemd160", "md4", "md2"]);
|
|
1
|
+
// CONSTANTS
|
|
2
|
+
const kWeakAlgorithms = new Set(["md5", "sha1", "ripemd160", "md4", "md2"]);
|
|
6
3
|
|
|
7
4
|
function validateNode(node) {
|
|
8
5
|
const isCallExpression = node.type === "CallExpression";
|
|
@@ -21,10 +18,10 @@ function main(node, { analysis }) {
|
|
|
21
18
|
const isCryptoImported = analysis.dependencies.has("crypto");
|
|
22
19
|
|
|
23
20
|
if (
|
|
24
|
-
|
|
21
|
+
kWeakAlgorithms.has(arg.value) &&
|
|
25
22
|
isCryptoImported
|
|
26
23
|
) {
|
|
27
|
-
analysis.addWarning(
|
|
24
|
+
analysis.addWarning("weak-crypto", arg.value, node.loc);
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
|
package/src/utils.js
CHANGED
|
@@ -164,25 +164,3 @@ export function toArrayLocation(location = rootLocation()) {
|
|
|
164
164
|
|
|
165
165
|
return [[start.line || 0, start.column || 0], [end.line || 0, end.column || 0]];
|
|
166
166
|
}
|
|
167
|
-
|
|
168
|
-
export function generateWarning(kind, options) {
|
|
169
|
-
const { location, file = null, value = null } = options;
|
|
170
|
-
|
|
171
|
-
if (kind === "encoded-literal") {
|
|
172
|
-
return { kind, value, location: [toArrayLocation(location)] };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const result = { kind, location: toArrayLocation(location) };
|
|
176
|
-
if (notNullOrUndefined(file)) {
|
|
177
|
-
result.file = file;
|
|
178
|
-
}
|
|
179
|
-
if (notNullOrUndefined(value)) {
|
|
180
|
-
result.value = value;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (kExperimentalWarnings.has(kind)) {
|
|
184
|
-
result.experimental = true;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return result;
|
|
188
|
-
}
|
package/src/warnings.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Import Internal Dependencies
|
|
2
|
+
import * as utils from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export const warnings = Object.freeze({
|
|
5
|
+
"ast-error": {
|
|
6
|
+
i18n: "sast_warnings.ast_error",
|
|
7
|
+
severity: "Information"
|
|
8
|
+
},
|
|
9
|
+
"unsafe-import": {
|
|
10
|
+
i18n: "sast_warnings.unsafe_import",
|
|
11
|
+
severity: "Warning"
|
|
12
|
+
},
|
|
13
|
+
"unsafe-regex": {
|
|
14
|
+
i18n: "sast_warnings.unsafe_regex",
|
|
15
|
+
severity: "Warning"
|
|
16
|
+
},
|
|
17
|
+
"unsafe-stmt": {
|
|
18
|
+
code: "unsafe-stmt",
|
|
19
|
+
i18n: "sast_warnings.unsafe_stmt",
|
|
20
|
+
severity: "Warning"
|
|
21
|
+
},
|
|
22
|
+
"unsafe-assign": {
|
|
23
|
+
i18n: "sast_warnings.unsafe_assign",
|
|
24
|
+
severity: "Warning"
|
|
25
|
+
},
|
|
26
|
+
"encoded-literal": {
|
|
27
|
+
i18n: "sast_warnings.encoded_literal",
|
|
28
|
+
severity: "Information"
|
|
29
|
+
},
|
|
30
|
+
"short-identifiers": {
|
|
31
|
+
i18n: "sast_warnings.short_identifiers",
|
|
32
|
+
severity: "Warning"
|
|
33
|
+
},
|
|
34
|
+
"suspicious-literal": {
|
|
35
|
+
i18n: "sast_warnings.suspicious_literal",
|
|
36
|
+
severity: "Warning"
|
|
37
|
+
},
|
|
38
|
+
"obfuscated-code": {
|
|
39
|
+
i18n: "sast_warnings.obfuscated_code",
|
|
40
|
+
severity: "Critical",
|
|
41
|
+
experimental: true
|
|
42
|
+
},
|
|
43
|
+
"weak-crypto": {
|
|
44
|
+
i18n: "sast_warnings.weak_crypto",
|
|
45
|
+
severity: "Information",
|
|
46
|
+
experimental: true
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export function generateWarning(kind, options) {
|
|
51
|
+
const { location, file = null, value = null } = options;
|
|
52
|
+
|
|
53
|
+
if (kind === "encoded-literal") {
|
|
54
|
+
return Object.assign(
|
|
55
|
+
{ kind, value, location: [utils.toArrayLocation(location)] },
|
|
56
|
+
warnings[kind]
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = { kind, location: utils.toArrayLocation(location) };
|
|
61
|
+
if (utils.notNullOrUndefined(file)) {
|
|
62
|
+
result.file = file;
|
|
63
|
+
}
|
|
64
|
+
if (utils.notNullOrUndefined(value)) {
|
|
65
|
+
result.value = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return Object.assign(result, warnings[kind]);
|
|
69
|
+
}
|
|
70
|
+
|