eslint-plugin-node-security 4.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +50 -0
  3. package/package.json +79 -0
  4. package/src/index.d.ts +10 -0
  5. package/src/index.js +118 -0
  6. package/src/index.js.map +1 -0
  7. package/src/rules/detect-child-process/index.d.ts +30 -0
  8. package/src/rules/detect-child-process/index.js +535 -0
  9. package/src/rules/detect-child-process/index.js.map +1 -0
  10. package/src/rules/detect-eval-with-expression/index.d.ts +28 -0
  11. package/src/rules/detect-eval-with-expression/index.js +398 -0
  12. package/src/rules/detect-eval-with-expression/index.js.map +1 -0
  13. package/src/rules/detect-non-literal-fs-filename/index.d.ts +26 -0
  14. package/src/rules/detect-non-literal-fs-filename/index.js +460 -0
  15. package/src/rules/detect-non-literal-fs-filename/index.js.map +1 -0
  16. package/src/rules/detect-suspicious-dependencies/index.d.ts +12 -0
  17. package/src/rules/detect-suspicious-dependencies/index.js +77 -0
  18. package/src/rules/detect-suspicious-dependencies/index.js.map +1 -0
  19. package/src/rules/lock-file/index.d.ts +13 -0
  20. package/src/rules/lock-file/index.js +94 -0
  21. package/src/rules/lock-file/index.js.map +1 -0
  22. package/src/rules/no-arbitrary-file-access/index.d.ts +12 -0
  23. package/src/rules/no-arbitrary-file-access/index.js +201 -0
  24. package/src/rules/no-arbitrary-file-access/index.js.map +1 -0
  25. package/src/rules/no-buffer-overread/index.d.ts +39 -0
  26. package/src/rules/no-buffer-overread/index.js +612 -0
  27. package/src/rules/no-buffer-overread/index.js.map +1 -0
  28. package/src/rules/no-cryptojs/index.d.ts +24 -0
  29. package/src/rules/no-cryptojs/index.js +104 -0
  30. package/src/rules/no-cryptojs/index.js.map +1 -0
  31. package/src/rules/no-cryptojs-weak-random/index.d.ts +24 -0
  32. package/src/rules/no-cryptojs-weak-random/index.js +112 -0
  33. package/src/rules/no-cryptojs-weak-random/index.js.map +1 -0
  34. package/src/rules/no-data-in-temp-storage/index.d.ts +14 -0
  35. package/src/rules/no-data-in-temp-storage/index.js +99 -0
  36. package/src/rules/no-data-in-temp-storage/index.js.map +1 -0
  37. package/src/rules/no-deprecated-cipher-method/index.d.ts +23 -0
  38. package/src/rules/no-deprecated-cipher-method/index.js +118 -0
  39. package/src/rules/no-deprecated-cipher-method/index.js.map +1 -0
  40. package/src/rules/no-dynamic-dependency-loading/index.d.ts +12 -0
  41. package/src/rules/no-dynamic-dependency-loading/index.js +55 -0
  42. package/src/rules/no-dynamic-dependency-loading/index.js.map +1 -0
  43. package/src/rules/no-dynamic-require/index.d.ts +21 -0
  44. package/src/rules/no-dynamic-require/index.js +122 -0
  45. package/src/rules/no-dynamic-require/index.js.map +1 -0
  46. package/src/rules/no-ecb-mode/index.d.ts +23 -0
  47. package/src/rules/no-ecb-mode/index.js +113 -0
  48. package/src/rules/no-ecb-mode/index.js.map +1 -0
  49. package/src/rules/no-insecure-key-derivation/index.d.ts +24 -0
  50. package/src/rules/no-insecure-key-derivation/index.js +116 -0
  51. package/src/rules/no-insecure-key-derivation/index.js.map +1 -0
  52. package/src/rules/no-insecure-rsa-padding/index.d.ts +24 -0
  53. package/src/rules/no-insecure-rsa-padding/index.js +110 -0
  54. package/src/rules/no-insecure-rsa-padding/index.js.map +1 -0
  55. package/src/rules/no-pii-in-logs/index.d.ts +12 -0
  56. package/src/rules/no-pii-in-logs/index.js +74 -0
  57. package/src/rules/no-pii-in-logs/index.js.map +1 -0
  58. package/src/rules/no-self-signed-certs/index.d.ts +23 -0
  59. package/src/rules/no-self-signed-certs/index.js +116 -0
  60. package/src/rules/no-self-signed-certs/index.js.map +1 -0
  61. package/src/rules/no-sha1-hash/index.d.ts +24 -0
  62. package/src/rules/no-sha1-hash/index.js +128 -0
  63. package/src/rules/no-sha1-hash/index.js.map +1 -0
  64. package/src/rules/no-static-iv/index.d.ts +23 -0
  65. package/src/rules/no-static-iv/index.js +147 -0
  66. package/src/rules/no-static-iv/index.js.map +1 -0
  67. package/src/rules/no-timing-unsafe-compare/index.d.ts +23 -0
  68. package/src/rules/no-timing-unsafe-compare/index.js +114 -0
  69. package/src/rules/no-timing-unsafe-compare/index.js.map +1 -0
  70. package/src/rules/no-toctou-vulnerability/index.d.ts +26 -0
  71. package/src/rules/no-toctou-vulnerability/index.js +214 -0
  72. package/src/rules/no-toctou-vulnerability/index.js.map +1 -0
  73. package/src/rules/no-unsafe-dynamic-require/index.d.ts +19 -0
  74. package/src/rules/no-unsafe-dynamic-require/index.js +112 -0
  75. package/src/rules/no-unsafe-dynamic-require/index.js.map +1 -0
  76. package/src/rules/no-weak-cipher-algorithm/index.d.ts +25 -0
  77. package/src/rules/no-weak-cipher-algorithm/index.js +190 -0
  78. package/src/rules/no-weak-cipher-algorithm/index.js.map +1 -0
  79. package/src/rules/no-weak-hash-algorithm/index.d.ts +25 -0
  80. package/src/rules/no-weak-hash-algorithm/index.js +218 -0
  81. package/src/rules/no-weak-hash-algorithm/index.js.map +1 -0
  82. package/src/rules/no-zip-slip/index.d.ts +35 -0
  83. package/src/rules/no-zip-slip/index.js +451 -0
  84. package/src/rules/no-zip-slip/index.js.map +1 -0
  85. package/src/rules/prefer-native-crypto/index.d.ts +23 -0
  86. package/src/rules/prefer-native-crypto/index.js +124 -0
  87. package/src/rules/prefer-native-crypto/index.js.map +1 -0
  88. package/src/rules/require-dependency-integrity/index.d.ts +12 -0
  89. package/src/rules/require-dependency-integrity/index.js +70 -0
  90. package/src/rules/require-dependency-integrity/index.js.map +1 -0
  91. package/src/rules/require-secure-credential-storage/index.d.ts +12 -0
  92. package/src/rules/require-secure-credential-storage/index.js +54 -0
  93. package/src/rules/require-secure-credential-storage/index.js.map +1 -0
  94. package/src/rules/require-secure-deletion/index.d.ts +12 -0
  95. package/src/rules/require-secure-deletion/index.js +46 -0
  96. package/src/rules/require-secure-deletion/index.js.map +1 -0
  97. package/src/rules/require-storage-encryption/index.d.ts +12 -0
  98. package/src/rules/require-storage-encryption/index.js +54 -0
  99. package/src/rules/require-storage-encryption/index.js.map +1 -0
  100. package/src/types/index.d.ts +24 -0
  101. package/src/types/index.js +8 -0
  102. package/src/types/index.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-pii-in-logs/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH;;;;GAIG;AAEH,4DAAsG;AAUzF,QAAA,WAAW,GAAG,IAAA,0BAAU,EAA0B;IAC7D,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,wDAAwD;SACtE;QACD,QAAQ,EAAE;YACR,iBAAiB,EAAE,IAAA,gCAAgB,EAAC;gBAClC,IAAI,EAAE,4BAAY,CAAC,QAAQ;gBAC3B,SAAS,EAAE,oBAAoB;gBAC/B,GAAG,EAAE,SAAS;gBACd,WAAW,EAAE,2FAA2F;gBACxG,QAAQ,EAAE,MAAM;gBAChB,GAAG,EAAE,mCAAmC;gBACxC,iBAAiB,EAAE,iDAAiD;aACrE,CAAC;SACH;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,SAAS,MAAM,CAAC,IAAmB;YACjC,OAAO,CAAC,MAAM,CAAC;gBACb,IAAI;gBACJ,SAAS,EAAE,mBAAmB;aAC/B,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,cAAc,CAAC,IAA6B;gBAC1C,qCAAqC;gBACrC,IACE,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,8BAAc,CAAC,gBAAgB;oBACpD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU;oBACrD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS;oBACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU;oBACvD,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EACpE,CAAC;oBACD,kDAAkD;oBAClD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;wBACjC,IACE,GAAG,CAAC,IAAI,KAAK,8BAAc,CAAC,gBAAgB;4BAC5C,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU,EAC/C,CAAC;4BACD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;4BACjD,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;4BAErE,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gCAC7C,MAAM,CAAC,IAAI,CAAC,CAAC;4BACf,CAAC;wBACH,CAAC;wBAED,uCAAuC;wBACvC,IAAI,GAAG,CAAC,IAAI,KAAK,8BAAc,CAAC,OAAO,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;4BACzE,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;4BACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gCACnF,MAAM,CAAC,IAAI,CAAC,CAAC;4BACf,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2025 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * ESLint Rule: no-self-signed-certs
