eslint-plugin-secure-coding 1.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 (155) hide show
  1. package/AGENTS.md +196 -0
  2. package/CHANGELOG.md +105 -0
  3. package/LICENSE +23 -0
  4. package/README.md +377 -0
  5. package/package.json +80 -0
  6. package/src/index.d.ts +32 -0
  7. package/src/index.js +345 -0
  8. package/src/index.js.map +1 -0
  9. package/src/rules/security/database-injection.d.ts +13 -0
  10. package/src/rules/security/database-injection.js +407 -0
  11. package/src/rules/security/database-injection.js.map +1 -0
  12. package/src/rules/security/detect-child-process.d.ts +11 -0
  13. package/src/rules/security/detect-child-process.js +460 -0
  14. package/src/rules/security/detect-child-process.js.map +1 -0
  15. package/src/rules/security/detect-eval-with-expression.d.ts +9 -0
  16. package/src/rules/security/detect-eval-with-expression.js +393 -0
  17. package/src/rules/security/detect-eval-with-expression.js.map +1 -0
  18. package/src/rules/security/detect-non-literal-fs-filename.d.ts +7 -0
  19. package/src/rules/security/detect-non-literal-fs-filename.js +322 -0
  20. package/src/rules/security/detect-non-literal-fs-filename.js.map +1 -0
  21. package/src/rules/security/detect-non-literal-regexp.d.ts +9 -0
  22. package/src/rules/security/detect-non-literal-regexp.js +387 -0
  23. package/src/rules/security/detect-non-literal-regexp.js.map +1 -0
  24. package/src/rules/security/detect-object-injection.d.ts +11 -0
  25. package/src/rules/security/detect-object-injection.js +411 -0
  26. package/src/rules/security/detect-object-injection.js.map +1 -0
  27. package/src/rules/security/no-buffer-overread.d.ts +14 -0
  28. package/src/rules/security/no-buffer-overread.js +519 -0
  29. package/src/rules/security/no-buffer-overread.js.map +1 -0
  30. package/src/rules/security/no-clickjacking.d.ts +10 -0
  31. package/src/rules/security/no-clickjacking.js +381 -0
  32. package/src/rules/security/no-clickjacking.js.map +1 -0
  33. package/src/rules/security/no-directive-injection.d.ts +12 -0
  34. package/src/rules/security/no-directive-injection.js +446 -0
  35. package/src/rules/security/no-directive-injection.js.map +1 -0
  36. package/src/rules/security/no-document-cookie.d.ts +5 -0
  37. package/src/rules/security/no-document-cookie.js +90 -0
  38. package/src/rules/security/no-document-cookie.js.map +1 -0
  39. package/src/rules/security/no-electron-security-issues.d.ts +10 -0
  40. package/src/rules/security/no-electron-security-issues.js +421 -0
  41. package/src/rules/security/no-electron-security-issues.js.map +1 -0
  42. package/src/rules/security/no-exposed-sensitive-data.d.ts +11 -0
  43. package/src/rules/security/no-exposed-sensitive-data.js +341 -0
  44. package/src/rules/security/no-exposed-sensitive-data.js.map +1 -0
  45. package/src/rules/security/no-format-string-injection.d.ts +17 -0
  46. package/src/rules/security/no-format-string-injection.js +653 -0
  47. package/src/rules/security/no-format-string-injection.js.map +1 -0
  48. package/src/rules/security/no-graphql-injection.d.ts +12 -0
  49. package/src/rules/security/no-graphql-injection.js +410 -0
  50. package/src/rules/security/no-graphql-injection.js.map +1 -0
  51. package/src/rules/security/no-hardcoded-credentials.d.ts +26 -0
  52. package/src/rules/security/no-hardcoded-credentials.js +377 -0
  53. package/src/rules/security/no-hardcoded-credentials.js.map +1 -0
  54. package/src/rules/security/no-improper-sanitization.d.ts +12 -0
  55. package/src/rules/security/no-improper-sanitization.js +408 -0
  56. package/src/rules/security/no-improper-sanitization.js.map +1 -0
  57. package/src/rules/security/no-improper-type-validation.d.ts +10 -0
  58. package/src/rules/security/no-improper-type-validation.js +420 -0
  59. package/src/rules/security/no-improper-type-validation.js.map +1 -0
  60. package/src/rules/security/no-insecure-comparison.d.ts +7 -0
  61. package/src/rules/security/no-insecure-comparison.js +125 -0
  62. package/src/rules/security/no-insecure-comparison.js.map +1 -0
  63. package/src/rules/security/no-insecure-cookie-settings.d.ts +9 -0
  64. package/src/rules/security/no-insecure-cookie-settings.js +305 -0
  65. package/src/rules/security/no-insecure-cookie-settings.js.map +1 -0
  66. package/src/rules/security/no-insecure-jwt.d.ts +10 -0
  67. package/src/rules/security/no-insecure-jwt.js +338 -0
  68. package/src/rules/security/no-insecure-jwt.js.map +1 -0
  69. package/src/rules/security/no-insecure-redirects.d.ts +7 -0
  70. package/src/rules/security/no-insecure-redirects.js +215 -0
  71. package/src/rules/security/no-insecure-redirects.js.map +1 -0
  72. package/src/rules/security/no-insufficient-postmessage-validation.d.ts +14 -0
  73. package/src/rules/security/no-insufficient-postmessage-validation.js +390 -0
  74. package/src/rules/security/no-insufficient-postmessage-validation.js.map +1 -0
  75. package/src/rules/security/no-insufficient-random.d.ts +9 -0
  76. package/src/rules/security/no-insufficient-random.js +207 -0
  77. package/src/rules/security/no-insufficient-random.js.map +1 -0
  78. package/src/rules/security/no-ldap-injection.d.ts +10 -0
  79. package/src/rules/security/no-ldap-injection.js +449 -0
  80. package/src/rules/security/no-ldap-injection.js.map +1 -0
  81. package/src/rules/security/no-missing-authentication.d.ts +13 -0
  82. package/src/rules/security/no-missing-authentication.js +322 -0
  83. package/src/rules/security/no-missing-authentication.js.map +1 -0
  84. package/src/rules/security/no-missing-cors-check.d.ts +9 -0
  85. package/src/rules/security/no-missing-cors-check.js +449 -0
  86. package/src/rules/security/no-missing-cors-check.js.map +1 -0
  87. package/src/rules/security/no-missing-csrf-protection.d.ts +11 -0
  88. package/src/rules/security/no-missing-csrf-protection.js +183 -0
  89. package/src/rules/security/no-missing-csrf-protection.js.map +1 -0
  90. package/src/rules/security/no-missing-security-headers.d.ts +7 -0
  91. package/src/rules/security/no-missing-security-headers.js +217 -0
  92. package/src/rules/security/no-missing-security-headers.js.map +1 -0
  93. package/src/rules/security/no-privilege-escalation.d.ts +13 -0
  94. package/src/rules/security/no-privilege-escalation.js +321 -0
  95. package/src/rules/security/no-privilege-escalation.js.map +1 -0
  96. package/src/rules/security/no-redos-vulnerable-regex.d.ts +7 -0
  97. package/src/rules/security/no-redos-vulnerable-regex.js +307 -0
  98. package/src/rules/security/no-redos-vulnerable-regex.js.map +1 -0
  99. package/src/rules/security/no-sensitive-data-exposure.d.ts +11 -0
  100. package/src/rules/security/no-sensitive-data-exposure.js +251 -0
  101. package/src/rules/security/no-sensitive-data-exposure.js.map +1 -0
  102. package/src/rules/security/no-sql-injection.d.ts +10 -0
  103. package/src/rules/security/no-sql-injection.js +332 -0
  104. package/src/rules/security/no-sql-injection.js.map +1 -0
  105. package/src/rules/security/no-timing-attack.d.ts +10 -0
  106. package/src/rules/security/no-timing-attack.js +358 -0
  107. package/src/rules/security/no-timing-attack.js.map +1 -0
  108. package/src/rules/security/no-toctou-vulnerability.d.ts +7 -0
  109. package/src/rules/security/no-toctou-vulnerability.js +165 -0
  110. package/src/rules/security/no-toctou-vulnerability.js.map +1 -0
  111. package/src/rules/security/no-unchecked-loop-condition.d.ts +12 -0
  112. package/src/rules/security/no-unchecked-loop-condition.js +635 -0
  113. package/src/rules/security/no-unchecked-loop-condition.js.map +1 -0
  114. package/src/rules/security/no-unencrypted-transmission.d.ts +11 -0
  115. package/src/rules/security/no-unencrypted-transmission.js +237 -0
  116. package/src/rules/security/no-unencrypted-transmission.js.map +1 -0
  117. package/src/rules/security/no-unescaped-url-parameter.d.ts +9 -0
  118. package/src/rules/security/no-unescaped-url-parameter.js +266 -0
  119. package/src/rules/security/no-unescaped-url-parameter.js.map +1 -0
  120. package/src/rules/security/no-unlimited-resource-allocation.d.ts +12 -0
  121. package/src/rules/security/no-unlimited-resource-allocation.js +659 -0
  122. package/src/rules/security/no-unlimited-resource-allocation.js.map +1 -0
  123. package/src/rules/security/no-unsafe-deserialization.d.ts +10 -0
  124. package/src/rules/security/no-unsafe-deserialization.js +501 -0
  125. package/src/rules/security/no-unsafe-deserialization.js.map +1 -0
  126. package/src/rules/security/no-unsafe-dynamic-require.d.ts +5 -0
  127. package/src/rules/security/no-unsafe-dynamic-require.js +107 -0
  128. package/src/rules/security/no-unsafe-dynamic-require.js.map +1 -0
  129. package/src/rules/security/no-unsafe-regex-construction.d.ts +9 -0
  130. package/src/rules/security/no-unsafe-regex-construction.js +292 -0
  131. package/src/rules/security/no-unsafe-regex-construction.js.map +1 -0
  132. package/src/rules/security/no-unsanitized-html.d.ts +9 -0
  133. package/src/rules/security/no-unsanitized-html.js +347 -0
  134. package/src/rules/security/no-unsanitized-html.js.map +1 -0
  135. package/src/rules/security/no-unvalidated-user-input.d.ts +9 -0
  136. package/src/rules/security/no-unvalidated-user-input.js +418 -0
  137. package/src/rules/security/no-unvalidated-user-input.js.map +1 -0
  138. package/src/rules/security/no-weak-crypto.d.ts +11 -0
  139. package/src/rules/security/no-weak-crypto.js +350 -0
  140. package/src/rules/security/no-weak-crypto.js.map +1 -0
  141. package/src/rules/security/no-weak-password-recovery.d.ts +12 -0
  142. package/src/rules/security/no-weak-password-recovery.js +401 -0
  143. package/src/rules/security/no-weak-password-recovery.js.map +1 -0
  144. package/src/rules/security/no-xpath-injection.d.ts +10 -0
  145. package/src/rules/security/no-xpath-injection.js +487 -0
  146. package/src/rules/security/no-xpath-injection.js.map +1 -0
  147. package/src/rules/security/no-xxe-injection.d.ts +7 -0
  148. package/src/rules/security/no-xxe-injection.js +270 -0
  149. package/src/rules/security/no-xxe-injection.js.map +1 -0
  150. package/src/rules/security/no-zip-slip.d.ts +9 -0
  151. package/src/rules/security/no-zip-slip.js +446 -0
  152. package/src/rules/security/no-zip-slip.js.map +1 -0
  153. package/src/types/index.d.ts +131 -0
  154. package/src/types/index.js +18 -0
  155. package/src/types/index.js.map +1 -0
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noTimingAttack = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
+ const eslint_devkit_3 = require("@interlace/eslint-devkit");
7
+ exports.noTimingAttack = (0, eslint_devkit_1.createRule)({
8
+ name: 'no-timing-attack',
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Detects timing attack vulnerabilities in authentication code',
13
+ },
14
+ fixable: 'code',
15
+ messages: {
16
+ timingAttack: (0, eslint_devkit_2.formatLLMMessage)({
17
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
18
+ issueName: 'Timing Attack Vulnerability',
19
+ cwe: 'CWE-208',
20
+ description: 'Timing attack possible - execution time reveals secret information',
21
+ severity: '{{severity}}',
22
+ fix: '{{safeAlternative}}',
23
+ documentationLink: 'https://cwe.mitre.org/data/definitions/208.html',
24
+ }),
25
+ insecureStringComparison: (0, eslint_devkit_2.formatLLMMessage)({
26
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
27
+ issueName: 'Insecure String Comparison',
28
+ cwe: 'CWE-208',
29
+ description: 'String comparison may leak timing information',
30
+ severity: 'HIGH',
31
+ fix: 'Use crypto.timingSafeEqual() for comparing secrets',
32
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequal',
33
+ }),
34
+ earlyReturnLeakage: (0, eslint_devkit_2.formatLLMMessage)({
35
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
36
+ issueName: 'Early Return Timing Leak',
37
+ cwe: 'CWE-208',
38
+ description: 'Early return may leak information through timing',
39
+ severity: 'MEDIUM',
40
+ fix: 'Process all inputs consistently to avoid timing differences',
41
+ documentationLink: 'https://cwe.mitre.org/data/definitions/208.html',
42
+ }),
43
+ useTimingSafeEqual: (0, eslint_devkit_2.formatLLMMessage)({
44
+ icon: eslint_devkit_2.MessageIcons.INFO,
45
+ issueName: 'Use Timing Safe Equal',
46
+ description: 'Use crypto.timingSafeEqual for constant-time comparison',
47
+ severity: 'LOW',
48
+ fix: 'crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
49
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequal',
50
+ }),
51
+ useConstantTimeComparison: (0, eslint_devkit_2.formatLLMMessage)({
52
+ icon: eslint_devkit_2.MessageIcons.INFO,
53
+ issueName: 'Constant Time Comparison',
54
+ description: 'Implement constant-time comparison algorithm',
55
+ severity: 'LOW',
56
+ fix: 'Compare all bytes regardless of content',
57
+ documentationLink: 'https://en.wikipedia.org/wiki/Timing_attack',
58
+ }),
59
+ avoidEarlyReturns: (0, eslint_devkit_2.formatLLMMessage)({
60
+ icon: eslint_devkit_2.MessageIcons.INFO,
61
+ issueName: 'Avoid Early Returns',
62
+ description: 'Avoid early returns in security-sensitive code',
63
+ severity: 'LOW',
64
+ fix: 'Process all inputs before making decisions',
65
+ documentationLink: 'https://cwe.mitre.org/data/definitions/208.html',
66
+ }),
67
+ strategyTimingSafe: (0, eslint_devkit_2.formatLLMMessage)({
68
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
69
+ issueName: 'Timing Safe Strategy',
70
+ description: 'Use built-in timing-safe comparison functions',
71
+ severity: 'LOW',
72
+ fix: 'Use crypto.timingSafeEqual or equivalent library functions',
73
+ documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequal',
74
+ }),
75
+ strategyConstantTime: (0, eslint_devkit_2.formatLLMMessage)({
76
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
77
+ issueName: 'Constant Time Strategy',
78
+ description: 'Implement custom constant-time comparison',
79
+ severity: 'LOW',
80
+ fix: 'Compare all characters/bytes with consistent timing',
81
+ documentationLink: 'https://en.wikipedia.org/wiki/Timing_attack',
82
+ }),
83
+ strategyConsistentTiming: (0, eslint_devkit_2.formatLLMMessage)({
84
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
85
+ issueName: 'Consistent Timing Strategy',
86
+ description: 'Ensure consistent execution time for all inputs',
87
+ severity: 'LOW',
88
+ fix: 'Pad inputs or use fixed-time operations',
89
+ documentationLink: 'https://cwe.mitre.org/data/definitions/208.html',
90
+ })
91
+ },
92
+ schema: [
93
+ {
94
+ type: 'object',
95
+ properties: {
96
+ authFunctions: {
97
+ type: 'array',
98
+ items: { type: 'string' },
99
+ default: ['authenticate', 'login', 'verifyPassword', 'checkToken', 'validateCredentials'],
100
+ },
101
+ sensitiveVariables: {
102
+ type: 'array',
103
+ items: { type: 'string' },
104
+ default: ['password', 'token', 'secret', 'key', 'credentials', 'auth'],
105
+ },
106
+ allowEarlyReturns: {
107
+ type: 'boolean',
108
+ default: false,
109
+ description: 'Allow early returns outside security-sensitive contexts'
110
+ },
111
+ trustedSanitizers: {
112
+ type: 'array',
113
+ items: { type: 'string' },
114
+ default: [],
115
+ description: 'Additional function names to consider as timing-safe',
116
+ },
117
+ trustedAnnotations: {
118
+ type: 'array',
119
+ items: { type: 'string' },
120
+ default: [],
121
+ description: 'Additional JSDoc annotations to consider as timing-safe markers',
122
+ },
123
+ strictMode: {
124
+ type: 'boolean',
125
+ default: false,
126
+ description: 'Disable all false positive detection (strict mode)',
127
+ },
128
+ },
129
+ additionalProperties: false,
130
+ },
131
+ ],
132
+ },
133
+ defaultOptions: [
134
+ {
135
+ authFunctions: ['authenticate', 'login', 'verifyPassword', 'checkToken', 'validateCredentials'],
136
+ sensitiveVariables: ['password', 'token', 'secret', 'key', 'credentials', 'auth'],
137
+ allowEarlyReturns: false,
138
+ trustedSanitizers: ['sanitize'],
139
+ trustedAnnotations: ['@timing-safe'],
140
+ strictMode: false,
141
+ },
142
+ ],
143
+ create(context) {
144
+ const options = context.options[0] || {};
145
+ const { authFunctions = ['authenticate', 'login', 'verifyPassword', 'checkToken', 'validateCredentials'], sensitiveVariables = ['password', 'token', 'secret', 'key', 'credentials', 'auth'], allowEarlyReturns = false, trustedSanitizers = [], trustedAnnotations = [], strictMode = false, } = options;
146
+ const sourceCode = context.sourceCode || context.sourceCode;
147
+ const filename = context.filename || context.getFilename();
148
+ // Create safety checker for false positive detection
149
+ const safetyChecker = (0, eslint_devkit_3.createSafetyChecker)({
150
+ trustedSanitizers,
151
+ trustedAnnotations,
152
+ trustedOrmPatterns: [],
153
+ strictMode,
154
+ });
155
+ // Track variables that contain sensitive data
156
+ const sensitiveVars = new Set();
157
+ /**
158
+ * Check if a variable name indicates sensitive/auth data
159
+ */
160
+ const isSensitiveVariable = (varName) => {
161
+ return sensitiveVariables.some(sensitive => varName.toLowerCase().includes(sensitive.toLowerCase()));
162
+ };
163
+ /**
164
+ * Check if we're in an authentication/security context
165
+ */
166
+ const isInAuthContext = (node) => {
167
+ // Check if we're inside an authentication function
168
+ let current = node;
169
+ /* c8 ignore start -- defensive auth context detection */
170
+ while (current) {
171
+ if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression' || current.type === 'ArrowFunctionExpression') {
172
+ const funcName = current.id?.name;
173
+ if (funcName) {
174
+ // Check exact matches first
175
+ if (authFunctions.includes(funcName)) {
176
+ return true;
177
+ }
178
+ // Check pattern matches for common auth function names
179
+ const authPatterns = ['auth', 'login', 'verify', 'token', 'password', 'credential', 'authenticate'];
180
+ if (authPatterns.some(pattern => funcName.toLowerCase().includes(pattern))) {
181
+ return true;
182
+ }
183
+ }
184
+ }
185
+ if (current.type === 'CallExpression') {
186
+ const callee = current.callee;
187
+ if (callee.type === 'Identifier') {
188
+ if (authFunctions.includes(callee.name)) {
189
+ return true;
190
+ }
191
+ // Check pattern matches
192
+ const authPatterns = ['auth', 'login', 'verify', 'token', 'password', 'credential', 'authenticate'];
193
+ if (authPatterns.some(pattern => callee.name.toLowerCase().includes(pattern))) {
194
+ return true;
195
+ }
196
+ }
197
+ }
198
+ current = current.parent;
199
+ }
200
+ // Check if we're dealing with sensitive variables
201
+ return sensitiveVars.size > 0;
202
+ /* c8 ignore stop */
203
+ };
204
+ /**
205
+ * Check if a comparison is timing-safe
206
+ */
207
+ const isTimingSafeComparison = (node) => {
208
+ // Check for crypto.timingSafeEqual calls
209
+ let current = node;
210
+ while (current) {
211
+ if (current.type === 'CallExpression') {
212
+ const callee = current.callee;
213
+ if (callee.type === 'MemberExpression' &&
214
+ callee.object.type === 'Identifier' &&
215
+ callee.object.name === 'crypto' &&
216
+ callee.property.type === 'Identifier' &&
217
+ callee.property.name === 'timingSafeEqual') {
218
+ return true;
219
+ }
220
+ }
221
+ current = current.parent;
222
+ }
223
+ return false;
224
+ };
225
+ /**
226
+ * Check if early return is in a security-sensitive context
227
+ */
228
+ const isEarlyReturnInAuthContext = (node) => {
229
+ // If early returns are explicitly allowed, don't flag them
230
+ if (allowEarlyReturns) {
231
+ /* c8 ignore next */
232
+ return false;
233
+ }
234
+ return isInAuthContext(node);
235
+ };
236
+ return {
237
+ // Track sensitive variable declarations
238
+ VariableDeclarator(node) {
239
+ if (node.id.type === 'Identifier' && isSensitiveVariable(node.id.name)) {
240
+ sensitiveVars.add(node.id.name);
241
+ }
242
+ // Also check if the variable is assigned sensitive data
243
+ if (node.init && node.id.type === 'Identifier') {
244
+ const initText = sourceCode.getText(node.init).toLowerCase();
245
+ if (sensitiveVariables.some(sensitive => initText.includes(sensitive))) {
246
+ sensitiveVars.add(node.id.name);
247
+ }
248
+ }
249
+ },
250
+ // Check binary expressions for insecure comparisons
251
+ BinaryExpression(node) {
252
+ if (node.operator !== '===' && node.operator !== '==') {
253
+ return;
254
+ }
255
+ // Skip if already using timing-safe comparison
256
+ if (isTimingSafeComparison(node)) {
257
+ return;
258
+ }
259
+ // FALSE POSITIVE REDUCTION: Skip if annotated as safe
260
+ if (safetyChecker.isSafe(node, context)) {
261
+ return;
262
+ }
263
+ // Check if either side involves sensitive data
264
+ const leftText = sourceCode.getText(node.left).toLowerCase();
265
+ const rightText = sourceCode.getText(node.right).toLowerCase();
266
+ const involvesSensitiveData = [leftText, rightText].some(text => sensitiveVariables.some(sensitive => text.toLowerCase().includes(sensitive.toLowerCase())));
267
+ if (!involvesSensitiveData && !isInAuthContext(node)) {
268
+ return;
269
+ }
270
+ context.report({
271
+ node,
272
+ messageId: 'insecureStringComparison',
273
+ data: {
274
+ filePath: filename,
275
+ line: String(node.loc?.start.line ?? 0),
276
+ },
277
+ });
278
+ },
279
+ // Check for early returns that could leak timing information
280
+ ReturnStatement(node) {
281
+ // FALSE POSITIVE REDUCTION: Skip if annotated as safe
282
+ if (safetyChecker.isSafe(node, context)) {
283
+ return;
284
+ }
285
+ if (!isEarlyReturnInAuthContext(node)) {
286
+ return;
287
+ }
288
+ // Look for conditional returns (if/else returns)
289
+ let current = node;
290
+ let isConditionalReturn = false;
291
+ while (current && !isConditionalReturn) {
292
+ if (current.type === 'IfStatement') {
293
+ isConditionalReturn = true;
294
+ break;
295
+ }
296
+ current = current.parent;
297
+ }
298
+ if (!isConditionalReturn) {
299
+ return;
300
+ }
301
+ // Check if this return involves sensitive data
302
+ const returnText = sourceCode.getText(node).toLowerCase();
303
+ const involvesSensitiveData = sensitiveVariables.some(sensitive => returnText.includes(sensitive));
304
+ if (!involvesSensitiveData && !isInAuthContext(node)) {
305
+ return;
306
+ }
307
+ context.report({
308
+ node,
309
+ messageId: 'earlyReturnLeakage',
310
+ data: {
311
+ filePath: filename,
312
+ line: String(node.loc?.start.line ?? 0),
313
+ },
314
+ });
315
+ },
316
+ // Check function calls for timing-sensitive operations
317
+ CallExpression(node) {
318
+ const callee = node.callee;
319
+ // Check for insecure comparison functions
320
+ if (callee.type === 'MemberExpression' &&
321
+ callee.property.type === 'Identifier' &&
322
+ ['equals', 'compare', 'matches'].includes(callee.property.name)) {
323
+ // Skip known timing-safe libraries
324
+ const objectName = callee.object.type === 'Identifier' ? callee.object.name : null;
325
+ if (objectName) {
326
+ // Known timing-safe comparison libraries
327
+ const timingSafeLibraries = ['bcrypt', 'crypto'];
328
+ if (timingSafeLibraries.includes(objectName) && callee.property.name === 'compare') {
329
+ return; // bcrypt.compare and crypto.compare are timing-safe
330
+ }
331
+ }
332
+ // Check if arguments involve sensitive data
333
+ const argsText = node.arguments
334
+ .map((arg) => sourceCode.getText(arg).toLowerCase())
335
+ .join(' ');
336
+ const involvesSensitiveData = sensitiveVariables.some(sensitive => argsText.includes(sensitive));
337
+ if (involvesSensitiveData || isInAuthContext(node)) {
338
+ // FALSE POSITIVE REDUCTION: Skip if annotated as safe
339
+ if (safetyChecker.isSafe(node, context)) {
340
+ return;
341
+ }
342
+ context.report({
343
+ node,
344
+ messageId: 'timingAttack',
345
+ data: {
346
+ filePath: filename,
347
+ line: String(node.loc?.start.line ?? 0),
348
+ severity: 'HIGH',
349
+ safeAlternative: 'Use constant-time comparison functions',
350
+ },
351
+ });
352
+ }
353
+ }
354
+ }
355
+ };
356
+ },
357
+ });
358
+ //# sourceMappingURL=no-timing-attack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-timing-attack.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-secure-coding/src/rules/security/no-timing-attack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export interface Options {
2
+ /** Ignore in test files. Default: true */
3
+ ignoreInTests?: boolean;
4
+ /** File system methods to check. Default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'] */
5
+ fsMethods?: string[];
6
+ }
7
+ export declare const noToctouVulnerability: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noToctouVulnerability = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
+ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
7
+ name: 'no-toctou-vulnerability',
8
+ meta: {
9
+ type: 'problem',
10
+ docs: {
11
+ description: 'Detects Time-of-Check-Time-of-Use vulnerabilities',
12
+ },
13
+ hasSuggestions: true,
14
+ messages: {
15
+ toctouVulnerability: (0, eslint_devkit_1.formatLLMMessage)({
16
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
17
+ issueName: 'TOCTOU vulnerability',
18
+ cwe: 'CWE-367',
19
+ description: 'Time-of-check Time-of-use race condition detected',
20
+ severity: 'HIGH',
21
+ fix: 'Use atomic operations or fs.promises for file operations',
22
+ documentationLink: 'https://cwe.mitre.org/data/definitions/367.html',
23
+ }),
24
+ useAtomicOperations: (0, eslint_devkit_1.formatLLMMessage)({
25
+ icon: eslint_devkit_1.MessageIcons.INFO,
26
+ issueName: 'Use Atomic Operations',
27
+ description: 'Use atomic file operations',
28
+ severity: 'LOW',
29
+ fix: 'fs.promises.access() then fs.promises.readFile()',
30
+ documentationLink: 'https://nodejs.org/api/fs.html#fspromisesaccesspath-mode',
31
+ }),
32
+ useFsPromises: (0, eslint_devkit_1.formatLLMMessage)({
33
+ icon: eslint_devkit_1.MessageIcons.INFO,
34
+ issueName: 'Use fs.promises',
35
+ description: 'Use fs.promises API',
36
+ severity: 'LOW',
37
+ fix: 'await fs.promises.readFile() instead of sync operations',
38
+ documentationLink: 'https://nodejs.org/api/fs.html#promises-api',
39
+ }),
40
+ addProperLocking: (0, eslint_devkit_1.formatLLMMessage)({
41
+ icon: eslint_devkit_1.MessageIcons.INFO,
42
+ issueName: 'Add File Locking',
43
+ description: 'Add proper locking mechanism',
44
+ severity: 'LOW',
45
+ fix: 'Use proper-lockfile or similar for concurrent access',
46
+ documentationLink: 'https://github.com/moxystudio/node-proper-lockfile',
47
+ }),
48
+ },
49
+ schema: [
50
+ {
51
+ type: 'object',
52
+ properties: {
53
+ ignoreInTests: {
54
+ type: 'boolean',
55
+ default: true,
56
+ },
57
+ fsMethods: {
58
+ type: 'array',
59
+ items: { type: 'string' },
60
+ default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'],
61
+ },
62
+ },
63
+ additionalProperties: false,
64
+ },
65
+ ],
66
+ },
67
+ defaultOptions: [
68
+ {
69
+ ignoreInTests: true,
70
+ fsMethods: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'],
71
+ },
72
+ ],
73
+ create(context, [options = {}]) {
74
+ const { ignoreInTests = true } = options || {};
75
+ const filename = context.getFilename();
76
+ const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
77
+ if (isTestFile) {
78
+ return {};
79
+ }
80
+ const sourceCode = context.sourceCode || context.sourceCode;
81
+ /**
82
+ * Check for TOCTOU patterns
83
+ */
84
+ function checkCallExpression(node) {
85
+ const nodeText = sourceCode.getText(node);
86
+ // Only flag file operations (not checks) that are part of check-then-use patterns
87
+ if (!/\b(fs\.readFileSync|fs\.writeFileSync|fs\.openSync|fs\.unlinkSync)\s*\(/.test(nodeText)) {
88
+ return; // Not a file operation we care about
89
+ }
90
+ // Check if this operation is inside an if statement that contains a file check
91
+ let current = node.parent;
92
+ while (current) {
93
+ if (current.type === 'IfStatement') {
94
+ // Check if the test (condition) contains a file check
95
+ const test = current.test;
96
+ if (test.type === 'CallExpression') {
97
+ const testText = sourceCode.getText(test);
98
+ if (/\b(fs\.existsSync|fs\.statSync|fs\.accessSync)\s*\(/.test(testText)) {
99
+ // Check if the file paths match
100
+ const testCall = test;
101
+ const currentCall = node;
102
+ if (testCall.arguments.length > 0 && currentCall.arguments.length > 0) {
103
+ const testArg = testCall.arguments[0];
104
+ const currentArg = currentCall.arguments[0];
105
+ if (testArg.type === 'Literal' && currentArg.type === 'Literal' &&
106
+ testArg.value === currentArg.value) {
107
+ context.report({
108
+ node,
109
+ messageId: 'toctouVulnerability',
110
+ suggest: [
111
+ {
112
+ messageId: 'useAtomicOperations',
113
+ fix: () => null,
114
+ },
115
+ {
116
+ messageId: 'useFsPromises',
117
+ fix: () => null,
118
+ },
119
+ {
120
+ messageId: 'addProperLocking',
121
+ fix: () => null,
122
+ },
123
+ ],
124
+ });
125
+ return; // Found a match, stop searching
126
+ }
127
+ }
128
+ }
129
+ }
130
+ // Also check for stat-then-use patterns
131
+ if (test.type === 'CallExpression' || test.type === 'MemberExpression') {
132
+ const testText = sourceCode.getText(test);
133
+ // Pattern: if (stats.isFile()) { ... fs.unlinkSync("file") ... }
134
+ if (testText.includes('isFile') && nodeText.includes('fs.unlinkSync')) {
135
+ context.report({
136
+ node,
137
+ messageId: 'toctouVulnerability',
138
+ suggest: [
139
+ {
140
+ messageId: 'useAtomicOperations',
141
+ fix: () => null,
142
+ },
143
+ {
144
+ messageId: 'useFsPromises',
145
+ fix: () => null,
146
+ },
147
+ {
148
+ messageId: 'addProperLocking',
149
+ fix: () => null,
150
+ },
151
+ ],
152
+ });
153
+ return; // Found a match, stop searching
154
+ }
155
+ }
156
+ }
157
+ current = current.parent;
158
+ }
159
+ }
160
+ return {
161
+ CallExpression: checkCallExpression,
162
+ };
163
+ },
164
+ });
165
+ //# sourceMappingURL=no-toctou-vulnerability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-toctou-vulnerability.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-secure-coding/src/rules/security/no-toctou-vulnerability.ts"],"names":[],"mappings":";;;AASA,4DAA0E;AAC1E,4DAAsD;AAkBzC,QAAA,qBAAqB,GAAG,IAAA,0BAAU,EAA0B;IACvE,IAAI,EAAE,yBAAyB;IAC/B,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,mDAAmD;SACjE;QACD,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE;YACR,mBAAmB,EAAE,IAAA,gCAAgB,EAAC;gBACpC,IAAI,EAAE,4BAAY,CAAC,QAAQ;gBAC3B,SAAS,EAAE,sBAAsB;gBACjC,GAAG,EAAE,SAAS;gBACd,WAAW,EAAE,mDAAmD;gBAChE,QAAQ,EAAE,MAAM;gBAChB,GAAG,EAAE,0DAA0D;gBAC/D,iBAAiB,EAAE,iDAAiD;aACrE,CAAC;YACF,mBAAmB,EAAE,IAAA,gCAAgB,EAAC;gBACpC,IAAI,EAAE,4BAAY,CAAC,IAAI;gBACvB,SAAS,EAAE,uBAAuB;gBAClC,WAAW,EAAE,4BAA4B;gBACzC,QAAQ,EAAE,KAAK;gBACf,GAAG,EAAE,kDAAkD;gBACvD,iBAAiB,EAAE,0DAA0D;aAC9E,CAAC;YACF,aAAa,EAAE,IAAA,gCAAgB,EAAC;gBAC9B,IAAI,EAAE,4BAAY,CAAC,IAAI;gBACvB,SAAS,EAAE,iBAAiB;gBAC5B,WAAW,EAAE,qBAAqB;gBAClC,QAAQ,EAAE,KAAK;gBACf,GAAG,EAAE,yDAAyD;gBAC9D,iBAAiB,EAAE,6CAA6C;aACjE,CAAC;YACF,gBAAgB,EAAE,IAAA,gCAAgB,EAAC;gBACjC,IAAI,EAAE,4BAAY,CAAC,IAAI;gBACvB,SAAS,EAAE,kBAAkB;gBAC7B,WAAW,EAAE,8BAA8B;gBAC3C,QAAQ,EAAE,KAAK;gBACf,GAAG,EAAE,sDAAsD;gBAC3D,iBAAiB,EAAE,oDAAoD;aACxE,CAAC;SACH;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,aAAa,EAAE;wBACb,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,IAAI;qBACd;oBACD,SAAS,EAAE;wBACT,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;wBACzB,OAAO,EAAE,CAAC,eAAe,EAAE,aAAa,EAAE,eAAe,CAAC;qBAC3D;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE;QACd;YACE,aAAa,EAAE,IAAI;YACnB,SAAS,EAAE,CAAC,eAAe,EAAE,aAAa,EAAE,eAAe,CAAC;SAC7D;KACF;IACD,MAAM,CAAC,OAAsD,EAAE,CAAC,OAAO,GAAG,EAAE,CAAC;QAC3E,MAAM,EACV,aAAa,GAAG,IAAI,EACnB,GAAY,OAAO,IAAI,EAAE,CAAC;QAEvB,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,UAAU,GAAG,aAAa,IAAI,iCAAiC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErF,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;QAE5D;;WAEG;QACH,SAAS,mBAAmB,CAAC,IAA6B;YACxD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAE1C,kFAAkF;YAClF,IAAI,CAAC,yEAAyE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9F,OAAO,CAAC,qCAAqC;YAC/C,CAAC;YAED,+EAA+E;YAC/E,IAAI,OAAO,GAA8B,IAAI,CAAC,MAAM,CAAC;YACrD,OAAO,OAAO,EAAE,CAAC;gBACf,IAAI,OAAO,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;oBACnC,sDAAsD;oBACtD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;oBAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;wBACnC,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;wBAC1C,IAAI,qDAAqD,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;4BACzE,gCAAgC;4BAChC,MAAM,QAAQ,GAAG,IAAI,CAAC;4BACtB,MAAM,WAAW,GAAG,IAAI,CAAC;4BAEzB,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gCACtE,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gCACtC,MAAM,UAAU,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gCAE5C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS;oCAC3D,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC;oCACvC,OAAO,CAAC,MAAM,CAAC;wCACb,IAAI;wCACJ,SAAS,EAAE,qBAAqB;wCAChC,OAAO,EAAE;4CACP;gDACE,SAAS,EAAE,qBAAqB;gDAChC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;6CAChB;4CACD;gDACE,SAAS,EAAE,eAAe;gDAC1B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;6CAChB;4CACD;gDACE,SAAS,EAAE,kBAAkB;gDAC7B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;6CAChB;yCACF;qCACF,CAAC,CAAC;oCACH,OAAO,CAAC,gCAAgC;gCAC1C,CAAC;4BACH,CAAC;wBACH,CAAC;oBACH,CAAC;oBAED,wCAAwC;oBACxC,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;wBACvE,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;wBAE1C,iEAAiE;wBACjE,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;4BACtE,OAAO,CAAC,MAAM,CAAC;gCACb,IAAI;gCACJ,SAAS,EAAE,qBAAqB;gCAChC,OAAO,EAAE;oCACP;wCACE,SAAS,EAAE,qBAAqB;wCAChC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;qCAChB;oCACD;wCACE,SAAS,EAAE,eAAe;wCAC1B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;qCAChB;oCACD;wCACE,SAAS,EAAE,kBAAkB;wCAC7B,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI;qCAChB;iCACF;6BACF,CAAC,CAAC;4BACH,OAAO,CAAC,gCAAgC;wBAC1C,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,OAAO,GAAG,OAAO,CAAC,MAAuB,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,OAAO;YACL,cAAc,EAAE,mBAAmB;SACpC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type SecurityRuleOptions } from '@interlace/eslint-devkit';
2
+ export interface Options extends SecurityRuleOptions {
3
+ /** Maximum allowed loop iterations for static analysis */
4
+ maxStaticIterations?: number;
5
+ /** Variables that contain user input */
6
+ userInputVariables?: string[];
7
+ /** Allow while(true) loops with breaks */
8
+ allowWhileTrueWithBreak?: boolean;
9
+ /** Maximum recursion depth to allow */
10
+ maxRecursionDepth?: number;
11
+ }
12
+ export declare const noUncheckedLoopCondition: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;