@nodesecure/js-x-ray 5.1.0 → 6.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,68 +1,70 @@
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 isRequire from "./isRequire.js";
8
+ import isImportDeclaration from "./isImportDeclaration.js";
9
+ import isMemberExpression from "./isMemberExpression.js";
10
+ import isArrayExpression from "./isArrayExpression.js";
11
+ import isFunction from "./isFunction.js";
12
+ import isAssignmentExpression from "./isAssignmentExpression.js";
13
+ import isObjectExpression from "./isObjectExpression.js";
14
+ import isUnaryExpression from "./isUnaryExpression.js";
15
+ import isWeakCrypto from "./isWeakCrypto.js";
16
+ import isClassDeclaration from "./isClassDeclaration.js";
17
+ import isMethodDefinition from "./isMethodDefinition.js";
18
+
19
+ // CONSTANTS
20
+ const kListOfProbes = [
21
+ isUnsafeCallee,
22
+ isLiteral,
23
+ isLiteralRegex,
24
+ isRegexObject,
25
+ isVariableDeclaration,
26
+ isRequire,
27
+ isImportDeclaration,
28
+ isMemberExpression,
29
+ isAssignmentExpression,
30
+ isObjectExpression,
31
+ isArrayExpression,
32
+ isFunction,
33
+ isUnaryExpression,
34
+ isWeakCrypto,
35
+ isClassDeclaration,
36
+ isMethodDefinition
37
+ ];
38
+
39
+ const kSymBreak = Symbol.for("breakWalk");
40
+ const kSymSkip = Symbol.for("skipWalk");
41
+
42
+ export function runOnProbes(node, analysis) {
43
+ const breakedGroups = new Set();
44
+
45
+ for (const probe of kListOfProbes) {
46
+ if (breakedGroups.has(probe.breakGroup)) {
47
+ continue;
48
+ }
49
+
50
+ const [isMatching, data = null] = probe.validateNode(node, analysis);
51
+ if (isMatching) {
52
+ const result = probe.main(node, { analysis, data });
53
+
54
+ if (result === kSymSkip) {
55
+ return "skip";
56
+ }
57
+ if (result === kSymBreak || probe.breakOnMatch) {
58
+ const breakGroup = probe.breakGroup || null;
59
+ if (breakGroup === null) {
60
+ break;
61
+ }
62
+ else {
63
+ breakedGroups.add(breakGroup);
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ return null;
70
+ }
@@ -1,3 +1,9 @@
1
+ // Import Internal Dependencies
2
+ import { extractNode } from "../utils.js";
3
+
4
+ // CONSTANTS
5
+ const kLiteralExtractor = extractNode("Literal");
6
+
1
7
  /**
2
8
  * @description Search for ArrayExpression AST Node (Commonly known as JS Arrays)
3
9
  *
@@ -11,14 +17,11 @@ function validateNode(node) {
11
17
  ];
12
18
  }
13
19
 
14
- function main(node, options) {
15
- const { analysis } = options;
16
-
17
- for (const elem of node.elements) {
18
- if (elem !== null && elem.type === "Literal") {
19
- analysis.analyzeLiteral(elem, true);
20
- }
21
- }
20
+ function main(node, { analysis }) {
21
+ kLiteralExtractor(
22
+ (literalNode) => analysis.analyzeLiteral(literalNode, true),
23
+ node.elements
24
+ );
22
25
  }
23
26
 
24
27
  export default {
@@ -1,5 +1,5 @@
1
- // Import Internal Dependencies
2
- import { getIdName } from "../utils.js";
1
+ // Import Third-party Dependencies
2
+ import { getVariableDeclarationIdentifiers } from "@nodesecure/estree-ast-utils";
3
3
 
4
4
  /**
5
5
  * @description Search for AssignmentExpression (Not to be confused with AssignmentPattern).
@@ -18,7 +18,7 @@ function main(node, options) {
18
18
  const { analysis } = options;
19
19
 
20
20
  analysis.idtypes.assignExpr++;
21
- for (const name of getIdName(node.left)) {
21
+ for (const { name } of getVariableDeclarationIdentifiers(node.left)) {
22
22
  analysis.identifiersName.push({ name, type: "assignExpr" });
23
23
  }
24
24
  }
@@ -0,0 +1,25 @@
1
+ // Import Internal Dependencies
2
+ import { extractNode } from "../utils.js";
3
+
4
+ // CONSTANTS
5
+ const kIdExtractor = extractNode("Identifier");
6
+
7
+ function validateNode(node) {
8
+ return [
9
+ node.type === "ClassDeclaration"
10
+ ];
11
+ }
12
+
13
+ function main(node, options) {
14
+ const { analysis } = options;
15
+
16
+ kIdExtractor(
17
+ ({ name }) => analysis.identifiersName.push({ name, type: "class" }),
18
+ [node.id, node.superClass]
19
+ );
20
+ }
21
+
22
+ export default {
23
+ name: "isClassDeclaration",
24
+ validateNode, main, breakOnMatch: false
25
+ };
@@ -1,3 +1,9 @@
1
+ // Import Internal Dependencies
2
+ import { extractNode } from "../utils.js";
3
+
4
+ // CONSTANTS
5
+ const kIdExtractor = extractNode("Identifier");
6
+
1
7
  /**
2
8
  * @description Search for FunctionDeclaration AST Node.
3
9
  *
@@ -7,13 +13,18 @@
7
13
  */
8
14
  function validateNode(node) {
9
15
  return [
10
- node.type === "FunctionDeclaration"
16
+ node.type === "FunctionDeclaration" || node.type === "FunctionExpression"
11
17
  ];
12
18
  }
13
19
 
14
20
  function main(node, options) {
15
21
  const { analysis } = options;
16
22
 
23
+ kIdExtractor(
24
+ ({ name }) => analysis.identifiersName.push({ name, type: "params" }),
25
+ node.params
26
+ );
27
+
17
28
  if (node.id === null || node.id.type !== "Identifier") {
18
29
  return;
19
30
  }
@@ -1,52 +1,49 @@
1
- // Import Node.js Dependencies
2
- import { builtinModules } from "repl";
3
-
4
- // Import Third-party Dependencies
5
- import { Hex } from "@nodesecure/sec-literal";
6
-
7
- // Import Internal Dependencies
8
- import { globalParts } from "../constants.js";
9
-
10
- // CONSTANTS
11
- const kNodeDeps = new Set(builtinModules);
12
-
13
- /**
14
- * @description Search for Literal AST Node
15
- * @see https://github.com/estree/estree/blob/master/es5.md#literal
16
- * @example
17
- * "foobar"
18
- */
19
- function validateNode(node) {
20
- return [
21
- node.type === "Literal" && typeof node.value === "string"
22
- ];
23
- }
24
-
25
- function main(node, options) {
26
- const { analysis } = options;
27
-
28
- // We are searching for value obfuscated as hex of a minimum length of 4.
29
- if (/^[0-9A-Fa-f]{4,}$/g.test(node.value)) {
30
- const value = Buffer.from(node.value, "hex").toString();
31
- analysis.analyzeString(value);
32
-
33
- // If the value we are retrieving is the name of a Node.js dependency,
34
- // then we add it to the dependencies list and we throw an unsafe-import at the current location.
35
- if (kNodeDeps.has(value)) {
36
- analysis.dependencies.add(value, node.loc);
37
- analysis.addWarning("unsafe-import", null, node.loc);
38
- }
39
- else if (globalParts.has(value) || !Hex.isSafe(node.value)) {
40
- analysis.addWarning("encoded-literal", node.value, node.loc);
41
- }
42
- }
43
- // Else we are checking all other string with our suspect method
44
- else {
45
- analysis.analyzeLiteral(node);
46
- }
47
- }
48
-
49
- export default {
50
- name: "isLiteral",
51
- validateNode, main, breakOnMatch: false
52
- };
1
+ // Import Node.js Dependencies
2
+ import { builtinModules } from "repl";
3
+
4
+ // Import Third-party Dependencies
5
+ import { Hex } from "@nodesecure/sec-literal";
6
+
7
+ // CONSTANTS
8
+ const kNodeDeps = new Set(builtinModules);
9
+
10
+ /**
11
+ * @description Search for Literal AST Node
12
+ * @see https://github.com/estree/estree/blob/master/es5.md#literal
13
+ * @example
14
+ * "foobar"
15
+ */
16
+ function validateNode(node) {
17
+ return [
18
+ node.type === "Literal" && typeof node.value === "string"
19
+ ];
20
+ }
21
+
22
+ function main(node, options) {
23
+ const { analysis } = options;
24
+
25
+ // We are searching for value obfuscated as hex of a minimum length of 4.
26
+ if (/^[0-9A-Fa-f]{4,}$/g.test(node.value)) {
27
+ const value = Buffer.from(node.value, "hex").toString();
28
+ analysis.analyzeString(value);
29
+
30
+ // If the value we are retrieving is the name of a Node.js dependency,
31
+ // then we add it to the dependencies list and we throw an unsafe-import at the current location.
32
+ if (kNodeDeps.has(value)) {
33
+ analysis.dependencies.add(value, node.loc);
34
+ analysis.addWarning("unsafe-import", null, node.loc);
35
+ }
36
+ else if (value === "require" || !Hex.isSafe(node.value)) {
37
+ analysis.addWarning("encoded-literal", node.value, node.loc);
38
+ }
39
+ }
40
+ // Else we are checking all other string with our suspect method
41
+ else {
42
+ analysis.analyzeLiteral(node);
43
+ }
44
+ }
45
+
46
+ export default {
47
+ name: "isLiteral",
48
+ validateNode, main, breakOnMatch: false
49
+ };
@@ -1,7 +1,5 @@
1
- // Require Internal Dependencies
2
- import { isLiteralRegex } from "../utils.js";
3
-
4
1
  // Require Third-party Dependencies
2
+ import { isLiteralRegex } from "@nodesecure/estree-ast-utils";
5
3
  import safeRegex from "safe-regex";
6
4
 
7
5
  /**
@@ -1,31 +1,16 @@
1
- // Import Internal Dependencies
2
- import { getMemberExprName } from "../utils.js";
3
- import { processMainModuleRequire } from "../constants.js";
4
-
5
- // searching for "process.mainModule" pattern (processMainModuleRequire)
6
- function validateNode(node) {
7
- return [
8
- node.type === "MemberExpression"
9
- ];
10
- }
11
-
12
- function main(node, options) {
13
- const { analysis } = options;
14
-
15
- analysis.counter[node.computed ? "computedMemberExpr" : "memberExpr"]++;
16
-
17
- // retrieve the member name, like: foo.bar.hello
18
- // in our case we are searching for process.mainModule.*
19
- const memberName = getMemberExprName(node);
20
-
21
- if (memberName.startsWith(processMainModuleRequire)) {
22
- analysis.dependencies.add(memberName.slice(processMainModuleRequire.length), node.loc);
23
- }
24
-
25
- // TODO: require.main ?
26
- }
27
-
28
- export default {
29
- name: "isMemberExpression",
30
- validateNode, main, breakOnMatch: true, breakGroup: "import"
31
- };
1
+ function validateNode(node) {
2
+ return [
3
+ node.type === "MemberExpression"
4
+ ];
5
+ }
6
+
7
+ function main(node, options) {
8
+ const { analysis } = options;
9
+
10
+ analysis.counter[node.computed ? "computedMemberExpr" : "memberExpr"]++;
11
+ }
12
+
13
+ export default {
14
+ name: "isMemberExpression",
15
+ validateNode, main, breakOnMatch: true, breakGroup: "import"
16
+ };
@@ -0,0 +1,25 @@
1
+ // Import Internal Dependencies
2
+ import { extractNode } from "../utils.js";
3
+
4
+ // CONSTANTS
5
+ const kIdExtractor = extractNode("Identifier");
6
+
7
+ function validateNode(node) {
8
+ return [
9
+ node.type === "MethodDefinition"
10
+ ];
11
+ }
12
+
13
+ function main(node, options) {
14
+ const { analysis } = options;
15
+
16
+ kIdExtractor(
17
+ ({ name }) => analysis.identifiersName.push({ name, type: "method" }),
18
+ [node.key]
19
+ );
20
+ }
21
+
22
+ export default {
23
+ name: "isMethodDefinition",
24
+ validateNode, main, breakOnMatch: false
25
+ };
@@ -1,7 +1,5 @@
1
- // Import Internal Dependencies
2
- import { isLiteralRegex } from "../utils.js";
3
-
4
1
  // Import Third-party Dependencies
2
+ import { isLiteralRegex } from "@nodesecure/estree-ast-utils";
5
3
  import safeRegex from "safe-regex";
6
4
 
7
5
  /**
@@ -20,6 +18,13 @@ function main(node, options) {
20
18
  const { analysis } = options;
21
19
 
22
20
  const arg = node.arguments[0];
21
+ /**
22
+ * Note: RegExp Object can contain a RegExpLiteral
23
+ * @see https://github.com/estree/estree/blob/master/es5.md#regexpliteral
24
+ *
25
+ * @example
26
+ * new RegExp(/^foo/)
27
+ */
23
28
  const pattern = isLiteralRegex(arg) ? arg.regex.pattern : arg.value;
24
29
 
25
30
  // We use the safe-regex package to detect whether or not regex is safe!
@@ -1,54 +1,46 @@
1
1
  /* eslint-disable consistent-return */
2
2
 
3
- // Import Internal Dependencies
4
- import { isRequireGlobalMemberExpr, getMemberExprName, arrExprToString, concatBinaryExpr } from "../utils.js";
5
-
6
3
  // Import Third-party Dependencies
7
4
  import { Hex } from "@nodesecure/sec-literal";
8
5
  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;
6
+ import {
7
+ concatBinaryExpression,
8
+ arrayExpressionToString,
9
+ getMemberExpressionIdentifier,
10
+ getCallExpressionIdentifier
11
+ } from "@nodesecure/estree-ast-utils";
12
+
13
+ function validateNode(node, { tracer }) {
14
+ const id = getCallExpressionIdentifier(node);
15
+ if (id === null) {
16
+ return [false];
21
17
  }
22
18
 
23
- return node.callee.object.name === "require" && node.callee.property.name === "resolve";
24
- }
19
+ const data = tracer.getDataFromIdentifier(id);
25
20
 
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);
21
+ return [
22
+ data !== null && data.name === "require",
23
+ data?.identifierOrMemberExpr ?? void 0
24
+ ];
41
25
  }
42
26
 
43
27
  function main(node, options) {
44
28
  const { analysis } = options;
29
+ const { tracer } = analysis;
30
+
31
+ if (node.arguments.length === 0) {
32
+ return;
33
+ }
34
+ const arg = node.arguments.at(0);
45
35
 
46
- const arg = node.arguments[0];
47
36
  switch (arg.type) {
48
37
  // const foo = "http"; require(foo);
49
38
  case "Identifier":
50
- if (analysis.identifiers.has(arg.name)) {
51
- analysis.dependencies.add(analysis.identifiers.get(arg.name), node.loc);
39
+ if (analysis.tracer.literalIdentifiers.has(arg.name)) {
40
+ analysis.dependencies.add(
41
+ analysis.tracer.literalIdentifiers.get(arg.name),
42
+ node.loc
43
+ );
52
44
  }
53
45
  else {
54
46
  analysis.addWarning("unsafe-import", null, node.loc);
@@ -60,9 +52,12 @@ function main(node, options) {
60
52
  analysis.dependencies.add(arg.value, node.loc);
61
53
  break;
62
54
 
63
- // require(["ht" + "tp"])
55
+ // require(["ht", "tp"])
64
56
  case "ArrayExpression": {
65
- const value = arrExprToString(arg.elements, analysis.identifiers).trim();
57
+ const value = [...arrayExpressionToString(arg, { tracer })]
58
+ .join("")
59
+ .trim();
60
+
66
61
  if (value === "") {
67
62
  analysis.addWarning("unsafe-import", null, node.loc);
68
63
  }
@@ -75,23 +70,27 @@ function main(node, options) {
75
70
  // require("ht" + "tp");
76
71
  case "BinaryExpression": {
77
72
  if (arg.operator !== "+") {
73
+ analysis.addWarning("unsafe-import", null, node.loc);
78
74
  break;
79
75
  }
80
76
 
81
- const value = concatBinaryExpr(arg, analysis.identifiers);
82
- if (value === null) {
83
- analysis.addWarning("unsafe-import", null, node.loc);
77
+ try {
78
+ const iter = concatBinaryExpression(arg, {
79
+ tracer, stopOnUnsupportedNode: true
80
+ });
81
+
82
+ analysis.dependencies.add([...iter].join(""), node.loc);
84
83
  }
85
- else {
86
- analysis.dependencies.add(value, node.loc);
84
+ catch {
85
+ analysis.addWarning("unsafe-import", null, node.loc);
87
86
  }
88
87
  break;
89
88
  }
90
89
 
91
90
  // require(Buffer.from("...", "hex").toString());
92
91
  case "CallExpression": {
93
- const { dependencies } = parseRequireCallExpression(arg);
94
- dependencies.forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
92
+ walkRequireCallExpression(arg)
93
+ .forEach((depName) => analysis.dependencies.add(depName, node.loc, true));
95
94
 
96
95
  analysis.addWarning("unsafe-import", null, node.loc);
97
96
 
@@ -104,7 +103,7 @@ function main(node, options) {
104
103
  }
105
104
  }
106
105
 
107
- function parseRequireCallExpression(nodeToWalk) {
106
+ function walkRequireCallExpression(nodeToWalk) {
108
107
  const dependencies = new Set();
109
108
 
110
109
  walk(nodeToWalk, {
@@ -113,34 +112,30 @@ function parseRequireCallExpression(nodeToWalk) {
113
112
  return;
114
113
  }
115
114
 
116
- if (node.arguments[0].type === "Literal" && Hex.isHex(node.arguments[0].value)) {
117
- dependencies.add(Buffer.from(node.arguments[0].value, "hex").toString());
115
+ const rootArgument = node.arguments.at(0);
116
+ if (rootArgument.type === "Literal" && Hex.isHex(rootArgument.value)) {
117
+ dependencies.add(Buffer.from(rootArgument.value, "hex").toString());
118
118
 
119
119
  return this.skip();
120
120
  }
121
121
 
122
- const fullName = node.callee.type === "MemberExpression" ? getMemberExprName(node.callee) : node.callee.name;
122
+ const fullName = node.callee.type === "MemberExpression" ?
123
+ [...getMemberExpressionIdentifier(node.callee)].join(".") :
124
+ node.callee.name;
125
+
123
126
  switch (fullName) {
124
127
  case "Buffer.from": {
125
- const [element, convert] = node.arguments;
128
+ const [element] = node.arguments;
126
129
 
127
130
  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);
131
+ const depName = [...arrayExpressionToString(element)].join("").trim();
132
+ dependencies.add(depName);
136
133
  }
137
134
  break;
138
135
  }
139
136
  case "require.resolve": {
140
- const [element] = node.arguments;
141
-
142
- if (element.type === "Literal") {
143
- dependencies.add(element.value);
137
+ if (rootArgument.type === "Literal") {
138
+ dependencies.add(rootArgument.value);
144
139
  }
145
140
  break;
146
141
  }
@@ -148,9 +143,7 @@ function parseRequireCallExpression(nodeToWalk) {
148
143
  }
149
144
  });
150
145
 
151
- return {
152
- dependencies: [...dependencies]
153
- };
146
+ return [...dependencies];
154
147
  }
155
148
 
156
149
  export default {
@@ -1,4 +1,4 @@
1
- // Require Internal Dependencies
1
+ // Import Internal Dependencies
2
2
  import { isUnsafeCallee } from "../utils.js";
3
3
 
4
4
  /**
@@ -15,6 +15,8 @@ function main(node, options) {
15
15
  const { analysis, data: calleeName } = options;
16
16
 
17
17
  analysis.addWarning("unsafe-stmt", calleeName, node.loc);
18
+
19
+ return Symbol.for("skipWalk");
18
20
  }
19
21
 
20
22
  export default {