eslint-plugin-secure-coding 3.0.1 → 3.0.3

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 (135) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +41 -206
  3. package/package.json +6 -5
  4. package/src/index.d.ts +2 -2
  5. package/src/index.js +29 -263
  6. package/src/rules/detect-non-literal-regexp/index.d.ts +3 -1
  7. package/src/rules/detect-object-injection/index.d.ts +3 -1
  8. package/src/rules/detect-object-injection/index.js +63 -0
  9. package/src/rules/detect-weak-password-validation/index.d.ts +3 -1
  10. package/src/rules/no-directive-injection/index.d.ts +3 -1
  11. package/src/rules/no-electron-security-issues/index.d.ts +3 -1
  12. package/src/rules/no-format-string-injection/index.d.ts +3 -1
  13. package/src/rules/no-graphql-injection/index.d.ts +10 -1
  14. package/src/rules/no-graphql-injection/index.js +294 -38
  15. package/src/rules/no-hardcoded-credentials/index.d.ts +3 -1
  16. package/src/rules/no-hardcoded-session-tokens/index.d.ts +3 -1
  17. package/src/rules/no-improper-sanitization/index.d.ts +3 -1
  18. package/src/rules/no-improper-type-validation/index.d.ts +3 -1
  19. package/src/rules/no-insecure-comparison/index.d.ts +3 -1
  20. package/src/rules/no-insecure-comparison/index.js +9 -0
  21. package/src/rules/no-ldap-injection/index.d.ts +3 -1
  22. package/src/rules/no-missing-authentication/index.d.ts +3 -1
  23. package/src/rules/no-missing-authentication/index.js +0 -1
  24. package/src/rules/no-pii-in-logs/index.d.ts +3 -1
  25. package/src/rules/no-privilege-escalation/index.d.ts +3 -1
  26. package/src/rules/no-redos-vulnerable-regex/index.d.ts +3 -1
  27. package/src/rules/no-sensitive-data-exposure/index.d.ts +3 -1
  28. package/src/rules/no-sensitive-data-exposure/index.js +33 -18
  29. package/src/rules/no-unchecked-loop-condition/index.d.ts +3 -1
  30. package/src/rules/no-unlimited-resource-allocation/index.d.ts +3 -1
  31. package/src/rules/no-unsafe-deserialization/index.d.ts +3 -1
  32. package/src/rules/no-unsafe-regex-construction/index.d.ts +3 -1
  33. package/src/rules/no-weak-password-recovery/index.d.ts +3 -1
  34. package/src/rules/no-xpath-injection/index.d.ts +3 -1
  35. package/src/rules/no-xpath-injection/index.js +26 -2
  36. package/src/rules/no-xxe-injection/index.d.ts +3 -1
  37. package/src/rules/require-backend-authorization/index.d.ts +3 -1
  38. package/src/rules/require-secure-defaults/index.d.ts +3 -1
  39. package/src/types/index.d.ts +5 -52
  40. package/src/rules/detect-child-process/index.d.ts +0 -28
  41. package/src/rules/detect-child-process/index.js +0 -534
  42. package/src/rules/detect-eval-with-expression/index.d.ts +0 -26
  43. package/src/rules/detect-eval-with-expression/index.js +0 -397
  44. package/src/rules/detect-mixed-content/index.d.ts +0 -10
  45. package/src/rules/detect-mixed-content/index.js +0 -45
  46. package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -24
  47. package/src/rules/detect-non-literal-fs-filename/index.js +0 -459
  48. package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -10
  49. package/src/rules/detect-suspicious-dependencies/index.js +0 -76
  50. package/src/rules/no-allow-arbitrary-loads/index.d.ts +0 -10
  51. package/src/rules/no-allow-arbitrary-loads/index.js +0 -48
  52. package/src/rules/no-arbitrary-file-access/index.d.ts +0 -10
  53. package/src/rules/no-arbitrary-file-access/index.js +0 -200
  54. package/src/rules/no-buffer-overread/index.d.ts +0 -37
  55. package/src/rules/no-buffer-overread/index.js +0 -611
  56. package/src/rules/no-clickjacking/index.d.ts +0 -34
  57. package/src/rules/no-clickjacking/index.js +0 -401
  58. package/src/rules/no-client-side-auth-logic/index.d.ts +0 -10
  59. package/src/rules/no-client-side-auth-logic/index.js +0 -74
  60. package/src/rules/no-credentials-in-query-params/index.d.ts +0 -10
  61. package/src/rules/no-credentials-in-query-params/index.js +0 -62
  62. package/src/rules/no-data-in-temp-storage/index.d.ts +0 -10
  63. package/src/rules/no-data-in-temp-storage/index.js +0 -69
  64. package/src/rules/no-debug-code-in-production/index.d.ts +0 -10
  65. package/src/rules/no-debug-code-in-production/index.js +0 -54
  66. package/src/rules/no-disabled-certificate-validation/index.d.ts +0 -10
  67. package/src/rules/no-disabled-certificate-validation/index.js +0 -66
  68. package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -10
  69. package/src/rules/no-dynamic-dependency-loading/index.js +0 -54
  70. package/src/rules/no-exposed-debug-endpoints/index.d.ts +0 -10
  71. package/src/rules/no-exposed-debug-endpoints/index.js +0 -67
  72. package/src/rules/no-exposed-sensitive-data/index.d.ts +0 -28
  73. package/src/rules/no-exposed-sensitive-data/index.js +0 -345
  74. package/src/rules/no-http-urls/index.d.ts +0 -15
  75. package/src/rules/no-http-urls/index.js +0 -119
  76. package/src/rules/no-insecure-redirects/index.d.ts +0 -24
  77. package/src/rules/no-insecure-redirects/index.js +0 -221
  78. package/src/rules/no-insecure-websocket/index.d.ts +0 -10
  79. package/src/rules/no-insecure-websocket/index.js +0 -66
  80. package/src/rules/no-missing-cors-check/index.d.ts +0 -26
  81. package/src/rules/no-missing-cors-check/index.js +0 -404
  82. package/src/rules/no-missing-csrf-protection/index.d.ts +0 -28
  83. package/src/rules/no-missing-csrf-protection/index.js +0 -185
  84. package/src/rules/no-missing-security-headers/index.d.ts +0 -24
  85. package/src/rules/no-missing-security-headers/index.js +0 -223
  86. package/src/rules/no-password-in-url/index.d.ts +0 -10
  87. package/src/rules/no-password-in-url/index.js +0 -55
  88. package/src/rules/no-permissive-cors/index.d.ts +0 -10
  89. package/src/rules/no-permissive-cors/index.js +0 -74
  90. package/src/rules/no-sensitive-data-in-analytics/index.d.ts +0 -10
  91. package/src/rules/no-sensitive-data-in-analytics/index.js +0 -66
  92. package/src/rules/no-sensitive-data-in-cache/index.d.ts +0 -10
  93. package/src/rules/no-sensitive-data-in-cache/index.js +0 -53
  94. package/src/rules/no-toctou-vulnerability/index.d.ts +0 -24
  95. package/src/rules/no-toctou-vulnerability/index.js +0 -213
  96. package/src/rules/no-tracking-without-consent/index.d.ts +0 -10
  97. package/src/rules/no-tracking-without-consent/index.js +0 -72
  98. package/src/rules/no-unencrypted-transmission/index.d.ts +0 -28
  99. package/src/rules/no-unencrypted-transmission/index.js +0 -241
  100. package/src/rules/no-unescaped-url-parameter/index.d.ts +0 -26
  101. package/src/rules/no-unescaped-url-parameter/index.js +0 -360
  102. package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -17
  103. package/src/rules/no-unsafe-dynamic-require/index.js +0 -111
  104. package/src/rules/no-unvalidated-deeplinks/index.d.ts +0 -10
  105. package/src/rules/no-unvalidated-deeplinks/index.js +0 -67
  106. package/src/rules/no-unvalidated-user-input/index.d.ts +0 -26
  107. package/src/rules/no-unvalidated-user-input/index.js +0 -425
  108. package/src/rules/no-verbose-error-messages/index.d.ts +0 -10
  109. package/src/rules/no-verbose-error-messages/index.js +0 -73
  110. package/src/rules/no-zip-slip/index.d.ts +0 -33
  111. package/src/rules/no-zip-slip/index.js +0 -450
  112. package/src/rules/require-code-minification/index.d.ts +0 -10
  113. package/src/rules/require-code-minification/index.js +0 -48
  114. package/src/rules/require-csp-headers/index.d.ts +0 -10
  115. package/src/rules/require-csp-headers/index.js +0 -69
  116. package/src/rules/require-data-minimization/index.d.ts +0 -10
  117. package/src/rules/require-data-minimization/index.js +0 -55
  118. package/src/rules/require-dependency-integrity/index.d.ts +0 -10
  119. package/src/rules/require-dependency-integrity/index.js +0 -69
  120. package/src/rules/require-https-only/index.d.ts +0 -10
  121. package/src/rules/require-https-only/index.js +0 -67
  122. package/src/rules/require-mime-type-validation/index.d.ts +0 -10
  123. package/src/rules/require-mime-type-validation/index.js +0 -71
  124. package/src/rules/require-network-timeout/index.d.ts +0 -10
  125. package/src/rules/require-network-timeout/index.js +0 -57
  126. package/src/rules/require-package-lock/index.d.ts +0 -10
  127. package/src/rules/require-package-lock/index.js +0 -64
  128. package/src/rules/require-secure-credential-storage/index.d.ts +0 -10
  129. package/src/rules/require-secure-credential-storage/index.js +0 -53
  130. package/src/rules/require-secure-deletion/index.d.ts +0 -10
  131. package/src/rules/require-secure-deletion/index.js +0 -45
  132. package/src/rules/require-storage-encryption/index.d.ts +0 -10
  133. package/src/rules/require-storage-encryption/index.js +0 -53
  134. package/src/rules/require-url-validation/index.d.ts +0 -10
  135. package/src/rules/require-url-validation/index.js +0 -77
