eslint-plugin-security 1.4.0 → 1.5.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.
Files changed (40) hide show
  1. package/.eslintrc +11 -1
  2. package/.github/workflows/ci.yml +47 -0
  3. package/.github/workflows/pr.yml +15 -0
  4. package/.prettierrc.json +7 -0
  5. package/CHANGELOG.md +65 -34
  6. package/README.md +26 -24
  7. package/docs/avoid-command-injection-node.md +85 -0
  8. package/docs/bypass-connect-csrf-protection-by-abusing.md +42 -0
  9. package/docs/regular-expression-dos-and-node.md +83 -0
  10. package/docs/the-dangers-of-square-bracket-notation.md +107 -0
  11. package/index.js +1 -3
  12. package/package.json +26 -7
  13. package/rules/data/fsFunctionData.json +49 -49
  14. package/rules/detect-buffer-noassert.js +66 -55
  15. package/rules/detect-child-process.js +36 -27
  16. package/rules/detect-disable-mustache-escape.js +24 -14
  17. package/rules/detect-eval-with-expression.js +19 -9
  18. package/rules/detect-new-buffer.js +18 -15
  19. package/rules/detect-no-csrf-before-method-override.js +32 -25
  20. package/rules/detect-non-literal-fs-filename.js +38 -33
  21. package/rules/detect-non-literal-regexp.js +24 -18
  22. package/rules/detect-non-literal-require.js +25 -17
  23. package/rules/detect-object-injection.js +61 -59
  24. package/rules/detect-possible-timing-attacks.js +40 -42
  25. package/rules/detect-pseudoRandomBytes.js +18 -11
  26. package/rules/detect-unsafe-regex.js +36 -23
  27. package/test/detect-buffer-noassert.js +18 -18
  28. package/test/detect-child-process.js +14 -24
  29. package/test/detect-disable-mustache-escape.js +0 -1
  30. package/test/detect-eval-with-expression.js +0 -1
  31. package/test/detect-new-buffer.js +0 -1
  32. package/test/detect-no-csrf-before-method-override.js +0 -1
  33. package/test/detect-non-literal-fs-filename.js +0 -1
  34. package/test/detect-non-literal-regexp.js +0 -1
  35. package/test/detect-non-literal-require.js +8 -5
  36. package/test/detect-object-injection.js +0 -2
  37. package/test/detect-possible-timing-attacks.js +0 -2
  38. package/test/detect-pseudoRandomBytes.js +0 -1
  39. package/test/detect-unsafe-regexp.js +2 -4
  40. package/.npmignore +0 -1
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "eslint-plugin-security",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Security rules for eslint",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "changelog": "changelog eslint-plugin-security all > CHANGELOG.md",
8
- "test": "./node_modules/.bin/mocha test/**/*",
9
- "lint": "./node_modules/.bin/eslint .",
10
- "cont-int": "npm test && npm run-script lint"
8
+ "release": "npx semantic-release",
9
+ "test": "mocha test/**/*",
10
+ "format": "prettier --write **/*.{md,js,yml}",
11
+ "lint": "eslint .",
12
+ "lint:fix": "eslint --fix .",
13
+ "cont-int": "npm test && npm run lint"
11
14
  },
12
15
  "repository": {
13
16
  "type": "git",
@@ -24,13 +27,29 @@
24
27
  "url": "https://github.com/nodesecurity/eslint-plugin-security/issues"
25
28
  },
26
29
  "homepage": "https://github.com/nodesecurity/eslint-plugin-security#readme",
30
+ "gitHooks": {
31
+ "pre-commit": "lint-staged"
32
+ },
33
+ "lint-staged": {
34
+ "*.js": [
35
+ "prettier --write",
36
+ "eslint --fix"
37
+ ],
38
+ "*.md": "prettier --write",
39
+ "*.yml": "prettier --write"
40
+ },
27
41
  "dependencies": {
28
- "safe-regex": "^1.1.0"
42
+ "safe-regex": "^2.1.1"
29
43
  },
