@nodesecure/js-x-ray 4.3.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.
@@ -1,160 +1,159 @@
1
- /* eslint-disable consistent-return */
2
-
3
- // Import Internal Dependencies
4
- import { isRequireGlobalMemberExpr, getMemberExprName, arrExprToString, concatBinaryExpr } from "../utils.js";
5
- import { warnings } from "../constants.js";
6
-
7
- // Import Third-party Dependencies
8
- import { Hex } from "@nodesecure/sec-literal";
9
- import { walk } from "estree-walker";
10
-
11
- function validateNode(node, analysis) {
12
- return [
13
- isRequireIdentifiers(node, analysis) ||
14
- isRequireResolve(node) ||
15
- isRequireMemberExpr(node)
16
- ];
17
- }
18
-
19
- function isRequireResolve(node) {
20
- if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
21
- return false;
22
- }
23
-
24
- return node.callee.object.name === "require" && node.callee.property.name === "resolve";
25
- }
26
-
27
- function isRequireMemberExpr(node) {
28
- if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
29
- return false;
30
- }
31
-
32
- return isRequireGlobalMemberExpr(getMemberExprName(node.callee));
33
- }
34
-
35
- function isRequireIdentifiers(node, analysis) {
36
- if (node.type !== "CallExpression") {
37
- return false;
38
- }
39
- const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
40
-
41
- return analysis.requireIdentifiers.has(fullName);
42
- }
43
-
44
- function main(node, options) {
45
- const { analysis } = options;
46
-
47
- const arg = node.arguments[0];
48
- switch (arg.type) {
49
- // const foo = "http"; require(foo);
50
- case "Identifier":
51
- if (analysis.identifiers.has(arg.name)) {
52
- analysis.dependencies.add(analysis.identifiers.get(arg.name), node.loc);
53
- }
54
- else {
55
- analysis.addWarning(warnings.unsafeImport, null, node.loc);
56
- }
57
- break;
58
-
59
- // require("http")
60
- case "Literal":
61
- analysis.dependencies.add(arg.value, node.loc);
62
- break;
63
-
64
- // require(["ht" + "tp"])
65
- case "ArrayExpression": {
66
- const value = arrExprToString(arg.elements, analysis.identifiers).trim();
67
- if (value === "") {
68
- analysis.addWarning(warnings.unsafeImport, null, node.loc);
69
- }
70
- else {
71
- analysis.dependencies.add(value, node.loc);
72
- }
73
- break;
74
- }
75
-
76
- // require("ht" + "tp");
77
- case "BinaryExpression": {
78
- if (arg.operator !== "+") {
79
- break;
80
- }
81
-
82
- const value = concatBinaryExpr(arg, analysis.identifiers);
83
- if (value === null) {
84
- analysis.addWarning(warnings.unsafeImport, null, node.loc);
85
- }
86
- else {
87
- analysis.dependencies.add(value, node.loc);
88
- }
89
- break;
90
- }
91
-
92
- // require(Buffer.from("...", "hex").toString());
93
- case "CallExpression": {
94
- const { dependencies } = parseRequireCallExpression(arg);
95
- dependencies.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
96
-
97
- analysis.addWarning(warnings.unsafeImport, null, node.loc);
98
-
99
- // We skip walking the tree to avoid anymore warnings...
100
- return Symbol.for("skipWalk");
101
- }
102
-
103
- default:
104
- analysis.addWarning(warnings.unsafeImport, null, node.loc);
105
- }
106
- }
107
-
108
- function parseRequireCallExpression(nodeToWalk) {
109
- const dependencies = new Set();
110
-
111
- walk(nodeToWalk, {
112
- enter(node) {
113
- if (node.type !== "CallExpression" || node.arguments.length === 0) {
114
- return;
115
- }
116
-
117
- if (node.arguments[0].type === "Literal" && Hex.isHex(node.arguments[0].value)) {
118
- dependencies.add(Buffer.from(node.arguments[0].value, "hex").toString());
119
-
120
- return this.skip();
121
- }
122
-
123
- const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
124
- switch (fullName) {
125
- case "Buffer.from": {
126
- const [element, convert] = node.arguments;
127
-
128
- if (element.type === "ArrayExpression") {
129
- const depName = arrExprToString(element);
130
- if (depName.trim() !== "") {
131
- dependencies.add(depName);
132
- }
133
- }
134
- else if (element.type === "Literal" && convert.type === "Literal" && convert.value === "hex") {
135
- const value = Buffer.from(element.value, "hex").toString();
136
- dependencies.add(value);
137
- }
138
- break;
139
- }
140
- case "require.resolve": {
141
- const [element] = node.arguments;
142
-
143
- if (element.type === "Literal") {
144
- dependencies.add(element.value);
145
- }
146
- break;
147
- }
148
- }
149
- }
150
- });
151
-
152
- return {
153
- dependencies: [...dependencies]
154
- };
155
- }
156
-
157
- export default {
158
- name: "isRequire",
159
- validateNode, main, breakOnMatch: true, breakGroup: "import"
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,18 +1,26 @@
1
- function validateNode(node) {
2
- return [
3
- node.type === "UnaryExpression"
4
- ];
5
- }
6
-
7
- function main(node, options) {
8
- const { analysis } = options;
9
-
10
- if (node.argument.type === "UnaryExpression" && node.argument.argument.type === "ArrayExpression") {
11
- analysis.counter.doubleUnaryArray++;
12
- }
13
- }
14
-
15
- export default {
16
- name: "isUnaryExpression",
17
- validateNode, main, breakOnMatch: false
18
- };
1
+ /**
2
+ * @description Search for UnaryExpression AST Node
3
+ * @see https://github.com/estree/estree/blob/master/es5.md#unaryexpression
4
+ * @example
5
+ * -2
6
+ */
7
+ function validateNode(node) {
8
+ return [
9
+ node.type === "UnaryExpression"
10
+ ];
11
+ }
12
+
13
+ function main(node, options) {
14
+ const { analysis } = options;
15
+
16
+ // Example: !![]
17
+ // See: https://docs.google.com/document/d/11ZrfW0bDQ-kd7Gr_Ixqyk8p3TGvxckmhFH3Z8dFoPhY/edit#
18
+ if (node.argument.type === "UnaryExpression" && node.argument.argument.type === "ArrayExpression") {
19
+ analysis.counter.doubleUnaryArray++;
20
+ }
21
+ }
22
+
23
+ export default {
24
+ name: "isUnaryExpression",
25
+ validateNode, main, breakOnMatch: false
26
+ };
@@ -1,19 +1,23 @@
1
- // Require Internal Dependencies
2
- import { isUnsafeCallee } from "../utils.js";
3
- import { warnings } from "../constants.js";
4
-
5
- // Detect unsafe statement like eval("this") or Function("return this")();
6
- function validateNode(node) {
7
- return isUnsafeCallee(node);
8
- }
9
-
10
- function main(node, options) {
11
- const { analysis, data: calleeName } = options;
12
-
13
- analysis.addWarning(warnings.unsafeStmt, calleeName, node.loc);
14
- }
15
-
16
- export default {
17
- name: "isUnsafeCallee",
18
- validateNode, main, breakOnMatch: false
19
- };
1
+ // Require Internal Dependencies
2
+ import { isUnsafeCallee } from "../utils.js";
3
+
4
+ /**
5
+ * @description Detect unsafe statement
6
+ * @example
7
+ * eval("this");
8
+ * Function("return this")();
9
+ */
10
+ function validateNode(node) {
11
+ return isUnsafeCallee(node);
12
+ }
13
+
14
+ function main(node, options) {
15
+ const { analysis, data: calleeName } = options;
16
+
17
+ analysis.addWarning("unsafe-stmt", calleeName, node.loc);
18
+ }
19
+
20
+ export default {
21
+ name: "isUnsafeCallee",
22
+ validateNode, main, breakOnMatch: false
23
+ };
@@ -1,104 +1,104 @@
1
- // Require Internal Dependencies
2
- import { getIdName, getMemberExprName, isUnsafeCallee, isRequireGlobalMemberExpr } from "../utils.js";
3
- import { warnings, 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(warnings.unsafeAssign, 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(warnings.unsafeAssign, 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(warnings.unsafeAssign, 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(warnings.unsafeAssign, 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
+ // 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
+ };
@@ -0,0 +1,31 @@
1
+ // CONSTANTS
2
+ const kWeakAlgorithms = new Set(["md5", "sha1", "ripemd160", "md4", "md2"]);
3
+
4
+ function validateNode(node) {
5
+ const isCallExpression = node.type === "CallExpression";
6
+ const isSimpleIdentifier = isCallExpression &&
7
+ node.callee.type === "Identifier" &&
8
+ node.callee.name === "createHash";
9
+ const isMemberExpression = isCallExpression &&
10
+ node.callee.type === "MemberExpression" &&
11
+ node.callee.property.name === "createHash";
12
+
13
+ return [isSimpleIdentifier || isMemberExpression];
14
+ }
15
+
16
+ function main(node, { analysis }) {
17
+ const arg = node.arguments.at(0);
18
+ const isCryptoImported = analysis.dependencies.has("crypto");
19
+
20
+ if (
21
+ kWeakAlgorithms.has(arg.value) &&
22
+ isCryptoImported
23
+ ) {
24
+ analysis.addWarning("weak-crypto", arg.value, node.loc);
25
+ }
26
+ }
27
+
28
+ export default {
29
+ name: "isWeakCrypto",
30
+ validateNode, main, breakOnMatch: false
31
+ };