8
+ * Detects rejectUnauthorized: false in TLS options
9
+ * CWE-295: Improper Certificate Validation
10
+ *
11
+ * @see https://cwe.mitre.org/data/definitions/295.html
12
+ */
13
+ import type { TSESLint } from '@interlace/eslint-devkit';
14
+ type MessageIds = 'insecureTls' | 'enableValidation';
15
+ export interface Options {
16
+ /** Allow in test/development files. Default: false */
17
+ allowInTests?: boolean;
18
+ }
19
+ type RuleOptions = [Options?];
20
+ export declare const noSelfSignedCerts: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
21
+ name: string;
22
+ };
23
+ export type { Options as NoSelfSignedCertsOptions };
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.noSelfSignedCerts = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ exports.noSelfSignedCerts = (0, eslint_devkit_1.createRule)({
11
+ name: 'no-self-signed-certs',
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'Disallow rejectUnauthorized: false in TLS options',
16
+ },
17
+ hasSuggestions: true,
18
+ messages: {
19
+ insecureTls: (0, eslint_devkit_1.formatLLMMessage)({
20
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
21
+ issueName: 'TLS certificate validation disabled',
22
+ cwe: 'CWE-295',
23
+ description: 'rejectUnauthorized: false disables TLS certificate validation, enabling man-in-the-middle attacks. Any certificate will be accepted, including self-signed and expired ones.',
24
+ severity: 'CRITICAL',
25
+ fix: 'Remove rejectUnauthorized: false or set it to true',
26
+ documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html',
27
+ }),
28
+ enableValidation: (0, eslint_devkit_1.formatLLMMessage)({
29
+ icon: eslint_devkit_1.MessageIcons.INFO,
30
+ issueName: 'Enable certificate validation',
31
+ description: 'Enable proper TLS certificate validation',
32
+ severity: 'LOW',
33
+ fix: 'rejectUnauthorized: true (or remove the property)',
34
+ documentationLink: 'https://nodejs.org/api/tls.html#tlsconnectoptions-callback',
35
+ }),
36
+ },
37
+ schema: [
38
+ {
39
+ type: 'object',
40
+ properties: {
41
+ allowInTests: {
42
+ type: 'boolean',
43
+ default: false,
44
+ description: 'Allow in test files',
45
+ },
46
+ },
47
+ additionalProperties: false,
48
+ },
49
+ ],
50
+ },
51
+ defaultOptions: [
52
+ {
53
+ allowInTests: false,
54
+ },
55
+ ],
56
+ create(context, [options = {}]) {
57
+ const { allowInTests = false } = options;
58
+ const filename = context.filename;
59
+ const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
60
+ function checkProperty(node) {
61
+ if (isTestFile)
62
+ return;
63
+ // Check for rejectUnauthorized: false
64
+ if (node.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
65
+ node.key.name === 'rejectUnauthorized' &&
66
+ node.value.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
67
+ node.value.value === false) {
68
+ context.report({
69
+ node,
70
+ messageId: 'insecureTls',
71
+ suggest: [
72
+ {
73
+ messageId: 'enableValidation',
74
+ fix: (fixer) => {
75
+ return fixer.replaceText(node.value, 'true');
76
+ },
77
+ },
78
+ ],
79
+ });
80
+ }
81
+ }
82
+ function checkAssignmentExpression(node) {
83
+ if (isTestFile)
84
+ return;
85
+ // Check for process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
86
+ if (node.left.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
87
+ node.left.object.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
88
+ node.left.object.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
89
+ node.left.object.object.name === 'process' &&
90
+ node.left.object.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
91
+ node.left.object.property.name === 'env' &&
92
+ node.left.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
93
+ node.left.property.name === 'NODE_TLS_REJECT_UNAUTHORIZED') {
94
+ if (node.right.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
95
+ (node.right.value === '0' || node.right.value === 0)) {
96
+ /* c8 ignore next 10 -- process.env.NODE_TLS_REJECT_UNAUTHORIZED pattern requires complex AST */
97
+ context.report({
98
+ node,
99
+ messageId: 'insecureTls',
100
+ suggest: [
101
+ {
102
+ messageId: 'enableValidation',
103
+ fix: () => null, // Should remove the assignment
104
+ },
105
+ ],
106
+ });
107
+ }
108
+ }
109
+ }
110
+ return {
111
+ Property: checkProperty,
112
+ AssignmentExpression: checkAssignmentExpression,
113
+ };
114
+ },
115
+ });
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-self-signed-certs/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright (c) 2025 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * ESLint Rule: no-sha1-hash
8
+ * Detects sha1() usage from crypto-hash package
9
+ * CWE-327: SHA-1 is cryptographically broken
10
+ *
11
+ * The crypto-hash package itself warns: "SHA-1 is insecure and should not be used"
12
+ * @see https://www.npmjs.com/package/crypto-hash
13
+ */
14
+ import type { TSESLint } from '@interlace/eslint-devkit';
15
+ type MessageIds = 'sha1Deprecated' | 'useSha256' | 'useSha512';
16
+ export interface Options {
17
+ /** Allow SHA1 in test files. Default: false */
18
+ allowInTests?: boolean;
19
+ }
20
+ type RuleOptions = [Options?];
21
+ export declare const noSha1Hash: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
22
+ name: string;
23
+ };
24
+ export type { Options as NoSha1HashOptions };
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.noSha1Hash = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ exports.noSha1Hash = (0, eslint_devkit_1.createRule)({
11
+ name: 'no-sha1-hash',
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'Disallow sha1() from crypto-hash package (use sha256 or sha512)',
16
+ },
17
+ hasSuggestions: true,
18
+ messages: {
19
+ sha1Deprecated: (0, eslint_devkit_1.formatLLMMessage)({
20
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
21
+ issueName: 'SHA-1 hash detected',
22
+ cwe: 'CWE-327',
23
+ description: 'SHA-1 is cryptographically broken and should not be used. The crypto-hash package itself warns against using sha1().',
24
+ severity: 'HIGH',
25
+ fix: 'Use sha256() or sha512() instead',
26
+ documentationLink: 'https://shattered.io/',
27
+ }),
28
+ useSha256: (0, eslint_devkit_1.formatLLMMessage)({
29
+ icon: eslint_devkit_1.MessageIcons.INFO,
30
+ issueName: 'Use sha256',
31
+ description: 'SHA-256 provides adequate security for most use cases',
32
+ severity: 'LOW',
33
+ fix: "import { sha256 } from 'crypto-hash'; await sha256(data)",
34
+ documentationLink: 'https://www.npmjs.com/package/crypto-hash',
35
+ }),
36
+ useSha512: (0, eslint_devkit_1.formatLLMMessage)({
37
+ icon: eslint_devkit_1.MessageIcons.INFO,
38
+ issueName: 'Use sha512',
39
+ description: 'SHA-512 provides stronger security with longer hash',
40
+ severity: 'LOW',
41
+ fix: "import { sha512 } from 'crypto-hash'; await sha512(data)",
42
+ documentationLink: 'https://www.npmjs.com/package/crypto-hash',
43
+ }),
44
+ },
45
+ schema: [
46
+ {
47
+ type: 'object',
48
+ properties: {
49
+ allowInTests: {
50
+ type: 'boolean',
51
+ default: false,
52
+ description: 'Allow SHA1 in test files',
53
+ },
54
+ },
55
+ additionalProperties: false,
56
+ },
57
+ ],
58
+ },
59
+ defaultOptions: [
60
+ {
61
+ allowInTests: false,
62
+ },
63
+ ],
64
+ create(context, [options = {}]) {
65
+ const { allowInTests = false } = options;
66
+ const filename = context.filename;
67
+ const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
68
+ // Track if sha1 was imported from crypto-hash
69
+ let sha1ImportedFromCryptoHash = false;
70
+ return {
71
+ ImportDeclaration(node) {
72
+ if (isTestFile)
73
+ return;
74
+ // Check for: import { sha1 } from 'crypto-hash'
75
+ if (node.source.value === 'crypto-hash') {
76
+ for (const specifier of node.specifiers) {
77
+ if (specifier.type === eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier &&
78
+ specifier.imported.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
79
+ specifier.imported.name === 'sha1') {
80
+ sha1ImportedFromCryptoHash = true;
81
+ context.report({
82
+ node: specifier,
83
+ messageId: 'sha1Deprecated',
84
+ suggest: [
85
+ {
86
+ messageId: 'useSha256',
87
+ fix: (fixer) => {
88
+ return fixer.replaceText(specifier.imported, 'sha256');
89
+ },
90
+ },
91
+ {
92
+ messageId: 'useSha512',
93
+ fix: (fixer) => {
94
+ return fixer.replaceText(specifier.imported, 'sha512');
95
+ },
96
+ },
97
+ ],
98
+ });
99
+ }
100
+ }
101
+ }
102
+ },
103
+ CallExpression(node) {
104
+ if (isTestFile)
105
+ return;
106
+ // Check for sha1() call if imported from crypto-hash
107
+ if (sha1ImportedFromCryptoHash &&
108
+ node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
109
+ node.callee.name === 'sha1') {
110
+ /* c8 ignore next 12 -- sha1() call pattern requires specific import tracking */
111
+ context.report({
112
+ node,
113
+ messageId: 'sha1Deprecated',
114
+ suggest: [
115
+ {
116
+ messageId: 'useSha256',
117
+ fix: (fixer) => {
118
+ return fixer.replaceText(node.callee, 'sha256');
119
+ },
120
+ },
121
+ ],
122
+ });
123
+ }
124
+ },
125
+ };
126
+ },
127
+ });
128
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-sha1-hash/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2025 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * ESLint Rule: no-static-iv
8
+ * Detects use of hardcoded or reused initialization vectors (IVs)
9
+ * CWE-329: Not Using an Unpredictable IV with CBC Mode
10
+ *
11
+ * @see https://cwe.mitre.org/data/definitions/329.html
12
+ */
13
+ import type { TSESLint } from '@interlace/eslint-devkit';
14
+ type MessageIds = 'staticIv' | 'useRandomBytes';
15
+ export interface Options {
16
+ /** Allow static IVs in test files. Default: false */
17
+ allowInTests?: boolean;
18
+ }
19
+ type RuleOptions = [Options?];
20
+ export declare const noStaticIv: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
21
+ name: string;
22
+ };
23
+ export type { Options as NoStaticIvOptions };
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.noStaticIv = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ /**
11
+ * Patterns that indicate a hardcoded IV
12
+ */
13
+ const STATIC_IV_PATTERNS = [
14
+ /^[0-9a-f]+$/i, // Hex string
15
+ /^[A-Za-z0-9+/]+=*$/, // Base64
16
+ ];
17
+ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
18
+ name: 'no-static-iv',
19
+ meta: {
20
+ type: 'problem',
21
+ docs: {
22
+ description: 'Disallow static or hardcoded initialization vectors (IVs)',
23
+ },
24
+ hasSuggestions: false,
25
+ messages: {
26
+ staticIv: (0, eslint_devkit_1.formatLLMMessage)({
27
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
28
+ issueName: 'Static IV detected',
29
+ cwe: 'CWE-329',
30
+ description: 'Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.',
31
+ severity: 'HIGH',
32
+ fix: 'Generate IV dynamically using crypto.randomBytes(16)',
33
+ documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors',
34
+ }),
35
+ useRandomBytes: (0, eslint_devkit_1.formatLLMMessage)({
36
+ icon: eslint_devkit_1.MessageIcons.INFO,
37
+ issueName: 'Use randomBytes',
38
+ description: 'Generate IV dynamically for each encryption operation',
39
+ severity: 'LOW',
40
+ fix: 'const iv = crypto.randomBytes(16);',
41
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptorandombytessize-callback',
42
+ }),
43
+ },
44
+ schema: [
45
+ {
46
+ type: 'object',
47
+ properties: {
48
+ allowInTests: {
49
+ type: 'boolean',
50
+ default: false,
51
+ description: 'Allow static IVs in test files',
52
+ },
53
+ },
54
+ additionalProperties: false,
55
+ },
56
+ ],
57
+ },
58
+ defaultOptions: [
59
+ {
60
+ allowInTests: false,
61
+ },
62
+ ],
63
+ create(context, [options = {}]) {
64
+ const { allowInTests = false } = options;
65
+ const filename = context.filename;
66
+ const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
67
+ // Track variables assigned from randomBytes
68
+ const randomIvVariables = new Set();
69
+ function checkVariableDeclarator(node) {
70
+ // Track: const iv = crypto.randomBytes(16)
71
+ if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
72
+ node.init?.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
73
+ const init = node.init;
74
+ if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
75
+ init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
76
+ init.callee.property.name === 'randomBytes') {
77
+ randomIvVariables.add(node.id.name);
78
+ }
79
+ /* c8 ignore next 4 -- standalone randomBytes call requires specific import pattern */
80
+ if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
81
+ init.callee.name === 'randomBytes') {
82
+ randomIvVariables.add(node.id.name);
83
+ }
84
+ }
85
+ }
86
+ function checkCallExpression(node) {
87
+ if (isTestFile)
88
+ return;
89
+ // Check for createCipheriv calls
90
+ const isCipherivCall = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
91
+ node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
92
+ (node.callee.property.name === 'createCipheriv' || node.callee.property.name === 'createDecipheriv')) ||
93
+ (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
94
+ (node.callee.name === 'createCipheriv' || node.callee.name === 'createDecipheriv'));
95
+ if (isCipherivCall && node.arguments.length >= 3) {
96
+ const ivArg = node.arguments[2];
97
+ checkIvArgument(ivArg);
98
+ }
99
+ }
100
+ function checkIvArgument(ivArg) {
101
+ // Check for string literal IV
102
+ if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof ivArg.value === 'string') {
103
+ const value = ivArg.value;
104
+ if (STATIC_IV_PATTERNS.some(p => p.test(value)) || value.length >= 8) {
105
+ reportStaticIv(ivArg);
106
+ }
107
+ }
108
+ // Check for Buffer.from('static')
109
+ if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
110
+ ivArg.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
111
+ ivArg.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
112
+ ivArg.callee.object.name === 'Buffer' &&
113
+ ivArg.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
114
+ (ivArg.callee.property.name === 'from' || ivArg.callee.property.name === 'alloc')) {
115
+ const firstArg = ivArg.arguments[0];
116
+ if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
117
+ reportStaticIv(ivArg);
118
+ }
119
+ /* c8 ignore next 7 -- ArrayExpression with all numeric literals is rare pattern */
120
+ // Check for new Uint8Array([...])
121
+ if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.ArrayExpression) {
122
+ const allLiterals = firstArg.elements.every((el) => el?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof el.value === 'number');
123
+ if (allLiterals) {
124
+ reportStaticIv(ivArg);
125
+ }
126
+ }
127
+ }
128
+ // Check for variable reference that's NOT from randomBytes
129
+ if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
130
+ // If we've tracked this variable as coming from randomBytes, it's OK
131
+ // Otherwise, we can't be sure - only flag if we have evidence it's static
132
+ // For now, we don't report variables as we can't always determine their source
133
+ }
134
+ }
135
+ function reportStaticIv(node) {
136
+ context.report({
137
+ node,
138
+ messageId: 'staticIv',
139
+ });
140
+ }
141
+ return {
142
+ VariableDeclarator: checkVariableDeclarator,
143
+ CallExpression: checkCallExpression,
144
+ };
145
+ },
146
+ });
147
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-static-iv/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2025 Ofri Peretz
3
+ * Licensed under the MIT License. Use of this source code is governed by the
4
+ * MIT license that can be found in the LICENSE file.
5
+ */
6
+ /**
7
+ * ESLint Rule: no-timing-unsafe-compare
8
+ * Detects === comparison of secrets, suggest crypto.timingSafeEqual()
9
+ * CWE-208: Observable Timing Discrepancy
10
+ *
11
+ * @see https://cwe.mitre.org/data/definitions/208.html
12
+ */
13
+ import type { TSESLint } from '@interlace/eslint-devkit';
14
+ type MessageIds = 'timingUnsafeCompare' | 'useTimingSafeEqual';
15
+ export interface Options {
16
+ /** Variable name patterns that indicate secrets. Default: ['token', 'secret', 'key', 'password', 'hash', 'signature', 'mac', 'hmac', 'digest', 'apiKey', 'api_key'] */
17
+ secretPatterns?: string[];
18
+ }
19
+ type RuleOptions = [Options?];
20
+ export declare const noTimingUnsafeCompare: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
21
+ name: string;
22
+ };
23
+ export type { Options as NoTimingUnsafeCompareOptions };
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2025 Ofri Peretz
4
+ * Licensed under the MIT License. Use of this source code is governed by the
5
+ * MIT license that can be found in the LICENSE file.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.noTimingUnsafeCompare = void 0;
9
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
+ const DEFAULT_SECRET_PATTERNS = [
11
+ // Common secret names (camelCase, snake_case, kebab-case)
12
+ 'token', 'secret', 'key', 'password', 'hash', 'signature',
13
+ 'mac', 'hmac', 'digest', 'apiKey', 'api_key', 'api-key',
14
+ 'auth', 'credential', 'bearer', 'jwt', 'csrf', 'nonce',
15
+ // PII and sensitive data patterns
16
+ 'ssn', 'social_security', 'social-security',
17
+ 'pii', 'private_key', 'private-key', 'privateKey',
18
+ 'access_token', 'access-token', 'accessToken',
19
+ 'refresh_token', 'refresh-token', 'refreshToken',
20
+ 'session_id', 'session-id', 'sessionId',
21
+ 'auth_token', 'auth-token', 'authToken',
22
+ 'encryption_key', 'encryption-key', 'encryptionKey',
23
+ ];
24
+ exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
25
+ name: 'no-timing-unsafe-compare',
26
+ meta: {
27
+ type: 'problem',
28
+ docs: {
29
+ description: 'Disallow timing-unsafe comparison of secrets',
30
+ },
31
+ hasSuggestions: true,
32
+ messages: {
33
+ timingUnsafeCompare: (0, eslint_devkit_1.formatLLMMessage)({
34
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
35
+ issueName: 'Timing-unsafe comparison',
36
+ cwe: 'CWE-208',
37
+ description: 'Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.',
38
+ severity: 'HIGH',
39
+ fix: 'Use crypto.timingSafeEqual() for constant-time comparison',
40
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
41
+ }),
42
+ useTimingSafeEqual: (0, eslint_devkit_1.formatLLMMessage)({
43
+ icon: eslint_devkit_1.MessageIcons.INFO,
44
+ issueName: 'Use timingSafeEqual',
45
+ description: 'Use constant-time comparison to prevent timing attacks',
46
+ severity: 'LOW',
47
+ fix: 'crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
48
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
49
+ }),
50
+ },
51
+ schema: [
52
+ {
53
+ type: 'object',
54
+ properties: {
55
+ secretPatterns: {
56
+ type: 'array',
57
+ items: { type: 'string' },
58
+ default: DEFAULT_SECRET_PATTERNS,
59
+ description: 'Variable name patterns that indicate secrets',
60
+ },
61
+ },
62
+ additionalProperties: false,
63
+ },
64
+ ],
65
+ },
66
+ defaultOptions: [
67
+ {
68
+ secretPatterns: DEFAULT_SECRET_PATTERNS,
69
+ },
70
+ ],
71
+ create(context, [options = {}]) {
72
+ const { secretPatterns = DEFAULT_SECRET_PATTERNS } = options;
73
+ const patterns = secretPatterns.map(p => new RegExp(p, 'i'));
74
+ function isSecretIdentifier(node) {
75
+ if (node.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
76
+ return patterns.some(p => p.test(node.name));
77
+ }
78
+ if (node.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
79
+ const prop = node.property;
80
+ if (prop.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
81
+ return patterns.some(p => p.test(prop.name));
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ function checkBinaryExpression(node) {
87
+ // Check for === or == comparisons
88
+ if (node.operator !== '===' && node.operator !== '==' &&
89
+ node.operator !== '!==' && node.operator !== '!=') {
90
+ return;
91
+ }
92
+ // Check if either side looks like a secret
93
+ const leftIsSecret = isSecretIdentifier(node.left);
94
+ const rightIsSecret = isSecretIdentifier(node.right);
95
+ if (leftIsSecret || rightIsSecret) {
96
+ /* c8 ignore next 11 -- suggestions with fix: () => null cannot be tested with RuleTester */
97
+ context.report({
98
+ node,
99
+ messageId: 'timingUnsafeCompare',
100
+ suggest: [
101
+ {
102
+ messageId: 'useTimingSafeEqual',
103
+ fix: () => null, // Complex refactoring
104
+ },
105
+ ],
106
+ });
107
+ }
108
+ }
109
+ return {
110
+ BinaryExpression: checkBinaryExpression,
111
+ };
112
+ },
113
+ });
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-timing-unsafe-compare/index.ts"],"names":[],"mappings":"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"}