eslint-plugin-security 1.4.0 → 1.6.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 (66) hide show
  1. package/.eslint-doc-generatorrc.js +9 -0
  2. package/.eslintrc +29 -1
  3. package/.github/ISSUE_TEMPLATE/bug-report.yml +85 -0
  4. package/.github/ISSUE_TEMPLATE/new-rule.yml +39 -0
  5. package/.github/ISSUE_TEMPLATE/rule-change.yml +61 -0
  6. package/.github/workflows/ci.yml +55 -0
  7. package/.github/workflows/pr.yml +19 -0
  8. package/.github/workflows/release-please.yml +39 -0
  9. package/.markdownlint.json +4 -0
  10. package/.markdownlintignore +3 -0
  11. package/.prettierrc.json +7 -0
  12. package/CHANGELOG.md +114 -34
  13. package/README.md +45 -85
  14. package/docs/avoid-command-injection-node.md +85 -0
  15. package/docs/bypass-connect-csrf-protection-by-abusing.md +42 -0
  16. package/docs/regular-expression-dos-and-node.md +83 -0
  17. package/docs/rules/detect-bidi-characters.md +50 -0
  18. package/docs/rules/detect-buffer-noassert.md +9 -0
  19. package/docs/rules/detect-child-process.md +9 -0
  20. package/docs/rules/detect-disable-mustache-escape.md +9 -0
  21. package/docs/rules/detect-eval-with-expression.md +7 -0
  22. package/docs/rules/detect-new-buffer.md +5 -0
  23. package/docs/rules/detect-no-csrf-before-method-override.md +9 -0
  24. package/docs/rules/detect-non-literal-fs-filename.md +7 -0
  25. package/docs/rules/detect-non-literal-regexp.md +7 -0
  26. package/docs/rules/detect-non-literal-require.md +7 -0
  27. package/docs/rules/detect-object-injection.md +7 -0
  28. package/docs/rules/detect-possible-timing-attacks.md +5 -0
  29. package/docs/rules/detect-pseudoRandomBytes.md +5 -0
  30. package/docs/rules/detect-unsafe-regex.md +7 -0
  31. package/docs/the-dangers-of-square-bracket-notation.md +107 -0
  32. package/index.js +10 -9
  33. package/package.json +34 -7
  34. package/rules/detect-bidi-characters.js +101 -0
  35. package/rules/detect-buffer-noassert.js +66 -55
  36. package/rules/detect-child-process.js +57 -25
  37. package/rules/detect-disable-mustache-escape.js +24 -14
  38. package/rules/detect-eval-with-expression.js +19 -9
  39. package/rules/detect-new-buffer.js +19 -16
  40. package/rules/detect-no-csrf-before-method-override.js +32 -25
  41. package/rules/detect-non-literal-fs-filename.js +86 -33
  42. package/rules/detect-non-literal-regexp.js +24 -18
  43. package/rules/detect-non-literal-require.js +25 -17
  44. package/rules/detect-object-injection.js +61 -59
  45. package/rules/detect-possible-timing-attacks.js +40 -42
  46. package/rules/detect-pseudoRandomBytes.js +18 -11
  47. package/rules/detect-unsafe-regex.js +36 -23
  48. package/test/detect-bidi-characters.js +74 -0
  49. package/test/detect-buffer-noassert.js +18 -18
  50. package/test/detect-child-process.js +49 -23
  51. package/test/detect-disable-mustache-escape.js +3 -4
  52. package/test/detect-eval-with-expression.js +4 -5
  53. package/test/detect-new-buffer.js +4 -5
  54. package/test/detect-no-csrf-before-method-override.js +3 -4
  55. package/test/detect-non-literal-fs-filename.js +135 -9
  56. package/test/detect-non-literal-regexp.js +5 -6
  57. package/test/detect-non-literal-require.js +11 -8
  58. package/test/detect-object-injection.js +3 -5
  59. package/test/detect-possible-timing-attacks.js +8 -10
  60. package/test/detect-pseudoRandomBytes.js +3 -4
  61. package/test/detect-unsafe-regexp.js +9 -11
  62. package/test/utils/import-utils.js +172 -0
  63. package/utils/data/fsFunctionData.json +51 -0
  64. package/utils/import-utils.js +196 -0
  65. package/.npmignore +0 -1
  66. package/rules/data/fsFunctionData.json +0 -51