30
44
  "devDependencies": {
31
45
  "changelog": "1.3.0",
32
- "eslint": "^2.10.1",
46
+ "eslint": "^8.11.0",
33
47
  "eslint-config-nodesecurity": "^1.3.1",
34
- "mocha": "^2.4.5"
48
+ "eslint-config-prettier": "^8.5.0",
49
+ "lint-staged": "^12.3.7",
50
+ "mocha": "^9.2.2",
51
+ "prettier": "^2.6.2",
52
+ "semantic-release": "^19.0.2",
53
+ "yorkie": "^2.0.0"
35
54
  }
36
55
  }
@@ -1,51 +1,51 @@
1
1
  {
2
- "appendFile": [0],
3
- "appendFileSync": [0],
4
- "chmod": [0],
5
- "chmodSync": [0],
6
- "chown": [0],
7
- "chownSync": [0],
8
- "createReadStream": [0],
9
- "createWriteStream": [0],
10
- "exists": [0],
11
- "existsSync": [0],
12
- "lchmod": [0],
13
- "lchmodSync": [0],
14
- "lchown": [0],
15
- "lchownSync": [0],
16
- "link": [0,1],
17
- "linkSync": [0,1],
18
- "lstat": [0],
19
- "lstatSync": [0],
20
- "mkdir": [0],
21
- "mkdirSync": [0],
22
- "open": [0],
23
- "openSync": [0],
24
- "readdir": [0],
25
- "readdirSync": [0],
26
- "readFile": [0],
27
- "readFileSync": [0],
28
- "readlink": [0],
29
- "readlinkSync": [0],
30
- "realpath": [0],
31
- "realpathSync": [0],
32
- "rename": [0,1],
33
- "renameSync": [0,1],
34
- "rmdir": [0],
35
- "rmdirSync": [0],
36
- "stat": [0],
37
- "statSync": [0],
38
- "symlink": [0,1],
39
- "symlinkSync": [0,1],
40
- "truncate": [0],
41
- "truncateSync": [0],
42
- "unlink": [0],
43
- "unlinkSync": [0],
44
- "unwatchFile": [0],
45
- "utimes": [0],
46
- "utimesSync": [0],
47
- "watch": [0],
48
- "watchFile": [0],
49
- "writeFile": [0],
50
- "writeFileSync": [0]
2
+ "appendFile": [0],
3
+ "appendFileSync": [0],
4
+ "chmod": [0],
5
+ "chmodSync": [0],
6
+ "chown": [0],
7
+ "chownSync": [0],
8
+ "createReadStream": [0],
9
+ "createWriteStream": [0],
10
+ "exists": [0],
11
+ "existsSync": [0],
12
+ "lchmod": [0],
13
+ "lchmodSync": [0],
14
+ "lchown": [0],
15
+ "lchownSync": [0],
16
+ "link": [0, 1],
17
+ "linkSync": [0, 1],
18
+ "lstat": [0],
19
+ "lstatSync": [0],
20
+ "mkdir": [0],
21
+ "mkdirSync": [0],
22
+ "open": [0],
23
+ "openSync": [0],
24
+ "readdir": [0],
25
+ "readdirSync": [0],
26
+ "readFile": [0],
27
+ "readFileSync": [0],
28
+ "readlink": [0],
29
+ "readlinkSync": [0],
30
+ "realpath": [0],
31
+ "realpathSync": [0],
32
+ "rename": [0, 1],
33
+ "renameSync": [0, 1],
34
+ "rmdir": [0],
35
+ "rmdirSync": [0],
36
+ "stat": [0],
37
+ "statSync": [0],
38
+ "symlink": [0, 1],
39
+ "symlinkSync": [0, 1],
40
+ "truncate": [0],
41
+ "truncateSync": [0],
42
+ "unlink": [0],
43
+ "unlinkSync": [0],
44
+ "unwatchFile": [0],
45
+ "utimes": [0],
46
+ "utimesSync": [0],
47
+ "watch": [0],
48
+ "watchFile": [0],
49
+ "writeFile": [0],
50
+ "writeFileSync": [0]
51
51
  }
