eslint-plugin-secure-coding 3.0.1 → 3.0.2

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 (130) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +60 -226
  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-weak-password-validation/index.d.ts +3 -1
  9. package/src/rules/no-directive-injection/index.d.ts +3 -1
  10. package/src/rules/no-electron-security-issues/index.d.ts +3 -1
  11. package/src/rules/no-format-string-injection/index.d.ts +3 -1
  12. package/src/rules/no-graphql-injection/index.d.ts +3 -1
  13. package/src/rules/no-hardcoded-credentials/index.d.ts +3 -1
  14. package/src/rules/no-hardcoded-session-tokens/index.d.ts +3 -1
  15. package/src/rules/no-improper-sanitization/index.d.ts +3 -1
  16. package/src/rules/no-improper-type-validation/index.d.ts +3 -1
  17. package/src/rules/no-insecure-comparison/index.d.ts +3 -1
  18. package/src/rules/no-ldap-injection/index.d.ts +3 -1
  19. package/src/rules/no-missing-authentication/index.d.ts +3 -1
  20. package/src/rules/no-missing-authentication/index.js +0 -1
  21. package/src/rules/no-pii-in-logs/index.d.ts +3 -1
  22. package/src/rules/no-privilege-escalation/index.d.ts +3 -1
  23. package/src/rules/no-redos-vulnerable-regex/index.d.ts +3 -1
  24. package/src/rules/no-sensitive-data-exposure/index.d.ts +3 -1
  25. package/src/rules/no-unchecked-loop-condition/index.d.ts +3 -1
  26. package/src/rules/no-unlimited-resource-allocation/index.d.ts +3 -1
  27. package/src/rules/no-unsafe-deserialization/index.d.ts +3 -1
  28. package/src/rules/no-unsafe-regex-construction/index.d.ts +3 -1
  29. package/src/rules/no-weak-password-recovery/index.d.ts +3 -1
  30. package/src/rules/no-xpath-injection/index.d.ts +3 -1
  31. package/src/rules/no-xxe-injection/index.d.ts +3 -1
  32. package/src/rules/require-backend-authorization/index.d.ts +3 -1
  33. package/src/rules/require-secure-defaults/index.d.ts +3 -1
  34. package/src/types/index.d.ts +5 -52
  35. package/src/rules/detect-child-process/index.d.ts +0 -28
  36. package/src/rules/detect-child-process/index.js +0 -534
  37. package/src/rules/detect-eval-with-expression/index.d.ts +0 -26
  38. package/src/rules/detect-eval-with-expression/index.js +0 -397
  39. package/src/rules/detect-mixed-content/index.d.ts +0 -10
  40. package/src/rules/detect-mixed-content/index.js +0 -45
  41. package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -24
  42. package/src/rules/detect-non-literal-fs-filename/index.js +0 -459
  43. package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -10
  44. package/src/rules/detect-suspicious-dependencies/index.js +0 -76
  45. package/src/rules/no-allow-arbitrary-loads/index.d.ts +0 -10
  46. package/src/rules/no-allow-arbitrary-loads/index.js +0 -48
  47. package/src/rules/no-arbitrary-file-access/index.d.ts +0 -10
  48. package/src/rules/no-arbitrary-file-access/index.js +0 -200
  49. package/src/rules/no-buffer-overread/index.d.ts +0 -37
  50. package/src/rules/no-buffer-overread/index.js +0 -611
  51. package/src/rules/no-clickjacking/index.d.ts +0 -34
  52. package/src/rules/no-clickjacking/index.js +0 -401
  53. package/src/rules/no-client-side-auth-logic/index.d.ts +0 -10
  54. package/src/rules/no-client-side-auth-logic/index.js +0 -74
  55. package/src/rules/no-credentials-in-query-params/index.d.ts +0 -10
  56. package/src/rules/no-credentials-in-query-params/index.js +0 -62
  57. package/src/rules/no-data-in-temp-storage/index.d.ts +0 -10
  58. package/src/rules/no-data-in-temp-storage/index.js +0 -69
  59. package/src/rules/no-debug-code-in-production/index.d.ts +0 -10
  60. package/src/rules/no-debug-code-in-production/index.js +0 -54
  61. package/src/rules/no-disabled-certificate-validation/index.d.ts +0 -10
  62. package/src/rules/no-disabled-certificate-validation/index.js +0 -66
  63. package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -10
  64. package/src/rules/no-dynamic-dependency-loading/index.js +0 -54
  65. package/src/rules/no-exposed-debug-endpoints/index.d.ts +0 -10
  66. package/src/rules/no-exposed-debug-endpoints/index.js +0 -67
  67. package/src/rules/no-exposed-sensitive-data/index.d.ts +0 -28
  68. package/src/rules/no-exposed-sensitive-data/index.js +0 -345
  69. package/src/rules/no-http-urls/index.d.ts +0 -15
  70. package/src/rules/no-http-urls/index.js +0 -119
  71. package/src/rules/no-insecure-redirects/index.d.ts +0 -24
  72. package/src/rules/no-insecure-redirects/index.js +0 -221
  73. package/src/rules/no-insecure-websocket/index.d.ts +0 -10
  74. package/src/rules/no-insecure-websocket/index.js +0 -66
  75. package/src/rules/no-missing-cors-check/index.d.ts +0 -26
  76. package/src/rules/no-missing-cors-check/index.js +0 -404
  77. package/src/rules/no-missing-csrf-protection/index.d.ts +0 -28
  78. package/src/rules/no-missing-csrf-protection/index.js +0 -185
  79. package/src/rules/no-missing-security-headers/index.d.ts +0 -24
  80. package/src/rules/no-missing-security-headers/index.js +0 -223
  81. package/src/rules/no-password-in-url/index.d.ts +0 -10
  82. package/src/rules/no-password-in-url/index.js +0 -55
  83. package/src/rules/no-permissive-cors/index.d.ts +0 -10
  84. package/src/rules/no-permissive-cors/index.js +0 -74
  85. package/src/rules/no-sensitive-data-in-analytics/index.d.ts +0 -10
  86. package/src/rules/no-sensitive-data-in-analytics/index.js +0 -66
  87. package/src/rules/no-sensitive-data-in-cache/index.d.ts +0 -10
  88. package/src/rules/no-sensitive-data-in-cache/index.js +0 -53
  89. package/src/rules/no-toctou-vulnerability/index.d.ts +0 -24
  90. package/src/rules/no-toctou-vulnerability/index.js +0 -213
  91. package/src/rules/no-tracking-without-consent/index.d.ts +0 -10
  92. package/src/rules/no-tracking-without-consent/index.js +0 -72
  93. package/src/rules/no-unencrypted-transmission/index.d.ts +0 -28
  94. package/src/rules/no-unencrypted-transmission/index.js +0 -241
  95. package/src/rules/no-unescaped-url-parameter/index.d.ts +0 -26
  96. package/src/rules/no-unescaped-url-parameter/index.js +0 -360
  97. package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -17
  98. package/src/rules/no-unsafe-dynamic-require/index.js +0 -111
  99. package/src/rules/no-unvalidated-deeplinks/index.d.ts +0 -10
  100. package/src/rules/no-unvalidated-deeplinks/index.js +0 -67
  101. package/src/rules/no-unvalidated-user-input/index.d.ts +0 -26
  102. package/src/rules/no-unvalidated-user-input/index.js +0 -425
  103. package/src/rules/no-verbose-error-messages/index.d.ts +0 -10
  104. package/src/rules/no-verbose-error-messages/index.js +0 -73
  105. package/src/rules/no-zip-slip/index.d.ts +0 -33
  106. package/src/rules/no-zip-slip/index.js +0 -450
  107. package/src/rules/require-code-minification/index.d.ts +0 -10
  108. package/src/rules/require-code-minification/index.js +0 -48
  109. package/src/rules/require-csp-headers/index.d.ts +0 -10
  110. package/src/rules/require-csp-headers/index.js +0 -69
  111. package/src/rules/require-data-minimization/index.d.ts +0 -10
  112. package/src/rules/require-data-minimization/index.js +0 -55
  113. package/src/rules/require-dependency-integrity/index.d.ts +0 -10
  114. package/src/rules/require-dependency-integrity/index.js +0 -69
  115. package/src/rules/require-https-only/index.d.ts +0 -10
  116. package/src/rules/require-https-only/index.js +0 -67
  117. package/src/rules/require-mime-type-validation/index.d.ts +0 -10
  118. package/src/rules/require-mime-type-validation/index.js +0 -71
  119. package/src/rules/require-network-timeout/index.d.ts +0 -10
  120. package/src/rules/require-network-timeout/index.js +0 -57
  121. package/src/rules/require-package-lock/index.d.ts +0 -10
  122. package/src/rules/require-package-lock/index.js +0 -64
  123. package/src/rules/require-secure-credential-storage/index.d.ts +0 -10
  124. package/src/rules/require-secure-credential-storage/index.js +0 -53
  125. package/src/rules/require-secure-deletion/index.d.ts +0 -10
  126. package/src/rules/require-secure-deletion/index.js +0 -45
  127. package/src/rules/require-storage-encryption/index.d.ts +0 -10
  128. package/src/rules/require-storage-encryption/index.js +0 -53
  129. package/src/rules/require-url-validation/index.d.ts +0 -10
  130. package/src/rules/require-url-validation/index.js +0 -77