@@ -3,47 +3,100 @@
3
3
  * @author Adam Baldwin
4
4
  */
5
5
 
6
+ 'use strict';
7
+
8
+ const fsMetaData = require('../utils/data/fsFunctionData.json');
9
+ const funcNames = Object.keys(fsMetaData);
10
+ const fsPackageNames = ['fs', 'node:fs', 'fs/promises', 'node:fs/promises', 'fs-extra'];
11
+
12
+ const { getImportAccessPath } = require('../utils/import-utils');
13
+
6
14
  //------------------------------------------------------------------------------
7
- // Rule Definition
15
+ // Utils
8
16
  //------------------------------------------------------------------------------
9
17
 
10
- var names = [];
11
- var fsMetaData = require('./data/fsFunctionData.json');
12
- var funcNames = Object.keys(fsMetaData);
18
+ function getIndices(node, argMeta) {
19
+ return (argMeta || []).filter((argIndex) => node.arguments[argIndex].type !== 'Literal');
20
+ }
13
21
 
14
- module.exports = function(context) {
22
+ function generateReport({ context, node, packageName, methodName, indices }) {
23
+ if (!indices || indices.length === 0) {
24
+ return;
25
+ }
26
+ context.report({ node, message: `Found ${methodName} from package "${packageName}" with non literal argument at index ${indices.join(',')}` });
27
+ }
15
28
 
16
- "use strict";
29
+ //------------------------------------------------------------------------------
30
+ // Rule Definition
31
+ //------------------------------------------------------------------------------
17
32
 
33
+ module.exports = {
34
+ meta: {
35
+ type: 'error',
36
+ docs: {
37
+ description: 'Detects variable in filename argument of "fs" calls, which might allow an attacker to access anything on your system.',
38
+ category: 'Possible Security Vulnerability',
39
+ recommended: true,
40
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-fs-filename.md',
41
+ },
42
+ },
43
+ create: function (context) {
18
44
  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(','));
36
- }
37
-
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
- */
45
+ CallExpression: function (node) {
46
+ // don't check require. If all arguments are Literals, it's surely safe!
47
+ if ((node.callee.type === 'Identifier' && node.callee.name === 'require') || node.arguments.every((argument) => argument.type === 'Literal')) {
48
+ return;
45
49
  }
46
50
 
47
- };
51
+ const pathInfo = getImportAccessPath({
52
+ node: node.callee,
53
+ scope: context.getScope(),
54
+ packageNames: fsPackageNames,
55
+ });
56
+ if (!pathInfo) {
57
+ return;
58
+ }
59
+ let fnName;
60
+ if (pathInfo.path.length === 1) {
61
+ // Check for:
62
+ // | var something = require('fs').readFile;
63
+ // | something(a);
64
+ // ,
65
+ // | var something = require('fs');
66
+ // | something.readFile(c);
67
+ // ,
68
+ // | var { readFile: something } = require('fs')
69
+ // | readFile(filename);
70
+ // ,
71
+ // | import { readFile as something } from 'fs';
72
+ // | something(filename);
73
+ // , or
74
+ // | import * as something from 'fs';
75
+ // | something.readFile(c);
76
+ fnName = pathInfo.path[0];
77
+ } else if (pathInfo.path.length === 2) {
78
+ // Check for:
79
+ // | var something = require('fs').promises;
80
+ // | something.readFile(filename)
81
+ fnName = pathInfo.path[1];
82
+ } else {
83
+ return;
84
+ }
85
+ if (!funcNames.includes(fnName)) {
86
+ return false;
87
+ }
88
+ const packageName = pathInfo.packageName;
89
+
90
+ const indices = getIndices(node, fsMetaData[fnName]);
48
91
 
92
+ generateReport({
93
+ context,
94
+ node,
95
+ packageName,
96
+ methodName: fnName,
97
+ indices,
98
+ });
99
+ },
100
+ };
101
+ },
49
102
  };