@@ -1,69 +1,80 @@
1
1
  /**
2
2
  * Tries to detect buffer read / write calls that use noAssert set to true
3
- * @author Adam Baldwin
3
+ * @author Adam Baldwin
4
4
  */
5
5
 
6
- //------------------------------------------------------------------------------
7
- // Rule Definition
8
- //------------------------------------------------------------------------------
6
+ 'use strict';
9
7
 
10
- var names = [];
8
+ //-----------------------------------------------------------------------------
9
+ // Helpers
10
+ //-----------------------------------------------------------------------------
11
11
 
12
- module.exports = function(context) {
12
+ const read = [
13
+ 'readUInt8',
14
+ 'readUInt16LE',
15
+ 'readUInt16BE',
16
+ 'readUInt32LE',
17
+ 'readUInt32BE',
18
+ 'readInt8',
19
+ 'readInt16LE',
20
+ 'readInt16BE',
21
+ 'readInt32LE',
22
+ 'readInt32BE',
23
+ 'readFloatLE',
24
+ 'readFloatBE',
25
+ 'readDoubleLE',
26
+ 'readDoubleBE',
27
+ ];
13
28
 
14
- "use strict";
29
+ const write = [
30
+ 'writeUInt8',
31
+ 'writeUInt16LE',
32
+ 'writeUInt16BE',
33
+ 'writeUInt32LE',
34
+ 'writeUInt32BE',
35
+ 'writeInt8',
36
+ 'writeInt16LE',
37
+ 'writeInt16BE',
38
+ 'writeInt32LE',
39
+ 'writeInt32BE',
40
+ 'writeFloatLE',
41
+ 'writeFloatBE',
42
+ 'writeDoubleLE',
43
+ 'writeDoubleBE',
44
+ ];
15
45
 
16
- var read = [
17
- "readUInt8",
18
- "readUInt16LE",
19
- "readUInt16BE",
20
- "readUInt32LE",
21
- "readUInt32BE",
22
- "readInt8",
23
- "readInt16LE",
24
- "readInt16BE",
25
- "readInt32LE",
26
- "readInt32BE",
27
- "readFloatLE",
28
- "readFloatBE",
29
- "readDoubleL",
30
- "readDoubleBE"
31
- ];
32
-
33
- var write = [
34
- "writeUInt8",
35
- "writeUInt16LE",
36
- "writeUInt16BE",
37
- "writeUInt32LE",
38
- "writeUInt32BE",
39
- "writeInt8",
40
- "writeInt16LE",
41
- "writeInt16BE",
42
- "writeInt32LE",
43
- "writeInt32BE",
44
- "writeFloatLE",
45
- "writeFloatBE",
46
- "writeDoubleLE",
47
- "writeDoubleBE"
48
- ];
46
+ //------------------------------------------------------------------------------
47
+ // Rule Definition
48
+ //------------------------------------------------------------------------------
49
49
 
50
+ module.exports = {
51
+ meta: {
52
+ type: 'error',
53
+ docs: {
54
+ description: 'Detect calls to "buffer" with "noAssert" flag set.',
55
+ category: 'Possible Security Vulnerability',
56
+ recommended: true,
57
+ url: 'https://github.com/nodesecurity/eslint-plugin-security#detect-buffer-noassert',
58
+ },
59
+ __methodsToCheck: {
60
+ read,
61
+ write,
62
+ },
63
+ },
64
+ create: function (context) {
50
65
  return {
51
- "MemberExpression": function (node) {
52
- var index;
53
- if (read.indexOf(node.property.name) !== -1) {
54
- index = 1;
55
- } else if (write.indexOf(node.property.name) !== -1) {
56
- index = 2;
57
- }
58
-
59
- if (index && node.parent && node.parent.arguments && node.parent.arguments[index] && node.parent.arguments[index].value) {
60
- var token = context.getTokens(node)[0];
61
- return context.report(node, 'Found Buffer.' + node.property.name + ' with noAssert flag set true');
62
-
63
- }
66
+ MemberExpression: function (node) {
67
+ let index;
68
+ if (read.indexOf(node.property.name) !== -1) {
69
+ index = 1;
70
+ } else if (write.indexOf(node.property.name) !== -1) {
71
+ index = 2;
64
72
  }
65
73
 
74
+ if (index && node.parent && node.parent.arguments && node.parent.arguments[index] && node.parent.arguments[index].value) {
75
+ return context.report(node, `Found Buffer.${node.property.name} with noAssert flag set true`);
76
+ }
77
+ },
66
78
  };
67
-
79
+ },
68
80
  };
69
-
@@ -3,40 +3,49 @@
3
3
  * @author Adam Baldwin
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
- var names = [];
11
-
12
- module.exports = function(context) {
13
-
14
- "use strict";
12
+ /*
13
+ * Stores variable names pointing to child_process to check (child_process).exec()
14
+ */
15
+ const names = [];
15
16
 
17
+ module.exports = {
18
+ meta: {
19
+ type: 'error',
20
+ docs: {
21
+ description: 'Detect instances of "child_process" & non-literal "exec()" calls.',
22
+ category: 'Possible Security Vulnerability',
23
+ recommended: true,
24
+ url: 'https://github.com/nodesecurity/eslint-plugin-security/blob/main/docs/avoid-command-injection-node.md',
25
+ },
26
+ },
27
+ create: function (context) {
16
28
  return {
17
- "CallExpression": function (node) {
18
- var token = context.getTokens(node)[0];
19
- if (node.callee.name === 'require') {
20
- var args = node.arguments[0];
21
- if (args && args.type === 'Literal' && args.value === 'child_process') {
22
- if (node.parent.type === 'VariableDeclarator') {
23
- names.push(node.parent.id.name);
24
- } else if (node.parent.type === 'AssignmentExpression' && node.parent.operator === '=') {
25
- names.push(node.parent.left.name);
26
- }
27
- return context.report(node, 'Found require("child_process")');
28
- }
29
- }
30
- },
31
- "MemberExpression": function (node) {
32
- var token = context.getTokens(node)[0];
33
- if (node.property.name === 'exec' && names.indexOf(node.object.name) > -1) {
34
- if (node.parent && node.parent.arguments && node.parent.arguments[0].type !== 'Literal') {
35
- return context.report(node, 'Found child_process.exec() with non Literal first argument');
36
- }
29
+ CallExpression: function (node) {
30
+ if (node.callee.name === 'require') {
31
+ const args = node.arguments[0];
32
+ if (args && args.type === 'Literal' && args.value === 'child_process') {
33
+ if (node.parent.type === 'VariableDeclarator') {
34
+ names.push(node.parent.id.name);
35
+ } else if (node.parent.type === 'AssignmentExpression' && node.parent.operator === '=') {
36
+ names.push(node.parent.left.name);
37
37
  }
38
+ return context.report(node, 'Found require("child_process")');
39
+ }
38
40
  }
39
-
41
+ },
42
+ MemberExpression: function (node) {
43
+ if (node.property.name === 'exec' && names.indexOf(node.object.name) > -1) {
44
+ if (node.parent && node.parent.arguments.length && node.parent.arguments[0].type !== 'Literal') {
45
+ return context.report(node, 'Found child_process.exec() with non Literal first argument');
46
+ }
47
+ }
48
+ },
40
49
  };
41
-
50
+ },
42
51
  };
@@ -1,18 +1,28 @@
1
- module.exports = function(context) {
1
+ 'use strict';
2
2
 
3
- "use strict";
3
+ module.exports = {
4
+ meta: {
5
+ type: 'error',
6
+ docs: {
7
+ description: 'Detects "object.escapeMarkup = false", which can be used with some template engines to disable escaping of HTML entities.',
8
+ category: 'Possible Security Vulnerability',
9
+ recommended: true,
10
+ url: 'https://github.com/nodesecurity/eslint-plugin-security#detect-disable-mustache-escape'
11
+ }
12
+ },
13
+ create: function (context) {
4
14
  return {
5
- "AssignmentExpression": function(node) {
6
- if (node.operator === '=') {
7
- if (node.left.property) {
8
- if (node.left.property.name == 'escapeMarkup') {
9
- if (node.right.value == false) {
10
- context.report(node, 'Markup escaping disabled.')
11
- }
12
- }
13
- }
15
+ AssignmentExpression: function (node) {
16
+ if (node.operator === '=') {
17
+ if (node.left.property) {
18
+ if (node.left.property.name === 'escapeMarkup') {
19
+ if (node.right.value === false) {
20
+ context.report(node, 'Markup escaping disabled.');
21
+ }
14
22
  }
23
+ }
15
24
  }
16
- }
17
-
18
- }
25
+ }
26
+ };
27
+ }
28
+ };
@@ -1,21 +1,31 @@
1
1
  /**
2
- * Idnetifies eval with expression
2
+ * Identifies eval with expression
3
3
  * @author Adam Baldwin
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
- module.exports = function(context) {
11
-
12
- "use strict";
13
-
12
+ module.exports = {
13
+ meta: {
14
+ type: 'error',
15
+ docs: {
16
+ description: 'Detects "eval(variable)" which can allow an attacker to run arbitrary code inside your process.',
17
+ category: 'Possible Security Vulnerability',
18
+ recommended: true,
19
+ url: 'https://github.com/nodesecurity/eslint-plugin-security#detect-eval-with-expression'
20
+ }
21
+ },
22
+ create: function (context) {
14
23
  return {
15
- "CallExpression": function(node) {
16
- if (node.callee.name === "eval" && node.arguments[0].type !== 'Literal') {
17
- context.report(node, "eval with argument of type " + node.arguments[0].type);
18
- }
24
+ CallExpression: function (node) {
25
+ if (node.callee.name === 'eval' && node.arguments[0].type !== 'Literal') {
26
+ context.report(node, `eval with argument of type ${node.arguments[0].type}`);
19
27
  }
28
+ }
20
29
  };
30
+ }
21
31
  };
@@ -1,19 +1,22 @@
1
- module.exports = function (context) {
2
- // Detects instances of new Buffer(argument)
3
- // where argument is any non literal value.
4
- return {
5
- "NewExpression": function (node) {
6
- if (node.callee.name === 'Buffer' &&
7
- node.arguments[0] &&
8
- node.arguments[0].type != 'Literal') {
1
+ 'use strict';
9
2
 
10
- return context.report(node, "Found new Buffer");
3
+ module.exports = {
4
+ meta: {
5
+ type: 'error',
6
+ docs: {
7
+ description: 'Detect instances of new Buffer(argument) where argument is any non-literal value.',
8
+ category: 'Possible Security Vulnerability',
9
+ recommended: true,
10
+ url: 'https://github.com/nodesecurity/eslint-plugin-security/blob/main/README.md'
11
+ }
12
+ },
13
+ create: function (context) {
14
+ return {
15
+ NewExpression: function (node) {
16
+ if (node.callee.name === 'Buffer' && node.arguments[0] && node.arguments[0].type !== 'Literal') {
17
+ return context.report(node, 'Found new Buffer');
11
18
  }
12
-
13
-
14
-
15
19
  }
16
20
  };
17
-
18
- }
19
-
21
+ }
22
+ };
@@ -3,37 +3,44 @@
3
3
  * @author Adam Baldwin
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
-
11
- module.exports = function(context) {
12
-
13
- "use strict";
14
- var csrf = false;
12
+ module.exports = {
13
+ meta: {
14
+ type: 'error',
15
+ docs: {
16
+ description: 'Detects Express "csrf" middleware setup before "method-override" middleware.',
17
+ category: 'Possible Security Vulnerability',
18
+ recommended: true,
19
+ url: 'https://github.com/nodesecurity/eslint-plugin-security/blob/main/docs/bypass-connect-csrf-protection-by-abusing.md',
20
+ },
21
+ },
22
+ create: function (context) {
23
+ let csrf = false;
15
24
 
16
25
  return {
17
- "CallExpression": function(node) {
18
- var token = context.getTokens(node)[0],
19
- nodeType = token.type,
20
- nodeValue = token.value;
21
-
22
- if (nodeValue === "express") {
23
- if (!node.callee || !node.callee.property) {
24
- return;
25
- }
26
-
27
- if (node.callee.property.name === "methodOverride" && csrf) {
28
- context.report(node, "express.csrf() middleware found before express.methodOverride()");
29
- }
30
- if (node.callee.property.name === "csrf") {
31
- // Keep track of found CSRF
32
- csrf = true;
33
- }
34
- }
26
+ CallExpression: function (node) {
27
+ const token = context.getTokens(node)[0];
28
+ const nodeValue = token.value;
29
+
30
+ if (nodeValue === 'express') {
31
+ if (!node.callee || !node.callee.property) {
32
+ return;
33
+ }
34
+
35
+ if (node.callee.property.name === 'methodOverride' && csrf) {
36
+ context.report(node, 'express.csrf() middleware found before express.methodOverride()');
37
+ }
38
+ if (node.callee.property.name === 'csrf') {
39
+ // Keep track of found CSRF
40
+ csrf = true;
41
+ }
35
42
  }
43
+ },
36
44
  };
37
-
45
+ },
38
46
  };
39
-
@@ -3,47 +3,52 @@
3
3
  * @author Adam Baldwin
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
- var names = [];
11
- var fsMetaData = require('./data/fsFunctionData.json');
12
- var funcNames = Object.keys(fsMetaData);
13
-
14
- module.exports = function(context) {
15
-
16
- "use strict";
17
-
12
+ const fsMetaData = require('./data/fsFunctionData.json');
13
+ const funcNames = Object.keys(fsMetaData);
14
+
15
+ module.exports = {
16
+ meta: {
17
+ type: 'error',
18
+ docs: {
19
+ description: 'Detects variable in filename argument of "fs" calls, which might allow an attacker to access anything on your system.',
20
+ category: 'Possible Security Vulnerability',
21
+ recommended: true,
22
+ url: 'https://github.com/nodesecurity/eslint-plugin-security#detect-non-literal-fs-filename',
23
+ },
24
+ },
25
+ create: function (context) {
18
26
  return {
19
- "MemberExpression": function (node) {
20
- var result = [];
21
- if (funcNames.indexOf(node.property.name) !== -1) {
22
- var meta = fsMetaData[node.property.name];
23
- var args = node.parent.arguments;
24
- meta.forEach(function (i) {
25
- if (args && args.length > i) {
26
- if (args[i].type !== 'Literal') {
27
- result.push(i);
28
- }
29
- }
30
- });
31
- }
32
-
33
- if (result.length > 0) {
34
- var token = context.getTokens(node)[0];
35
- return context.report(node, 'Found fs.' + node.property.name + ' with non literal argument at index ' + result.join(','));
27
+ MemberExpression: function (node) {
28
+ const result = [];
29
+ if (funcNames.indexOf(node.property.name) !== -1) {
30
+ const meta = fsMetaData[node.property.name];
31
+ const args = node.parent.arguments;
32
+ meta.forEach((i) => {
33
+ if (args && args.length > i) {
34
+ if (args[i].type !== 'Literal') {
35
+ result.push(i);
36
+ }
36
37
  }
38
+ });
39
+ }
37
40
 
38
-
39
- /*
40
- if (node.parent && node.parent.arguments && node.parent.arguments[index].value) {
41
- return context.report(node, 'found Buffer.' + node.property.name + ' with noAssert flag set true');
42
-
43
- }
44
- */
41
+ if (result.length > 0) {
42
+ return context.report(node, `Found fs.${node.property.name} with non literal argument at index ${result.join(',')}`);
45
43
  }
46
44
 
47
- };
45
+ /*
46
+ if (node.parent && node.parent.arguments && node.parent.arguments[index].value) {
47
+ return context.report(node, 'found Buffer.' + node.property.name + ' with noAssert flag set true');
48
48
 
49
+ }
50
+ */
51
+ },
52
+ };
53
+ },
49
54
  };