@@ -1,425 +0,0 @@
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.noUnvalidatedUserInput = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
- /**
12
- * Patterns that indicate unvalidated user input
13
- */
14
- const UNVALIDATED_INPUT_PATTERNS = [
15
- // Express/Node.js patterns
16
- { pattern: /\breq\.body\b/, name: 'req.body', context: 'Express request body' },
17
- { pattern: /\breq\.query\b/, name: 'req.query', context: 'Express query parameters' },
18
- { pattern: /\breq\.params\b/, name: 'req.params', context: 'Express route parameters' },
19
- { pattern: /\breq\.headers\b/, name: 'req.headers', context: 'Express headers' },
20
- { pattern: /\breq\.cookies\b/, name: 'req.cookies', context: 'Express cookies' },
21
- // Fastify patterns
22
- { pattern: /\brequest\.body\b/, name: 'request.body', context: 'Fastify request body' },
23
- { pattern: /\brequest\.query\b/, name: 'request.query', context: 'Fastify query parameters' },
24
- { pattern: /\brequest\.params\b/, name: 'request.params', context: 'Fastify route parameters' },
25
- // Next.js patterns
26
- { pattern: /\bsearchParams\b/, name: 'searchParams', context: 'Next.js search params' },
27
- // Generic patterns - ONLY flag clearly user-related patterns
28
- // Removed 'input' as it's too generic and causes many false positives
29
- { pattern: /\buserInput\b/, name: 'userInput', context: 'Generic user input' },
30
- { pattern: /\bunsafeInput\b/, name: 'unsafeInput', context: 'Explicitly unsafe input' },
31
- { pattern: /\brawInput\b/, name: 'rawInput', context: 'Raw/unprocessed input' },
32
- ];
33
- /**
34
- * Check if a node is inside a validation function call
35
- */
36
- function isInsideValidationCall(node, sourceCode, trustedLibraries) {
37
- let current = node;
38
- while (current) {
39
- // Check if current is an argument to a CallExpression
40
- if (current.parent && current.parent.type === 'CallExpression') {
41
- const callExpr = current.parent;
42
- // Verify that current is actually an argument of this call
43
- const isArgument = callExpr.arguments.some((arg) => arg === current);
44
- if (!isArgument) {
45
- // Not an argument, continue traversing
46
- if ('parent' in current && current.parent) {
47
- current = current.parent;
48
- continue;
49
- }
50
- else {
51
- break;
52
- }
53
- }
54
- const callee = callExpr.callee;
55
- // Check if it's a validation library call (e.g., schema.parse(), schema.validate())
56
- if (callee.type === 'MemberExpression') {
57
- const property = callee.property;
58
- if (property.type === 'Identifier') {
59
- const methodName = property.name.toLowerCase();
60
- // Check for validation methods (including async variants)
61
- // Note: safeParse is one word, not two
62
- if (['parse', 'validate', 'safeparse', 'parseasync', 'validateasync', 'safe_parse'].includes(methodName)) {
63
- return true;
64
- }
65
- }
66
- // Check if the object is a validation library
67
- const object = callee.object;
68
- if (object.type === 'Identifier') {
69
- const objectName = object.name.toLowerCase();
70
- if (trustedLibraries.some(lib => objectName.includes(lib.toLowerCase()))) {
71
- return true;
72
- }
73
- }
74
- }
75
- // Check if it's a direct validation function call (e.g., validate(), plainToClass())
76
- if (callee.type === 'Identifier') {
77
- const calleeName = callee.name.toLowerCase();
78
- if (['validate', 'plaintoclass', 'transform'].includes(calleeName)) {
79
- return true;
80
- }
81
- if (trustedLibraries.some(lib => calleeName.includes(lib.toLowerCase()))) {
82
- return true;
83
- }
84
- }
85
- }
86
- // Traverse up the AST
87
- if ('parent' in current && current.parent) {
88
- current = current.parent;
89
- }
90
- else {
91
- break;
92
- }
93
- }
94
- return false;
95
- }
96
- /**
97
- * Check if a string matches any ignore pattern
98
- */
99
- function matchesIgnorePattern(text, ignorePatterns) {
100
- return ignorePatterns.some(pattern => {
101
- try {
102
- const regex = new RegExp(pattern, 'i');
103
- return regex.test(text);
104
- }
105
- catch {
106
- // Invalid regex - treat as literal string match
107
- return text.toLowerCase().includes(pattern.toLowerCase());
108
- }
109
- });
110
- }
111
- exports.noUnvalidatedUserInput = (0, eslint_devkit_2.createRule)({
112
- name: 'no-unvalidated-user-input',
113
- meta: {
114
- type: 'problem',
115
- docs: {
116
- description: 'Detects unvalidated user input usage (req.body, req.query, etc.)',
117
- },
118
- hasSuggestions: true,
119
- messages: {
120
- unvalidatedInput: (0, eslint_devkit_1.formatLLMMessage)({
121
- icon: eslint_devkit_1.MessageIcons.SECURITY,
122
- issueName: 'Unvalidated User Input',
123
- cwe: 'CWE-20',
124
- description: 'Unvalidated user input detected: {{inputSource}}',
125
- severity: 'HIGH',
126
- fix: 'Use validation library: {{validationExample}}',
127
- documentationLink: 'https://cwe.mitre.org/data/definitions/20.html',
128
- }),
129
- useValidationLibrary: (0, eslint_devkit_1.formatLLMMessage)({
130
- icon: eslint_devkit_1.MessageIcons.INFO,
131
- issueName: 'Use Validation Library',
132
- description: 'Use validation library',
133
- severity: 'LOW',
134
- fix: 'Use Zod, Joi, Yup, or class-validator',
135
- documentationLink: 'https://zod.dev/',
136
- }),
137
- useZod: (0, eslint_devkit_1.formatLLMMessage)({
138
- icon: eslint_devkit_1.MessageIcons.INFO,
139
- issueName: 'Use Zod',
140
- description: 'Use Zod for validation',
141
- severity: 'LOW',
142
- fix: 'const data = z.object({ name: z.string() }).parse(req.body)',
143
- documentationLink: 'https://zod.dev/',
144
- }),
145
- useJoi: (0, eslint_devkit_1.formatLLMMessage)({
146
- icon: eslint_devkit_1.MessageIcons.INFO,
147
- issueName: 'Use Joi',
148
- description: 'Use Joi for validation',
149
- severity: 'LOW',
150
- fix: 'Joi.object({ name: Joi.string() }).validate(req.body)',
151
- documentationLink: 'https://joi.dev/',
152
- }),
153
- },
154
- schema: [
155
- {
156
- type: 'object',
157
- properties: {
158
- allowInTests: {
159
- type: 'boolean',
160
- default: false,
161
- description: 'Allow unvalidated input in test files',
162
- },
163
- trustedLibraries: {
164
- type: 'array',
165
- items: { type: 'string' },
166
- default: ['zod', 'joi', 'yup', 'class-validator'],
167
- description: 'Trusted validation libraries',
168
- },
169
- ignorePatterns: {
170
- type: 'array',
171
- items: { type: 'string' },
172
- default: [],
173
- description: 'Additional safe patterns to ignore',
174
- },
175
- },
176
- additionalProperties: false,
177
- },
178
- ],
179
- },
180
- defaultOptions: [
181
- {
182
- allowInTests: false,
183
- trustedLibraries: ['zod', 'joi', 'yup', 'class-validator'],
184
- ignorePatterns: ['^safe', '^sanitized', '^validated', '^clean'],
185
- },
186
- ],
187
- create(context, [options = {}]) {
188
- const { allowInTests = false, trustedLibraries = ['zod', 'joi', 'yup', 'class-validator'], ignorePatterns = ['^safe', '^sanitized', '^validated', '^clean'], } = options;
189
- const filename = context.getFilename();
190
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
191
- const sourceCode = context.sourceCode || context.sourceCode;
192
- function checkMemberExpression(node) {
193
- if (isTestFile) {
194
- return;
195
- }
196
- const text = sourceCode.getText(node);
197
- // Check if the variable name (if in assignment) matches ignore pattern
198
- // For cases like: const safeInput = req.body;
199
- if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id.type === 'Identifier') {
200
- const varName = node.parent.id.name;
201
- if (matchesIgnorePattern(varName, ignorePatterns)) {
202
- return;
203
- }
204
- }
205
- // Check if it matches any ignore pattern
206
- if (matchesIgnorePattern(text, ignorePatterns)) {
207
- return;
208
- }
209
- // Check if it matches unvalidated input patterns
210
- // For nested member expressions like req.body.name, check the base (req.body)
211
- let baseText = text;
212
- if (node.object.type === 'MemberExpression') {
213
- baseText = sourceCode.getText(node.object);
214
- }
215
- const matchedPattern = UNVALIDATED_INPUT_PATTERNS.find(p => p.pattern.test(text) || p.pattern.test(baseText));
216
- if (matchedPattern) {
217
- // Skip if this is a nested member expression and the parent also matches
218
- // This prevents double reporting for cases like req.query.id
219
- // We only want to report on the outermost matching expression
220
- if (node.object.type === 'MemberExpression') {
221
- const parentText = sourceCode.getText(node.object);
222
- const parentMatches = UNVALIDATED_INPUT_PATTERNS.some(p => p.pattern.test(parentText));
223
- if (parentMatches) {
224
- // Parent also matches, skip this nested one - it will be reported when we visit the parent
225
- return;
226
- }
227
- }
228
- // Skip if this is in a destructuring assignment - checkObjectPattern will handle it
229
- // This prevents double reporting for cases like: const { email } = req.body;
230
- if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id.type === 'ObjectPattern') {
231
- return; // checkObjectPattern will report on the init instead
232
- }
233
- // Check if it's inside a validation call
234
- if (isInsideValidationCall(node, sourceCode, trustedLibraries)) {
235
- return;
236
- }
237
- // Determine validation example based on context
238
- let validationExample = 'const schema = z.object({ field: z.string() }); const data = schema.parse(req.body);';
239
- if (text.includes('query')) {
240
- validationExample = 'const schema = z.object({ id: z.string() }); const data = schema.parse(req.query);';
241
- }
242
- else if (text.includes('params')) {
243
- validationExample = 'const schema = z.object({ id: z.string() }); const data = schema.parse(req.params);';
244
- }
245
- // Build suggestions - provide same code as output for test framework recognition
246
- const suggestions = [
247
- {
248
- messageId: 'useZod',
249
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
250
- fix: (_fixer) => {
251
- // This is a suggestion, not an auto-fix, so we return null
252
- return null;
253
- },
254
- },
255
- {
256
- messageId: 'useJoi',
257
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
258
- fix: (_fixer) => {
259
- return null;
260
- },
261
- },
262
- ];
263
- context.report({
264
- node,
265
- messageId: 'unvalidatedInput',
266
- data: {
267
- inputSource: matchedPattern.name,
268
- validationExample,
269
- },
270
- suggest: suggestions,
271
- });
272
- }
273
- }
274
- function checkIdentifier(node) {
275
- if (isTestFile) {
276
- return;
277
- }
278
- const text = node.name;
279
- // Check if it matches any ignore pattern
280
- if (matchesIgnorePattern(text, ignorePatterns)) {
281
- return;
282
- }
283
- // Skip if this identifier is assigned from a user input source (MemberExpression)
284
- // For cases like: const userInput = req.body;
285
- // We should only report on req.body, not on userInput
286
- // But don't skip if the init is the same identifier (e.g., const data = input;)
287
- if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.init) {
288
- const init = node.parent.init;
289
- // Only skip if init is a MemberExpression (like req.body) that will be caught by checkMemberExpression
290
- // Don't skip if init is the same identifier (like input) - we want to report on it
291
- if (init.type === 'MemberExpression') {
292
- const initText = sourceCode.getText(init);
293
- // Check if init matches any user input pattern
294
- const initMatchesPattern = UNVALIDATED_INPUT_PATTERNS.some(p => p.pattern.test(initText));
295
- if (initMatchesPattern) {
296
- return; // Skip - the init (e.g., req.body) will be reported by checkMemberExpression
297
- }
298
- }
299
- }
300
- // Check for generic input patterns (userInput, unsafeInput, rawInput)
301
- const genericInputPatternNames = ['userInput', 'unsafeInput', 'rawInput'];
302
- const matchedPattern = UNVALIDATED_INPUT_PATTERNS.find(p => genericInputPatternNames.includes(p.name) && p.pattern.test(text));
303
- if (matchedPattern) {
304
- // Check if it's inside a validation call
305
- if (isInsideValidationCall(node, sourceCode, trustedLibraries)) {
306
- return;
307
- }
308
- context.report({
309
- node,
310
- messageId: 'unvalidatedInput',
311
- data: {
312
- inputSource: matchedPattern.name,
313
- validationExample: 'const schema = z.object({ field: z.string() }); const data = schema.parse(input);',
314
- },
315
- suggest: [
316
- {
317
- messageId: 'useZod',
318
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
319
- fix: (_fixer) => null,
320
- },
321
- {
322
- messageId: 'useJoi',
323
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
324
- fix: (_fixer) => null,
325
- },
326
- ],
327
- });
328
- }
329
- }
330
- function checkObjectPattern(node) {
331
- if (isTestFile) {
332
- return;
333
- }
334
- // Check destructuring patterns like: const { page, limit } = req.query;
335
- if (node.parent && node.parent.type === eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator && node.parent.init) {
336
- const init = node.parent.init;
337
- const initText = sourceCode.getText(init);
338
- // If init is a CallExpression, check if it's a validation call
339
- // If so, the input is being validated, so skip
340
- if (init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
341
- const callee = init.callee;
342
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression && callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
343
- const methodName = callee.property.name.toLowerCase();
344
- if (['parse', 'validate', 'safeparse', 'parseasync', 'validateasync', 'safe_parse'].includes(methodName)) {
345
- return; // It's a validation call, skip
346
- }
347
- }
348
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
349
- const calleeName = callee.name.toLowerCase();
350
- if (['validate', 'plaintoclass', 'transform'].includes(calleeName)) {
351
- return; // It's a validation call, skip
352
- }
353
- }
354
- }
355
- // Check if the right side matches unvalidated input patterns
356
- const matchedPattern = UNVALIDATED_INPUT_PATTERNS.find(p => p.pattern.test(initText));
357
- if (matchedPattern) {
358
- // For CallExpressions, check the arguments to see if they're validated
359
- // The init itself being a validation call was already checked above
360
- if (init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
361
- // Check each argument to see if it's validated
362
- // If init is a validation call (like schema.validate(req.body)),
363
- // then req.body is validated, so skip
364
- const callee = init.callee;
365
- const isValidationCall = (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression && callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
366
- ['parse', 'validate', 'safeparse', 'parseasync', 'validateasync', 'safe_parse'].includes(callee.property.name.toLowerCase())) ||
367
- (callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
368
- ['validate', 'plaintoclass', 'transform'].includes(callee.name.toLowerCase()));
369
- if (isValidationCall) {
370
- return; // The init is a validation call, so the input is validated
371
- }
372
- // If init is not a validation call, check if arguments are validated
373
- const hasValidatedArg = init.arguments.some((arg) => {
374
- if (arg.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression || arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
375
- return isInsideValidationCall(arg, sourceCode, trustedLibraries);
376
- }
377
- return false;
378
- });
379
- if (hasValidatedArg) {
380
- return; // At least one argument is validated
381
- }
382
- }
383
- else {
384
- // For non-call expressions, check if init itself is inside a validation call
385
- if (isInsideValidationCall(init, sourceCode, trustedLibraries)) {
386
- return;
387
- }
388
- }
389
- // Check if variable name matches ignore pattern
390
- if (node.parent.id.type === eslint_devkit_1.AST_NODE_TYPES.ObjectPattern) {
391
- const varText = sourceCode.getText(node.parent.id);
392
- if (matchesIgnorePattern(varText, ignorePatterns)) {
393
- return;
394
- }
395
- }
396
- context.report({
397
- node: init,
398
- messageId: 'unvalidatedInput',
399
- data: {
400
- inputSource: matchedPattern.name,
401
- validationExample: 'const schema = z.object({ page: z.string(), limit: z.string() }); const { page, limit } = schema.parse(req.query);',
402
- },
403
- suggest: [
404
- {
405
- messageId: 'useZod',
406
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
407
- fix: (_fixer) => null,
408
- },
409
- {
410
- messageId: 'useJoi',
411
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
412
- fix: (_fixer) => null,
413
- },
414
- ],
415
- });
416
- }
417
- }
418
- }
419
- return {
420
- MemberExpression: checkMemberExpression,
421
- Identifier: checkIdentifier,
422
- ObjectPattern: checkObjectPattern,
423
- };
424
- },
425
- });
@@ -1,10 +0,0 @@
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
- export interface Options {
7
- }
8
- type RuleOptions = [Options?];
9
- export declare const noVerboseErrorMessages: import("@typescript-eslint/utils/ts-eslint").RuleModule<"violationDetected", RuleOptions, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
- export {};
@@ -1,73 +0,0 @@
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.noVerboseErrorMessages = void 0;
9
- /**
10
- * @fileoverview Prevent exposing stack traces to users
11
- * @see https://owasp.org/www-project-mobile-top-10/
12
- * @see https://cwe.mitre.org/data/definitions/209.html
13
- */
14
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
15
- exports.noVerboseErrorMessages = (0, eslint_devkit_1.createRule)({
16
- name: 'no-verbose-error-messages',
17
- meta: {
18
- type: 'problem',
19
- docs: {
20
- description: 'Prevent exposing stack traces to users',
21
- },
22
- messages: {
23
- violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
24
- icon: eslint_devkit_1.MessageIcons.SECURITY,
25
- issueName: 'violation Detected',
26
- cwe: 'CWE-209',
27
- description: 'Prevent exposing stack traces to users detected - this is a security risk',
28
- severity: 'MEDIUM',
29
- fix: 'Review and apply secure practices',
30
- documentationLink: 'https://cwe.mitre.org/data/definitions/209.html',
31
- })
32
- },
33
- schema: [],
34
- },
35
- defaultOptions: [],
36
- create(context) {
37
- function report(node) {
38
- context.report({
39
- node,
40
- messageId: 'violationDetected',
41
- });
42
- }
43
- return {
44
- CallExpression(node) {
45
- // Check res.send/res.json with error.stack
46
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
47
- node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
48
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
49
- ['send', 'json'].includes(node.callee.property.name)) {
50
- const arg = node.arguments[0];
51
- // Check for error.stack or err.stack
52
- if (arg?.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
53
- arg.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
54
- arg.property.name === 'stack') {
55
- report(node);
56
- }
57
- // Check for { stack: error.stack } in object
58
- if (arg?.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression) {
59
- const stackProp = arg.properties.find(p => p.type === eslint_devkit_1.AST_NODE_TYPES.Property &&
60
- p.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
61
- (p.key.name === 'stack' ||
62
- (p.value.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
63
- p.value.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
64
- p.value.property.name === 'stack')));
65
- if (stackProp) {
66
- report(node);
67
- }
68
- }
69
- }
70
- },
71
- };
72
- },
73
- });
@@ -1,33 +0,0 @@
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-zip-slip
8
- * Detects zip slip/archive extraction vulnerabilities (CWE-22)
9
- *
10
- * Zip slip vulnerabilities occur when extracting archives without properly
11
- * validating file paths, allowing attackers to write files outside the
12
- * intended extraction directory using path traversal sequences like "../".
13
- *
14
- * False Positive Reduction:
15
- * This rule uses security utilities to reduce false positives by detecting:
16
- * - Safe archive extraction patterns
17
- * - Path validation functions
18
- * - JSDoc annotations (@safe, @validated)
19
- * - Trusted extraction libraries
20
- */
21
- import type { TSESLint } from '@interlace/eslint-devkit';
22
- type MessageIds = 'zipSlipVulnerability' | 'unsafeArchiveExtraction' | 'pathTraversalInArchive' | 'unvalidatedArchivePath' | 'dangerousArchiveDestination' | 'useSafeArchiveExtraction' | 'validateArchivePaths' | 'sanitizeArchiveNames' | 'strategyPathValidation' | 'strategySafeLibraries' | 'strategySandboxing';
23
- export interface Options {
24
- /** Archive extraction functions to check */
25
- archiveFunctions?: string[];
26
- /** Functions that safely validate archive paths */
27
- pathValidationFunctions?: string[];
28
- /** Safe archive extraction libraries */
29
- safeLibraries?: string[];
30
- }
31
- type RuleOptions = [Options?];
32
- export declare const noZipSlip: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener>;
33
- export {};