@@ -3,26 +3,32 @@
3
3
  * @author Jon Lamendola
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
-
15
- return {
16
- "NewExpression": function(node) {
17
- if (node.callee.name === 'RegExp') {
18
- var args = node.arguments;
19
- if (args && args.length > 0 && args[0].type !== 'Literal') {
20
- var token = context.getTokens(node)[0];
21
- return context.report(node, 'Found non-literal argument to RegExp Constructor');
22
- }
23
- }
24
-
25
- }
26
-
12
+ module.exports = {
13
+ meta: {
14
+ type: 'error',
15
+ docs: {
16
+ description: 'Detects "RegExp(variable)", which might allow an attacker to DOS your server with a long-running regular expression.',
17
+ category: 'Possible Security Vulnerability',
18
+ recommended: true,
19
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-regexp.md',
20
+ },
21
+ },
22
+ create: function (context) {
23
+ return {
24
+ NewExpression: function (node) {
25
+ if (node.callee.name === 'RegExp') {
26
+ const args = node.arguments;
27
+ if (args && args.length > 0 && args[0].type !== 'Literal') {
28
+ return context.report({ node: node, message: 'Found non-literal argument to RegExp Constructor' });
29
+ }
27
30
  }
28
- }
31
+ },
32
+ };
33
+ },
34
+ };
@@ -1,29 +1,37 @@
1
1
  /**
2
2
  * Tries to detect calls to require with non-literal argument
3
- * @author Adam Baldwin
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 "require(variable)", which might allow an attacker to load and run arbitrary code, or access arbitrary files on disk.',
17
+ category: 'Possible Security Vulnerability',
18
+ recommended: true,
19
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-require.md',
20
+ },
21
+ },
22
+ create: function (context) {
14
23
  return {
15
- "CallExpression": function (node) {
16
- if (node.callee.name === 'require') {
17
- var args = node.arguments;
18
- if (args && args.length > 0 && args[0].type !== 'Literal') {
19
- var token = context.getTokens(node)[0];
20
- return context.report(node, 'Found non-literal argument in require');
21
- }
22
- }
23
-
24
+ CallExpression: function (node) {
25
+ if (node.callee.name === 'require') {
26
+ const args = node.arguments;
27
+ if (
28
+ (args && args.length > 0 && args[0].type === 'TemplateLiteral' && args[0].expressions.length > 0) ||
29
+ (args[0].type !== 'TemplateLiteral' && args[0].type !== 'Literal')
30
+ ) {
31
+ return context.report({ node: node, message: 'Found non-literal argument in require' });
32
+ }
24
33
  }
25
-
34
+ },
26
35
  };
27
-
36
+ },
28
37
  };
29
-
@@ -3,77 +3,79 @@
3
3
  * @author Jon Lamendola
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
- var Sinks = [];
11
- function getSerialize (fn, decycle) {
12
- var seen = [], keys = [];
13
- decycle = decycle || function(key, value) {
14
- return '[Circular ' + getPath(value, seen, keys) + ']'
15
- };
16
- return function(key, value) {
17
- var ret = value;
12
+ const getPath = (value, seen, keys) => {
13
+ let index = seen.indexOf(value);
14
+ const path = [keys[index]];
15
+ for (index--; index >= 0; index--) {
16
+ if (seen[index][path[0]] === value) {
17
+ value = seen[index];
18
+ path.unshift(keys[index]);
19
+ }
20
+ }
21
+ return `~${path.join('.')}`;
22
+ };
23
+
24
+ const getSerialize = (fn, decycle) => {
25
+ const seen = [];
26
+ const keys = [];
27
+ decycle =
28
+ decycle ||
29
+ function (key, value) {
30
+ return `[Circular ${getPath(value, seen, keys)}]`;
31
+ };
32
+ return function (key, value) {
33
+ let ret = value;
18
34
  if (typeof value === 'object' && value) {
19
- if (seen.indexOf(value) !== -1)
35
+ if (seen.indexOf(value) !== -1) {
20
36
  ret = decycle(key, value);
21
- else {
37
+ } else {
22
38
  seen.push(value);
23
39
  keys.push(key);
24
40
  }
25
41
  }
26
- if (fn) ret = fn(key, ret);
27
- return ret;
28
- }
29
- }
30
-
31
- function getPath (value, seen, keys) {
32
- var index = seen.indexOf(value);
33
- var path = [ keys[index] ];
34
- for (index--; index >= 0; index--) {
35
- if (seen[index][ path[0] ] === value) {
36
- value = seen[index];
37
- path.unshift(keys[index]);
42
+ if (fn) {
43
+ ret = fn(key, ret);
38
44
  }
39
- }
40
- return '~' + path.join('.');
41
- }
45
+ return ret;
46
+ };
47
+ };
42
48
 
43
- function stringify(obj, fn, spaces, decycle) {
49
+ const stringify = (obj, fn, spaces, decycle) => {
44
50
  return JSON.stringify(obj, getSerialize(fn, decycle), spaces);
45
- }
46
-
47
- stringify.getSerialize = getSerialize;module.exports = function(context) {
48
-
49
- "use strict";
50
-
51
- var isChanged = false;
52
-
53
-
54
-
55
- return {
56
- "MemberExpression": function(node) {
57
-
58
- if (node.computed === true) {
59
- var token = context.getTokens(node)[0];
60
- if (node.property.type === 'Identifier') {
61
- if (node.parent.type === 'VariableDeclarator') {
62
- context.report(node, 'Variable Assigned to Object Injection Sink');
63
-
64
- } else if (node.parent.type === 'CallExpression') {
65
- // console.log(node.parent)
66
- context.report(node, 'Function Call Object Injection Sink');
67
- } else {
68
- context.report(node, 'Generic Object Injection Sink');
69
-
70
- }
71
-
72
- }
73
- }
51
+ };
74
52
 
53
+ stringify.getSerialize = getSerialize;
54
+ module.exports = {
55
+ meta: {
56
+ type: 'error',
57
+ docs: {
58
+ description: 'Detects "variable[key]" as a left- or right-hand assignment operand.',
59
+ category: 'Possible Security Vulnerability',
60
+ recommended: true,
61
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-object-injection.md',
62
+ },
63
+ },
64
+ create: function (context) {
65
+ return {
66
+ MemberExpression: function (node) {
67
+ if (node.computed === true) {
68
+ if (node.property.type === 'Identifier') {
69
+ if (node.parent.type === 'VariableDeclarator') {
70
+ context.report({ node: node, message: 'Variable Assigned to Object Injection Sink' });
71
+ } else if (node.parent.type === 'CallExpression') {
72
+ context.report({ node: node, message: 'Function Call Object Injection Sink' });
73
+ } else {
74
+ context.report({ node: node, message: 'Generic Object Injection Sink' });
75
75
  }
76
-
77
- };
78
- }
79
-
76
+ }
77
+ }
78
+ },
79
+ };
80
+ },
81
+ };
@@ -3,58 +3,56 @@
3
3
  * @author Adam Baldwin / Jon Lamendola
4
4
  */
