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,360 +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.noUnescapedUrlParameter = 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 node is inside a URL encoding function call
13
- */
14
- function isInsideEncodingCall(node, sourceCode, trustedLibraries) {
15
- let current = node;
16
- while (current) {
17
- if (current.type === 'CallExpression') {
18
- const callee = current.callee;
19
- // Check for encodeURIComponent, encodeURI
20
- if (callee.type === 'Identifier') {
21
- const calleeName = callee.name;
22
- if (['encodeURIComponent', 'encodeURI', 'escape'].includes(calleeName)) {
23
- return true;
24
- }
25
- }
26
- // Check if it's a trusted library call
27
- if (callee.type === 'MemberExpression') {
28
- const object = callee.object;
29
- if (object.type === 'Identifier') {
30
- const objectName = object.name.toLowerCase();
31
- if (trustedLibraries.some(lib => objectName.includes(lib.toLowerCase()))) {
32
- return true;
33
- }
34
- }
35
- }
36
- }
37
- // Traverse up the AST
38
- if ('parent' in current && current.parent) {
39
- current = current.parent;
40
- }
41
- else {
42
- break;
43
- }
44
- }
45
- return false;
46
- }
47
- /**
48
- * Check if a string matches any ignore pattern
49
- */
50
- function matchesIgnorePattern(text, ignorePatterns) {
51
- return ignorePatterns.some(pattern => {
52
- try {
53
- const regex = new RegExp(pattern, 'i');
54
- return regex.test(text);
55
- }
56
- catch {
57
- return false;
58
- }
59
- });
60
- }
61
- /**
62
- * Check if a node is a URL construction pattern
63
- */
64
- function isUrlConstruction(node, sourceCode) {
65
- let text = sourceCode.getText(node);
66
- // For template literals, combine raw strings to improve pattern detection
67
- if (node.type === 'TemplateLiteral') {
68
- text = node.quasis.map(q => q.value.raw).join('');
69
- }
70
- // Check for URL construction patterns
71
- const urlPatterns = [
72
- /\bhttps?:\/\//, // HTTP/HTTPS URLs
73
- /\bnew\s+URL\s*\(/,
74
- /\burl\s*[=:]\s*/, // url = or url:
75
- /\burl\s*\+/, // url +
76
- /\bwindow\.location/,
77
- /\blocation\.href/,
78
- /\bwindow\.open\s*\(/,
79
- /\?[^=]+=/, // Query parameters
80
- ];
81
- return urlPatterns.some(pattern => pattern.test(text));
82
- }
83
- exports.noUnescapedUrlParameter = (0, eslint_devkit_2.createRule)({
84
- name: 'no-unescaped-url-parameter',
85
- meta: {
86
- type: 'problem',
87
- docs: {
88
- description: 'Detects unescaped URL parameters',
89
- },
90
- hasSuggestions: true,
91
- messages: {
92
- unescapedUrlParameter: (0, eslint_devkit_1.formatLLMMessage)({
93
- icon: eslint_devkit_1.MessageIcons.SECURITY,
94
- issueName: 'Unescaped URL Parameter',
95
- cwe: 'CWE-79',
96
- description: 'Unescaped URL parameter detected: {{parameter}}',
97
- severity: 'HIGH',
98
- fix: '{{safeAlternative}}',
99
- documentationLink: 'https://cwe.mitre.org/data/definitions/79.html',
100
- }),
101
- useEncodeURIComponent: (0, eslint_devkit_1.formatLLMMessage)({
102
- icon: eslint_devkit_1.MessageIcons.INFO,
103
- issueName: 'Use encodeURIComponent',
104
- description: 'Use encodeURIComponent for URL params',
105
- severity: 'LOW',
106
- fix: '`https://example.com?q=${encodeURIComponent(param)}`',
107
- documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent',
108
- }),
109
- useURLSearchParams: (0, eslint_devkit_1.formatLLMMessage)({
110
- icon: eslint_devkit_1.MessageIcons.INFO,
111
- issueName: 'Use URLSearchParams',
112
- description: 'Use URLSearchParams for safe URL construction',
113
- severity: 'LOW',
114
- fix: 'new URLSearchParams({ q: param }).toString()',
115
- documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams',
116
- }),
117
- },
118
- schema: [
119
- {
120
- type: 'object',
121
- properties: {
122
- allowInTests: {
123
- type: 'boolean',
124
- default: false,
125
- description: 'Allow unescaped URL parameters in test files',
126
- },
127
- trustedLibraries: {
128
- type: 'array',
129
- items: { type: 'string' },
130
- default: ['url', 'querystring'],
131
- description: 'Trusted URL construction libraries',
132
- },
133
- ignorePatterns: {
134
- type: 'array',
135
- items: { type: 'string' },
136
- default: [],
137
- description: 'Additional safe patterns to ignore',
138
- },
139
- },
140
- additionalProperties: false,
141
- },
142
- ],
143
- },
144
- defaultOptions: [
145
- {
146
- allowInTests: false,
147
- trustedLibraries: ['url', 'querystring'],
148
- ignorePatterns: [],
149
- },
150
- ],
151
- create(context, [options = {}]) {
152
- const { allowInTests = false, trustedLibraries = ['url', 'querystring'], ignorePatterns = [], } = options;
153
- const filename = context.getFilename();
154
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
155
- const sourceCode = context.sourceCode || context.sourceCode;
156
- function checkTemplateLiteral(node) {
157
- if (isTestFile) {
158
- return;
159
- }
160
- // Check if this is a URL construction
161
- if (!isUrlConstruction(node, sourceCode)) {
162
- return;
163
- }
164
- // Check each expression in the template
165
- for (const expression of node.expressions) {
166
- const text = sourceCode.getText(expression);
167
- // Check if it matches any ignore pattern
168
- if (matchesIgnorePattern(text, ignorePatterns)) {
169
- continue;
170
- }
171
- // Check if it's already encoded
172
- if (isInsideEncodingCall(expression, sourceCode, trustedLibraries)) {
173
- continue;
174
- }
175
- // Check if it's a user input pattern
176
- const userInputPatterns = [
177
- /\breq\.(query|params|body|headers|cookies)/,
178
- /\brequest\.(query|params|body)/,
179
- /\buserInput\b/i,
180
- /\binput\b/i,
181
- /\bsearchParams\b/,
182
- /\bparam\b/i, // Generic param variable
183
- ];
184
- // Check both the expression text and the full template literal
185
- // This catches nested patterns like req.query.id
186
- const fullText = sourceCode.getText(node);
187
- const exprText = sourceCode.getText(expression);
188
- const isUserInput = userInputPatterns.some(pattern => pattern.test(text)) ||
189
- userInputPatterns.some(pattern => pattern.test(fullText)) ||
190
- userInputPatterns.some(pattern => pattern.test(exprText)) ||
191
- // Also check for nested member expressions like req.query.id
192
- (expression.type === 'MemberExpression' &&
193
- userInputPatterns.some(pattern => {
194
- // Check the full expression including nested properties
195
- return pattern.test(exprText);
196
- }));
197
- if (isUserInput) {
198
- context.report({
199
- node: expression,
200
- messageId: 'unescapedUrlParameter',
201
- data: {
202
- parameter: text,
203
- safeAlternative: `Use encodeURIComponent() or URLSearchParams: const url = \`https://example.com?q=\${encodeURIComponent(${text})}\`;`,
204
- },
205
- suggest: [
206
- {
207
- messageId: 'useEncodeURIComponent',
208
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
209
- fix: (_fixer) => null,
210
- },
211
- {
212
- messageId: 'useURLSearchParams',
213
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
- fix: (_fixer) => null,
215
- },
216
- ],
217
- });
218
- }
219
- }
220
- }
221
- function checkBinaryExpression(node) {
222
- if (isTestFile) {
223
- return;
224
- }
225
- // Check for string concatenation in URL construction
226
- if (node.operator === '+') {
227
- if (!isUrlConstruction(node, sourceCode)) {
228
- return;
229
- }
230
- // Check right side (usually the parameter)
231
- if (node.right.type !== 'Literal') {
232
- const rightText = sourceCode.getText(node.right);
233
- // Check if it matches any ignore pattern
234
- if (matchesIgnorePattern(rightText, ignorePatterns)) {
235
- return;
236
- }
237
- // Check if it's already encoded
238
- if (isInsideEncodingCall(node.right, sourceCode, trustedLibraries)) {
239
- return;
240
- }
241
- // Check if it's a user input pattern
242
- const userInputPatterns = [
243
- /\breq\.(query|params|body)/,
244
- /\brequest\.(query|params|body)/,
245
- /\buserInput\b/,
246
- /\binput\b/,
247
- ];
248
- const isUserInput = userInputPatterns.some(pattern => pattern.test(rightText));
249
- if (isUserInput) {
250
- context.report({
251
- node: node.right,
252
- messageId: 'unescapedUrlParameter',
253
- data: {
254
- parameter: rightText,
255
- safeAlternative: `Use encodeURIComponent(): ${sourceCode.getText(node.left)} + encodeURIComponent(${rightText})`,
256
- },
257
- suggest: [
258
- {
259
- messageId: 'useEncodeURIComponent',
260
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
261
- fix: (_fixer) => null,
262
- },
263
- ],
264
- });
265
- }
266
- }
267
- }
268
- }
269
- function isUserControlled(node, visited = new Set()) {
270
- const text = sourceCode.getText(node);
271
- const patterns = [
272
- /\breq\.(query|params|body|headers|cookies)/,
273
- /\brequest\.(query|params|body)/,
274
- /\buserInput\b/i,
275
- /\binput\b/i,
276
- /\bsearchParams\b/,
277
- /\bparam\b/i,
278
- /\breturnUrl\b/i,
279
- /\burl\b/i,
280
- /\bredirect\b/i,
281
- /\bnext\b/i,
282
- ];
283
- if (patterns.some(p => p.test(text)))
284
- return true;
285
- // Trace identifiers
286
- if (node.type === 'Identifier') {
287
- if (visited.has(node.name))
288
- return false;
289
- visited.add(node.name);
290
- const scope = sourceCode.getScope(node);
291
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
- const variable = scope.variables.find((v) => v.name === node.name);
293
- if (variable && variable.defs.length > 0) {
294
- const def = variable.defs[0];
295
- if (def.type === 'Variable' && def.node.init) {
296
- const init = def.node.init;
297
- // Check if init is constructed from other user inputs
298
- if (isUserControlled(init, visited))
299
- return true;
300
- // Check if init is a TemplateLiteral containing user inputs in expressions
301
- if (init.type === 'TemplateLiteral') {
302
- return init.expressions.some(expr => isUserControlled(expr, visited));
303
- }
304
- // Check if init is BinaryExpression (concatenation)
305
- if (init.type === 'BinaryExpression') {
306
- return isUserControlled(init.left, visited) || isUserControlled(init.right, visited);
307
- }
308
- }
309
- }
310
- }
311
- return false;
312
- }
313
- return {
314
- TemplateLiteral: checkTemplateLiteral,
315
- BinaryExpression: checkBinaryExpression,
316
- AssignmentExpression(node) {
317
- if (isTestFile)
318
- return;
319
- // Check for window.location = ... or window.location.href = ...
320
- const left = node.left;
321
- let isLocationAssignment = false;
322
- if (left.type === 'MemberExpression') {
323
- const objectName = left.object.type === 'Identifier' ? left.object.name :
324
- (left.object.type === 'MemberExpression' ? sourceCode.getText(left.object) : '');
325
- const propName = left.property.type === 'Identifier' ? left.property.name : '';
326
- if ((objectName === 'window' && propName === 'location') ||
327
- (propName === 'href' && objectName.includes('location'))) {
328
- isLocationAssignment = true;
329
- }
330
- }
331
- else if (left.type === 'Identifier' && left.name === 'location') {
332
- // In browser location = ... is valid
333
- isLocationAssignment = true;
334
- }
335
- if (isLocationAssignment) {
336
- const right = node.right;
337
- const rightText = sourceCode.getText(right);
338
- // Skip TemplateLiteral and BinaryExpression as they are covered by their own visitors
339
- if (right.type === 'TemplateLiteral' || right.type === 'BinaryExpression') {
340
- return;
341
- }
342
- if (matchesIgnorePattern(rightText, ignorePatterns))
343
- return;
344
- if (isInsideEncodingCall(right, sourceCode, trustedLibraries))
345
- return;
346
- if (isUserControlled(right)) {
347
- context.report({
348
- node: right,
349
- messageId: 'unescapedUrlParameter',
350
- data: {
351
- parameter: rightText,
352
- safeAlternative: 'Validate and encode URL before redirecting',
353
- }
354
- });
355
- }
356
- }
357
- }
358
- };
359
- },
360
- });
@@ -1,17 +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-unsafe-dynamic-require
8
- * Detects dynamic require() calls that could lead to code injection
9
- */
10
- import type { TSESLint } from '@interlace/eslint-devkit';
11
- export interface Options {
12
- /** Allow dynamic import() expressions. Default: false (stricter) */
13
- allowDynamicImport?: boolean;
14
- }
15
- type RuleOptions = [Options?];
16
- export declare const noUnsafeDynamicRequire: TSESLint.RuleModule<"unsafeDynamicRequire", RuleOptions, unknown, TSESLint.RuleListener>;
17
- export {};
@@ -1,111 +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.noUnsafeDynamicRequire = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
- exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
12
- name: 'no-unsafe-dynamic-require',
13
- meta: {
14
- type: 'problem',
15
- docs: {
16
- description: 'Prevent unsafe dynamic require() calls that could enable code injection',
17
- },
18
- fixable: 'code',
19
- hasSuggestions: false,
20
- messages: {
21
- unsafeDynamicRequire: (0, eslint_devkit_1.formatLLMMessage)({
22
- icon: eslint_devkit_1.MessageIcons.SECURITY,
23
- issueName: 'Dynamic require()',
24
- cwe: 'CWE-95',
25
- description: 'Dynamic require() detected',
26
- severity: 'CRITICAL',
27
- fix: 'Use allowlist: const ALLOWED = ["mod1", "mod2"]; if (!ALLOWED.includes(name)) throw Error("Not allowed")',
28
- documentationLink: 'https://owasp.org/www-community/attacks/Code_Injection',
29
- }),
30
- },
31
- schema: [
32
- {
33
- type: 'object',
34
- properties: {
35
- allowDynamicImport: {
36
- type: 'boolean',
37
- default: false,
38
- },
39
- },
40
- additionalProperties: false,
41
- },
42
- ],
43
- },
44
- defaultOptions: [
45
- {
46
- allowDynamicImport: false,
47
- },
48
- ],
49
- create(context) {
50
- /**
51
- * Track variables that reference require
52
- * Maps variable name to the node where it was assigned
53
- */
54
- const requireVariables = new Set();
55
- /**
56
- * Check if a node is a reference to require
57
- */
58
- const isRequireReference = (node) => {
59
- if (node.type === 'Identifier' && node.name === 'require') {
60
- return true;
61
- }
62
- if (node.type === 'Identifier' && requireVariables.has(node.name)) {
63
- return true;
64
- }
65
- return false;
66
- };
67
- /**
68
- * Check if argument is dynamic (not a literal)
69
- */
70
- const isDynamicArgument = (arg) => {
71
- if (arg.type === 'Literal')
72
- return false;
73
- if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0)
74
- return false;
75
- return true;
76
- };
77
- return {
78
- VariableDeclarator(node) {
79
- // Track when require is assigned to a variable
80
- if (node.id.type === 'Identifier' && node.init) {
81
- if (node.init.type === 'Identifier' && node.init.name === 'require') {
82
- requireVariables.add(node.id.name);
83
- }
84
- }
85
- },
86
- CallExpression(node) {
87
- // Check for require() calls (direct or via variable)
88
- if (node.callee.type !== 'Identifier') {
89
- return;
90
- }
91
- // Check if callee is require or a variable that references require
92
- if (!isRequireReference(node.callee)) {
93
- return;
94
- }
95
- // Must have at least one argument
96
- if (node.arguments.length === 0)
97
- return;
98
- const firstArg = node.arguments[0];
99
- if (firstArg.type === 'SpreadElement')
100
- return;
101
- // Check if dynamic
102
- if (!isDynamicArgument(firstArg))
103
- return;
104
- context.report({
105
- node,
106
- messageId: 'unsafeDynamicRequire',
107
- });
108
- },
109
- };
110
- },
111
- });
@@ -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 noUnvalidatedDeeplinks: import("@typescript-eslint/utils/ts-eslint").RuleModule<"violationDetected", RuleOptions, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
10
- export {};
@@ -1,67 +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.noUnvalidatedDeeplinks = void 0;
9
- /**
10
- * @fileoverview Require validation of deep link URLs
11
- */
12
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
13
- exports.noUnvalidatedDeeplinks = (0, eslint_devkit_1.createRule)({
14
- name: 'no-unvalidated-deeplinks',
15
- meta: {
16
- type: 'problem',
17
- docs: {
18
- description: 'Require validation of deep link URLs',
19
- },
20
- messages: {
21
- violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
22
- icon: eslint_devkit_1.MessageIcons.SECURITY,
23
- issueName: 'Unvalidated Deeplink',
24
- cwe: 'CWE-939',
25
- description: 'Deep link URL used without validation - potential open redirect',
26
- severity: 'HIGH',
27
- fix: 'Validate deep link URLs against a whitelist before navigation',
28
- documentationLink: 'https://cwe.mitre.org/data/definitions/939.html',
29
- })
30
- },
31
- schema: [],
32
- },
33
- defaultOptions: [],
34
- create(context) {
35
- function report(node) {
36
- context.report({ node, messageId: 'violationDetected' });
37
- }
38
- return {
39
- CallExpression(node) {
40
- // Detect Linking.openURL() with variable argument (React Native)
41
- if (node.callee.type === 'MemberExpression' &&
42
- node.callee.object.type === 'Identifier' &&
43
- node.callee.object.name === 'Linking' &&
44
- node.callee.property.type === 'Identifier' &&
45
- node.callee.property.name === 'openURL') {
46
- const urlArg = node.arguments[0];
47
- // Flag if URL is a variable/expression, not a literal
48
- if (urlArg && urlArg.type === 'Identifier') {
49
- report(node);
50
- }
51
- if (urlArg && urlArg.type === 'MemberExpression') {
52
- report(node);
53
- }
54
- }
55
- // Detect navigation.navigate with external URLs
56
- if (node.callee.type === 'MemberExpression' &&
57
- node.callee.property.type === 'Identifier' &&
58
- node.callee.property.name === 'navigate') {
59
- const urlArg = node.arguments[0];
60
- if (urlArg && urlArg.type === 'Identifier') {
61
- report(node);
62
- }
63
- }
64
- },
65
- };
66
- },
67
- });
@@ -1,26 +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-unvalidated-user-input
8
- * Detects unvalidated user input usage (req.body, req.query, etc.)
9
- * CWE-20: Improper Input Validation
10
- *
11
- * @see https://cwe.mitre.org/data/definitions/20.html
12
- * @see https://owasp.org/www-community/vulnerabilities/Improper_Input_Validation
13
- */
14
- import type { TSESLint } from '@interlace/eslint-devkit';
15
- type MessageIds = 'unvalidatedInput' | 'useValidationLibrary' | 'useZod' | 'useJoi';
16
- export interface Options {
17
- /** Allow unvalidated input in test files. Default: false */
18
- allowInTests?: boolean;
19
- /** Trusted validation libraries. Default: ['zod', 'joi', 'yup', 'class-validator'] */
20
- trustedLibraries?: string[];
21
- /** Additional safe patterns to ignore. Default: ['^safe', '^sanitized', '^validated', '^clean'] (prefix patterns) */
22
- ignorePatterns?: string[];
23
- }
24
- type RuleOptions = [Options?];
25
- export declare const noUnvalidatedUserInput: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener>;
26
- export {};