@@ -1,404 +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.noMissingCorsCheck = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
- /**
12
- * Check if a string matches any ignore pattern
13
- */
14
- function matchesIgnorePattern(text, ignorePatterns) {
15
- return ignorePatterns.some(pattern => {
16
- try {
17
- const regex = new RegExp(pattern, 'i');
18
- return regex.test(text);
19
- }
20
- catch {
21
- return false;
22
- }
23
- });
24
- }
25
- exports.noMissingCorsCheck = (0, eslint_devkit_2.createRule)({
26
- name: 'no-missing-cors-check',
27
- meta: {
28
- type: 'problem',
29
- deprecated: true,
30
- replacedBy: ['@see eslint-plugin-express-security/no-permissive-cors'],
31
- docs: {
32
- description: 'Detects missing CORS validation (wildcard CORS, missing origin check)',
33
- },
34
- hasSuggestions: true,
35
- messages: {
36
- missingCorsCheck: (0, eslint_devkit_1.formatLLMMessage)({
37
- icon: eslint_devkit_1.MessageIcons.SECURITY,
38
- issueName: 'Missing CORS Validation',
39
- cwe: 'CWE-346',
40
- description: 'Missing CORS validation detected: {{issue}}',
41
- severity: 'HIGH',
42
- fix: '{{safeAlternative}}',
43
- documentationLink: 'https://cwe.mitre.org/data/definitions/346.html',
44
- }),
45
- useOriginValidation: (0, eslint_devkit_1.formatLLMMessage)({
46
- icon: eslint_devkit_1.MessageIcons.INFO,
47
- issueName: 'Validate Origin',
48
- description: 'Validate CORS origin',
49
- severity: 'LOW',
50
- fix: 'cors({ origin: (origin, cb) => allowedOrigins.includes(origin) ? cb(null, true) : cb(new Error()) })',
51
- documentationLink: 'https://github.com/expressjs/cors#configuration-options',
52
- }),
53
- useCorsMiddleware: (0, eslint_devkit_1.formatLLMMessage)({
54
- icon: eslint_devkit_1.MessageIcons.INFO,
55
- issueName: 'Use CORS Middleware',
56
- description: 'Use CORS middleware with origin validation',
57
- severity: 'LOW',
58
- fix: 'app.use(cors({ origin: allowedOrigins }))',
59
- documentationLink: 'https://github.com/expressjs/cors',
60
- }),
61
- },
62
- schema: [
63
- {
64
- type: 'object',
65
- properties: {
66
- allowInTests: {
67
- type: 'boolean',
68
- default: false,
69
- description: 'Allow missing CORS checks in test files',
70
- },
71
- trustedLibraries: {
72
- type: 'array',
73
- items: { type: 'string' },
74
- default: [],
75
- description: 'Custom CORS libraries to trust (wildcard origins in these libraries will not be reported)',
76
- },
77
- ignorePatterns: {
78
- type: 'array',
79
- items: { type: 'string' },
80
- default: [],
81
- description: 'Additional safe patterns to ignore',
82
- },
83
- },
84
- additionalProperties: false,
85
- },
86
- ],
87
- },
88
- defaultOptions: [
89
- {
90
- allowInTests: false,
91
- trustedLibraries: [], // Empty by default - users can add custom CORS libraries they trust
92
- ignorePatterns: [],
93
- },
94
- ],
95
- create(context, [options = {}]) {
96
- const { allowInTests = false, trustedLibraries: corsTrustedLibraries = [], ignorePatterns = [], } = options;
97
- const trustedLibraries = corsTrustedLibraries;
98
- const filename = context.getFilename();
99
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
100
- const sourceCode = context.sourceCode || context.sourceCode;
101
- function checkLiteral(node) {
102
- if (isTestFile) {
103
- return;
104
- }
105
- // Check for wildcard CORS origin
106
- if (node.value === '*' && typeof node.value === 'string') {
107
- const text = sourceCode.getText(node);
108
- // Check if it matches any ignore pattern
109
- if (matchesIgnorePattern(text, ignorePatterns)) {
110
- return;
111
- }
112
- // Check if it's in contexts handled by other checkers
113
- // 1. setHeader/header calls - checkMemberExpression handles these
114
- // 2. app.use(cors({ origin: "*" })) - checkCallExpression handles these with suggestions
115
- let shouldSkip = false;
116
- let current = node;
117
- while (current && current.parent) {
118
- current = current.parent;
119
- if (current.type === 'CallExpression') {
120
- const callText = sourceCode.getText(current);
121
- // Check if it's a setHeader/header call with Access-Control-Allow-Origin
122
- // Skip these - checkMemberExpression handles them
123
- if (/\b(setHeader|header)\s*\(/i.test(callText) && /\bAccess-Control-Allow-Origin\b/i.test(callText)) {
124
- shouldSkip = true;
125
- break;
126
- }
127
- // Check if it's app.use(cors({ origin: "*" })) - checkCallExpression handles these with suggestions
128
- if (/\buse\s*\(/i.test(callText) && /\bcors\s*\(/i.test(callText)) {
129
- // Check if the literal is in an object property named "origin"
130
- if (node.parent && node.parent.type === 'Property') {
131
- const prop = node.parent;
132
- if (prop.key.type === 'Identifier' && prop.key.name === 'origin') {
133
- shouldSkip = true;
134
- break;
135
- }
136
- }
137
- }
138
- }
139
- }
140
- // Skip if it's in a context handled by another checker
141
- if (shouldSkip) {
142
- return;
143
- }
144
- // Check if it's in a CORS-related context
145
- // Only report if it's actually in a CORS configuration (app.use(cors(...)), etc.)
146
- // Not just any object with origin: "*"
147
- let isActualCorsContext = false;
148
- // Check if it's in app.use(cors(...)) or similar
149
- current = node;
150
- while (current && current.parent) {
151
- current = current.parent;
152
- if (current.type === 'CallExpression') {
153
- const callText = sourceCode.getText(current);
154
- // Check if it's a CORS middleware call
155
- if (/\b(use|cors)\s*\(/i.test(callText) && /\bcors\s*\(/i.test(callText)) {
156
- isActualCorsContext = true;
157
- break;
158
- }
159
- }
160
- }
161
- // Also check if it's in an object property with name "origin" or "allowedOrigins"
162
- // but only if it's in a CORS-related call expression
163
- if (node.parent && node.parent.type === 'Property') {
164
- const prop = node.parent;
165
- if (prop.key.type === 'Identifier') {
166
- const keyName = prop.key.name.toLowerCase();
167
- if (keyName === 'origin' || keyName === 'allowedorigins') {
168
- // Check if this property is in a CORS call context
169
- let inCorsCall = false;
170
- let checkNode = prop;
171
- while (checkNode && checkNode.parent) {
172
- checkNode = checkNode.parent;
173
- if (checkNode.type === 'CallExpression') {
174
- const callText = sourceCode.getText(checkNode);
175
- if (/\bcors\s*\(/i.test(callText) ||
176
- (/\buse\s*\(/i.test(callText) && /\bcors/i.test(callText))) {
177
- inCorsCall = true;
178
- break;
179
- }
180
- }
181
- }
182
- if (inCorsCall) {
183
- // Always report wildcard CORS origin - it's never safe
184
- context.report({
185
- node,
186
- messageId: 'missingCorsCheck',
187
- data: {
188
- issue: 'Wildcard CORS origin (*) allows all origins',
189
- safeAlternative: 'Use origin validation: app.use(cors({ origin: (origin, callback) => { if (allowedOrigins.includes(origin)) callback(null, true); else callback(new Error("Not allowed")); } } }));',
190
- },
191
- suggest: [
192
- {
193
- messageId: 'useOriginValidation',
194
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
195
- fix: (_fixer) => null,
196
- },
197
- ],
198
- });
199
- return;
200
- }
201
- }
202
- }
203
- }
204
- // Only report if it's in an actual CORS context
205
- if (isActualCorsContext) {
206
- // Always report wildcard CORS origin - it's never safe
207
- context.report({
208
- node,
209
- messageId: 'missingCorsCheck',
210
- data: {
211
- issue: 'Wildcard CORS origin (*) allows all origins',
212
- safeAlternative: 'Use origin validation: app.use(cors({ origin: (origin, callback) => { if (allowedOrigins.includes(origin)) callback(null, true); else callback(new Error("Not allowed")); } } }));',
213
- },
214
- suggest: [
215
- {
216
- messageId: 'useOriginValidation',
217
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
218
- fix: (_fixer) => null,
219
- },
220
- ],
221
- });
222
- }
223
- }
224
- }
225
- function checkCallExpression(node) {
226
- if (isTestFile) {
227
- return;
228
- }
229
- // Check for app.use(cors({ origin: "*" })) or similar
230
- if (node.callee.type === 'MemberExpression') {
231
- const property = node.callee.property;
232
- if (property.type === 'Identifier' && property.name === 'use') {
233
- // Check if CORS is being used
234
- const text = sourceCode.getText(node);
235
- // Check if it matches any ignore pattern
236
- if (matchesIgnorePattern(text, ignorePatterns)) {
237
- return;
238
- }
239
- // Check if it's a CORS middleware call
240
- // Check for cors() or trusted library calls
241
- const firstArg = node.arguments.length > 0 ? node.arguments[0] : null;
242
- let isCorsCall = /\bcors\s*\(/i.test(text);
243
- if (!isCorsCall && firstArg && firstArg.type === 'CallExpression' && firstArg.callee.type === 'Identifier') {
244
- const callee = firstArg.callee;
245
- const calleeName = callee.name.toLowerCase();
246
- // Check if it's the standard 'cors' library or a trusted library
247
- isCorsCall = calleeName === 'cors' || trustedLibraries.some(lib => {
248
- return calleeName.includes(lib.toLowerCase());
249
- });
250
- }
251
- // Check if it's a trusted library - skip if explicitly trusted
252
- let isTrustedLibrary = false;
253
- if (firstArg && firstArg.type === 'CallExpression' && firstArg.callee.type === 'Identifier') {
254
- const calleeName = firstArg.callee.name.toLowerCase();
255
- isTrustedLibrary = trustedLibraries.some(lib => calleeName.includes(lib.toLowerCase()));
256
- }
257
- if (isTrustedLibrary) {
258
- return; // Trusted library, skip
259
- }
260
- // Check if it's a CORS call
261
- if (/\bcors\s*\(/i.test(text) || isCorsCall) {
262
- // Check arguments for wildcard origin
263
- // For app.use(cors({ origin: "*" })), we need to check the arguments to cors(), not app.use()
264
- const corsCallArg = firstArg && firstArg.type === 'CallExpression' ? firstArg : null;
265
- const argsToCheck = corsCallArg ? corsCallArg.arguments : node.arguments;
266
- for (const arg of argsToCheck) {
267
- if (arg.type === 'ObjectExpression') {
268
- // Check for origin property with wildcard value
269
- for (const prop of arg.properties) {
270
- if (prop.type === 'Property' &&
271
- prop.key.type === 'Identifier' &&
272
- prop.key.name === 'origin' &&
273
- prop.value.type === 'Literal' &&
274
- prop.value.value === '*') {
275
- context.report({
276
- node: prop.value,
277
- messageId: 'missingCorsCheck',
278
- data: {
279
- issue: 'Wildcard CORS origin (*) allows all origins',
280
- safeAlternative: 'Use origin validation: app.use(cors({ origin: (origin, callback) => { if (allowedOrigins.includes(origin)) callback(null, true); else callback(new Error("Not allowed")); } } }));',
281
- },
282
- suggest: [
283
- {
284
- messageId: 'useOriginValidation',
285
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
286
- fix: (_fixer) => null,
287
- },
288
- ],
289
- });
290
- }
291
- }
292
- }
293
- else if (arg.type === 'Identifier') {
294
- // Check if this identifier was assigned an object literal with origin: "*"
295
- // For cases like: const config = { origin: "*" }; app.use(cors(config));
296
- const varName = arg.name;
297
- // Traverse the AST to find the variable declaration
298
- let current = node;
299
- while (current) {
300
- if (current.type === 'Program' || current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression' || current.type === 'ArrowFunctionExpression') {
301
- // Search for variable declarations in this scope
302
- const scopeBody = current.type === 'Program' ? current.body :
303
- (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression' || current.type === 'ArrowFunctionExpression') ?
304
- (current.body.type === 'BlockStatement' ? current.body.body : []) : [];
305
- for (const stmt of scopeBody) {
306
- if (stmt.type === 'VariableDeclaration') {
307
- for (const declarator of stmt.declarations) {
308
- if (declarator.id.type === 'Identifier' && declarator.id.name === varName && declarator.init) {
309
- // Check if init is an object literal with origin: "*"
310
- if (declarator.init.type === 'ObjectExpression') {
311
- for (const prop of declarator.init.properties) {
312
- if (prop.type === 'Property' &&
313
- prop.key.type === 'Identifier' &&
314
- prop.key.name === 'origin' &&
315
- prop.value.type === 'Literal' &&
316
- prop.value.value === '*') {
317
- context.report({
318
- node: arg,
319
- messageId: 'missingCorsCheck',
320
- data: {
321
- issue: 'Wildcard CORS origin (*) allows all origins',
322
- safeAlternative: 'Use origin validation: app.use(cors({ origin: (origin, callback) => { if (allowedOrigins.includes(origin)) callback(null, true); else callback(new Error("Not allowed")); } } }));',
323
- },
324
- suggest: [
325
- {
326
- messageId: 'useOriginValidation',
327
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
328
- fix: (_fixer) => null,
329
- },
330
- ],
331
- });
332
- return; // Found and reported, exit
333
- }
334
- }
335
- }
336
- }
337
- }
338
- }
339
- }
340
- break; // Only check the immediate scope
341
- }
342
- if (current.parent) {
343
- current = current.parent;
344
- }
345
- else {
346
- break;
347
- }
348
- }
349
- }
350
- }
351
- }
352
- }
353
- }
354
- }
355
- function checkMemberExpression(node) {
356
- if (isTestFile) {
357
- return;
358
- }
359
- // Check for Access-Control-Allow-Origin header without validation
360
- if (node.property.type === 'Identifier') {
361
- const propertyName = node.property.name;
362
- if (propertyName === 'setHeader' || propertyName === 'header') {
363
- // Check if it matches any ignore pattern
364
- const text = sourceCode.getText(node);
365
- if (matchesIgnorePattern(text, ignorePatterns)) {
366
- return;
367
- }
368
- // Check if it's setting CORS headers
369
- // Need to check the full call expression, not just the member expression
370
- const parent = node.parent;
371
- if (parent && parent.type === 'CallExpression') {
372
- const callText = sourceCode.getText(parent);
373
- if (/\bAccess-Control-Allow-Origin\b/i.test(callText)) {
374
- // Check if the value is a wildcard
375
- const args = parent.arguments;
376
- if (args.length >= 2 && args[1].type === 'Literal' && args[1].value === '*') {
377
- context.report({
378
- node: args[1],
379
- messageId: 'missingCorsCheck',
380
- data: {
381
- issue: 'Wildcard CORS header allows all origins',
382
- safeAlternative: 'Validate origin before setting header: res.setHeader("Access-Control-Allow-Origin", allowedOrigins.includes(origin) ? origin : "null");',
383
- },
384
- suggest: [
385
- {
386
- messageId: 'useOriginValidation',
387
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
388
- fix: (_fixer) => null,
389
- },
390
- ],
391
- });
392
- }
393
- }
394
- }
395
- }
396
- }
397
- }
398
- return {
399
- Literal: checkLiteral,
400
- CallExpression: checkCallExpression,
401
- MemberExpression: checkMemberExpression,
402
- };
403
- },
404
- });
@@ -1,28 +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-missing-csrf-protection
8
- * Detects missing CSRF token validation in POST/PUT/DELETE requests
9
- * CWE-352: Cross-Site Request Forgery (CSRF)
10
- *
11
- * @see https://cwe.mitre.org/data/definitions/352.html
12
- * @see https://owasp.org/www-community/attacks/csrf
13
- */
14
- import type { TSESLint } from '@interlace/eslint-devkit';
15
- type MessageIds = 'missingCsrfProtection' | 'addCsrfValidation';
16
- export interface Options {
17
- /** Allow missing CSRF protection in test files. Default: false */
18
- allowInTests?: boolean;
19
- /** CSRF middleware patterns to recognize. Default: ['csrf', 'csurf', 'csrfProtection', 'verifyCsrfToken'] */
20
- csrfMiddlewarePatterns?: string[];
21
- /** HTTP methods that require CSRF protection. Default: ['post', 'put', 'delete', 'patch'] */
22
- protectedMethods?: string[];
23
- /** Additional safe patterns to ignore. Default: [] */
24
- ignorePatterns?: string[];
25
- }
26
- type RuleOptions = [Options?];
27
- export declare const noMissingCsrfProtection: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener>;
28
- export {};
@@ -1,185 +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.noMissingCsrfProtection = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
- /**
12
- * Default CSRF middleware patterns
13
- */
14
- const DEFAULT_CSRF_MIDDLEWARE_PATTERNS = [
15
- 'csrf',
16
- 'csurf',
17
- 'csrfProtection',
18
- 'verifyCsrfToken',
19
- 'csrfToken',
20
- 'validateCsrf',
21
- 'checkCsrf',
22
- 'csrfMiddleware',
23
- ];
24
- /**
25
- * Default HTTP methods that require CSRF protection
26
- */
27
- const DEFAULT_PROTECTED_METHODS = ['post', 'put', 'delete', 'patch'];
28
- /**
29
- * Check if a string matches any ignore pattern
30
- */
31
- function matchesIgnorePattern(text, patterns) {
32
- return patterns.some(pattern => {
33
- try {
34
- const regex = new RegExp(pattern, 'i');
35
- return regex.test(text);
36
- }
37
- catch {
38
- return text.toLowerCase().includes(pattern.toLowerCase());
39
- }
40
- });
41
- }
42
- exports.noMissingCsrfProtection = (0, eslint_devkit_2.createRule)({
43
- name: 'no-missing-csrf-protection',
44
- meta: {
45
- type: 'problem',
46
- deprecated: true,
47
- replacedBy: ['@see eslint-plugin-express-security/require-csrf-protection'],
48
- docs: {
49
- description: 'Detects missing CSRF token validation in POST/PUT/DELETE requests',
50
- },
51
- hasSuggestions: true,
52
- messages: {
53
- missingCsrfProtection: (0, eslint_devkit_1.formatLLMMessage)({
54
- icon: eslint_devkit_1.MessageIcons.SECURITY,
55
- issueName: 'Missing CSRF Protection',
56
- cwe: 'CWE-352',
57
- description: 'Missing CSRF protection detected: {{issue}}',
58
- severity: 'HIGH',
59
- fix: '{{safeAlternative}}',
60
- documentationLink: 'https://cwe.mitre.org/data/definitions/352.html',
61
- }),
62
- addCsrfValidation: (0, eslint_devkit_1.formatLLMMessage)({
63
- icon: eslint_devkit_1.MessageIcons.INFO,
64
- issueName: 'Add CSRF Validation',
65
- description: 'Add CSRF middleware',
66
- severity: 'LOW',
67
- fix: 'app.use(csrf({ cookie: true }))',
68
- documentationLink: 'https://github.com/expressjs/csurf',
69
- }),
70
- },
71
- schema: [
72
- {
73
- type: 'object',
74
- properties: {
75
- allowInTests: {
76
- type: 'boolean',
77
- default: false,
78
- description: 'Allow missing CSRF protection in test files',
79
- },
80
- csrfMiddlewarePatterns: {
81
- type: 'array',
82
- items: { type: 'string' },
83
- default: [],
84
- description: 'CSRF middleware patterns to recognize',
85
- },
86
- protectedMethods: {
87
- type: 'array',
88
- items: { type: 'string' },
89
- default: [],
90
- description: 'HTTP methods that require CSRF protection',
91
- },
92
- ignorePatterns: {
93
- type: 'array',
94
- items: { type: 'string' },
95
- default: [],
96
- description: 'Additional safe patterns to ignore',
97
- },
98
- },
99
- additionalProperties: false,
100
- },
101
- ],
102
- },
103
- defaultOptions: [
104
- {
105
- allowInTests: false,
106
- csrfMiddlewarePatterns: [],
107
- protectedMethods: [],
108
- ignorePatterns: [],
109
- },
110
- ],
111
- create(context, [options = {}]) {
112
- const { allowInTests = false, csrfMiddlewarePatterns, protectedMethods: customProtectedMethods, ignorePatterns = [], } = options;
113
- const csrfPatterns = csrfMiddlewarePatterns && csrfMiddlewarePatterns.length > 0
114
- ? csrfMiddlewarePatterns
115
- : DEFAULT_CSRF_MIDDLEWARE_PATTERNS;
116
- const protectedMethods = customProtectedMethods && customProtectedMethods.length > 0
117
- ? customProtectedMethods
118
- : DEFAULT_PROTECTED_METHODS;
119
- // Pre-compute Set for O(1) lookups (performance optimization)
120
- const protectedMethodsSet = new Set(protectedMethods.map(m => m.toLowerCase()));
121
- const filename = context.filename;
122
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
123
- const sourceCode = context.sourceCode;
124
- function checkCallExpression(node) {
125
- if (isTestFile) {
126
- return;
127
- }
128
- const callee = node.callee;
129
- const callText = sourceCode.getText(node);
130
- // Check if it matches any ignore pattern
131
- if (matchesIgnorePattern(callText, ignorePatterns)) {
132
- return;
133
- }
134
- // Check for route handler methods (app.post, router.put, etc.)
135
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression && callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
136
- const methodName = callee.property.name;
137
- // Only check if it's a route handler that requires CSRF (O(1) Set lookup)
138
- if (protectedMethodsSet.has(methodName.toLowerCase())) {
139
- // Must have at least 2 arguments (path and handler)
140
- if (node.arguments.length < 2) {
141
- return;
142
- }
143
- // Check if CSRF middleware is in the route chain arguments
144
- let hasCsrfInChain = false;
145
- // Check if any argument (after the first path argument) is a CSRF middleware
146
- // Skip the first argument (path) and check the rest
147
- for (let i = 1; i < node.arguments.length; i++) {
148
- const arg = node.arguments[i];
149
- const argText = sourceCode.getText(arg);
150
- if (csrfPatterns.some(pattern => argText.toLowerCase().includes(pattern.toLowerCase()))) {
151
- hasCsrfInChain = true;
152
- break;
153
- }
154
- }
155
- if (!hasCsrfInChain) {
156
- context.report({
157
- node,
158
- messageId: 'missingCsrfProtection',
159
- data: {
160
- issue: `${methodName.toUpperCase()} route handler missing CSRF protection`,
161
- safeAlternative: `Add CSRF middleware: app.${methodName}("/path", csrf(), handler) or use app.use(csrf()) globally`,
162
- },
163
- suggest: [
164
- {
165
- messageId: 'addCsrfValidation',
166
- fix(fixer) {
167
- // Add CSRF middleware after the first argument (path)
168
- const firstArg = node.arguments[0];
169
- if (firstArg) {
170
- return fixer.insertTextAfter(firstArg, ', csrf()');
171
- }
172
- return null;
173
- },
174
- },
175
- ],
176
- });
177
- }
178
- }
179
- }
180
- }
181
- return {
182
- CallExpression: checkCallExpression,
183
- };
184
- },
185
- });
@@ -1,24 +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-missing-security-headers
8
- * Detects missing security headers in HTTP responses
9
- * CWE-693: Protection Mechanism Failure
10
- *
11
- * @see https://cwe.mitre.org/data/definitions/693.html
12
- * @see https://owasp.org/www-project-secure-headers/
13
- */
14
- import type { TSESLint } from '@interlace/eslint-devkit';
15
- type MessageIds = 'missingSecurityHeader' | 'addSecurityHeaders' | 'useMiddleware' | 'setHeader';
16
- export interface Options {
17
- /** Required security headers. Default: ['Content-Security-Policy', 'X-Frame-Options', 'X-Content-Type-Options'] */
18
- requiredHeaders?: string[];
19
- /** Ignore in test files. Default: true */
20
- ignoreInTests?: boolean;
21
- }
22
- type RuleOptions = [Options?];
23
- export declare const noMissingSecurityHeaders: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener>;
24
- export {};