5
5
 
6
+ 'use strict';
7
+
6
8
  //------------------------------------------------------------------------------
7
9
  // Rule Definition
8
10
  //------------------------------------------------------------------------------
9
11
 
10
- var keywords = '((' + [
11
- 'password',
12
- 'secret',
13
- 'api',
14
- 'apiKey',
15
- 'token',
16
- 'auth',
17
- 'pass',
18
- 'hash'
19
- ].join(')|(') + '))';
20
-
21
- var re = new RegExp('^' + keywords + '$', 'im');
12
+ const keywords = `((${['password', 'secret', 'api', 'apiKey', 'token', 'auth', 'pass', 'hash'].join(')|(')}))`;
22
13
 
23
- function containsKeyword (node) {
24
- if (node.type === 'Identifier') {
25
- if (re.test(node.name))
26
- return true;
27
- }
28
- return
29
- }
14
+ const re = new RegExp(`^${keywords}$`, 'im');
30
15
 
31
- module.exports = function(context) {
32
-
33
- "use strict";
16
+ const containsKeyword = (node) => {
17
+ if (node.type === 'Identifier') {
18
+ if (re.test(node.name)) {
19
+ return true;
20
+ }
21
+ }
22
+ return;
23
+ };
34
24
 
25
+ module.exports = {
26
+ meta: {
27
+ type: 'error',
28
+ docs: {
29
+ description: 'Detects insecure comparisons (`==`, `!=`, `!==` and `===`), which check input sequentially.',
30
+ category: 'Possible Security Vulnerability',
31
+ recommended: true,
32
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-possible-timing-attacks.md',
33
+ },
34
+ },
35
+ create: function (context) {
35
36
  return {
36
- "IfStatement": function(node) {
37
- if (node.test && node.test.type === 'BinaryExpression') {
38
- if (node.test.operator === '==' || node.test.operator === '===' || node.test.operator === '!=' || node.test.operator === '!==') {
39
-
40
- var token = context.getTokens(node)[0];
41
-
42
- if (node.test.left) {
43
- var left = containsKeyword(node.test.left);
44
- if (left) {
45
- return context.report(node, "Potential timing attack, left side: " + left);
46
- }
47
- }
37
+ IfStatement: function (node) {
38
+ if (node.test && node.test.type === 'BinaryExpression') {
39
+ if (node.test.operator === '==' || node.test.operator === '===' || node.test.operator === '!=' || node.test.operator === '!==') {
40
+ if (node.test.left) {
41
+ const left = containsKeyword(node.test.left);
42
+ if (left) {
43
+ return context.report({ node: node, message: `Potential timing attack, left side: ${left}` });
44
+ }
45
+ }
48
46
 
49
- if (node.test.right) {
50
- var right = containsKeyword(node.test.right);
51
- if (right) {
52
- return context.report(node, "Potential timing attack, right side: " + right);
53
- }
54
- }
55
- }
47
+ if (node.test.right) {
48
+ const right = containsKeyword(node.test.right);
49
+ if (right) {
50
+ return context.report({ node: node, message: `Potential timing attack, right side: ${right}` });
51
+ }
56
52
  }
53
+ }
57
54
  }
55
+ },
58
56
  };
59
-
57
+ },
60
58
  };
@@ -3,22 +3,29 @@
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 if "pseudoRandomBytes()" is in use, which might not give you the randomness you need and expect.',
17
+ category: 'Possible Security Vulnerability',
18
+ recommended: true,
19
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-pseudoRandomBytes.md',
20
+ },
21
+ },
22
+ create: function (context) {
14
23
  return {
15
- "MemberExpression": function (node) {
16
- if (node.property.name === 'pseudoRandomBytes') {
17
- var token = context.getTokens(node)[0];
18
- return context.report(node, 'Found crypto.pseudoRandomBytes which does not produce cryptographically strong numbers');
19
- }
24
+ MemberExpression: function (node) {
25
+ if (node.property.name === 'pseudoRandomBytes') {
26
+ return context.report({ node: node, message: 'Found crypto.pseudoRandomBytes which does not produce cryptographically strong numbers' });
20
27
  }
21
-
28
+ },
22
29
  };
23
-
30
+ },
24
31
  };
@@ -1,37 +1,50 @@
1
- var safe = require('safe-regex');
2
1
  /**
3
2
  * Check if the regex is evil or not using the safe-regex module
4
3
  * @author Adam Baldwin
5
4
  */
6
5
 
6
+ 'use strict';
7
+
8
+ //-----------------------------------------------------------------------------
9
+ // Requirements
10
+ //-----------------------------------------------------------------------------
11
+
12
+ const safe = require('safe-regex');
13
+
7
14
  //------------------------------------------------------------------------------
8
15
  // Rule Definition
9
16
  //------------------------------------------------------------------------------
10
17
 
11
- module.exports = function(context) {
12
-
13
- "use strict";
14
-
18
+ module.exports = {
19
+ meta: {
20
+ type: 'error',
21
+ docs: {
22
+ description: 'Detects potentially unsafe regular expressions, which may take a very long time to run, blocking the event loop.',
23
+ category: 'Possible Security Vulnerability',
24
+ recommended: true,
25
+ url: 'https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-unsafe-regex.md',
26
+ },
27
+ },
28
+ create: function (context) {
15
29
  return {
16
- "Literal": function(node) {
17
- var token = context.getTokens(node)[0],
18
- nodeType = token.type,
19
- nodeValue = token.value;
30
+ Literal: function (node) {
31
+ const token = context.getSourceCode().getTokens(node)[0];
32
+ const nodeType = token.type;
33
+ const nodeValue = token.value;
20
34
 
21
- if (nodeType === "RegularExpression") {
22
- if (!safe(nodeValue)) {
23
- context.report(node, "Unsafe Regular Expression");
24
- }
25
- }
26
- },
27
- "NewExpression": function(node) {
28
- if (node.callee.name == "RegExp" && node.arguments && node.arguments.length > 0 && node.arguments[0].type == "Literal") {
29
- if (!safe(node.arguments[0].value)) {
30
- context.report(node, "Unsafe Regular Expression (new RegExp)");
31
- }
32
- }
35
+ if (nodeType === 'RegularExpression') {
36
+ if (!safe(nodeValue)) {
37
+ context.report({ node: node, message: 'Unsafe Regular Expression' });
38
+ }
39
+ }
40
+ },
41
+ NewExpression: function (node) {
42
+ if (node.callee.name === 'RegExp' && node.arguments && node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
43
+ if (!safe(node.arguments[0].value)) {
44
+ context.report({ node: node, message: 'Unsafe Regular Expression (new RegExp)' });
45
+ }
33
46
  }
47
+ },
34
48
  };
35
-
49
+ },
36
50
  };
37
-
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const RuleTester = require('eslint').RuleTester;
4
+ const tester = new RuleTester();
5
+
6
+ const ruleName = 'detect-bidi-characters';
7
+ const Rule = require(`../rules/${ruleName}`);
8
+
9
+ tester.run(ruleName, Rule, {
10
+ valid: [
11
+ {
12
+ code: `
13
+ var accessLevel = "user";
14
+ if (accessLevel != "user") { // Check if admin
15
+ console.log("You are an admin.");
16
+ }
17
+ `,
18
+ },
19
+ ],
20
+ invalid: [
21
+ {
22
+ code: `
23
+ var accessLevel = "user";
24
+ if (accessLevel != "user‮ ⁦// Check if admin⁩ ⁦") {
25
+ console.log("You are an admin.");
26
+ }
27
+ `,
28
+ errors: [
29
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this code/i, line: 3, endLine: 3, column: 31, endColumn: 32 },
30
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this code/i, line: 3, endLine: 3, column: 33, endColumn: 34 },
31
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this code/i, line: 3, endLine: 3, column: 51, endColumn: 52 },
32
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this code/i, line: 3, endLine: 3, column: 53, endColumn: 54 },
33
+ ],
34
+ },
35
+ ],
36
+ });
37
+
38
+ tester.run(`${ruleName} in comment-line`, Rule, {
39
+ valid: [
40
+ {
41
+ code: `
42
+ var isAdmin = false;
43
+ /* begin admins only */ if (isAdmin) {
44
+ console.log("You are an admin.");
45
+ /* end admins only */ }
46
+ `,
47
+ },
48
+ ],
49
+ invalid: [
50
+ {
51
+ code: `
52
+ var isAdmin = false;
53
+ /*‮ } ⁦if (isAdmin)⁩ ⁦ begin admins only */
54
+ console.log("You are an admin.");
55
+ /* end admins only ‮
56
+ ⁦*/
57
+ /* end admins only ‮
58
+ { ⁦*/
59
+ `,
60
+ errors: [
61
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 3, endLine: 3, column: 9, endColumn: 10 },
62
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 3, endLine: 3, column: 13, endColumn: 14 },
63
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 3, endLine: 3, column: 26, endColumn: 27 },
64
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 3, endLine: 3, column: 28, endColumn: 29 },
65
+
66
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 5, endLine: 5, column: 26, endColumn: 27 },
67
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 6, endLine: 6, column: 1, endColumn: 2 },
68
+
69
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 7, endLine: 7, column: 26, endColumn: 27 },
70
+ { message: /Detected potential trojan source attack with unicode bidi introduced in this comment/i, line: 8, endLine: 8, column: 4, endColumn: 5 },
71
+ ],
72
+ },
73
+ ],
74
+ });