eslint-plugin-secure-coding 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +196 -0
  2. package/CHANGELOG.md +105 -0
  3. package/LICENSE +23 -0
  4. package/README.md +377 -0
  5. package/package.json +80 -0
  6. package/src/index.d.ts +32 -0
  7. package/src/index.js +345 -0
  8. package/src/index.js.map +1 -0
  9. package/src/rules/security/database-injection.d.ts +13 -0
  10. package/src/rules/security/database-injection.js +407 -0
  11. package/src/rules/security/database-injection.js.map +1 -0
  12. package/src/rules/security/detect-child-process.d.ts +11 -0
  13. package/src/rules/security/detect-child-process.js +460 -0
  14. package/src/rules/security/detect-child-process.js.map +1 -0
  15. package/src/rules/security/detect-eval-with-expression.d.ts +9 -0
  16. package/src/rules/security/detect-eval-with-expression.js +393 -0
  17. package/src/rules/security/detect-eval-with-expression.js.map +1 -0
  18. package/src/rules/security/detect-non-literal-fs-filename.d.ts +7 -0
  19. package/src/rules/security/detect-non-literal-fs-filename.js +322 -0
  20. package/src/rules/security/detect-non-literal-fs-filename.js.map +1 -0
  21. package/src/rules/security/detect-non-literal-regexp.d.ts +9 -0
  22. package/src/rules/security/detect-non-literal-regexp.js +387 -0
  23. package/src/rules/security/detect-non-literal-regexp.js.map +1 -0
  24. package/src/rules/security/detect-object-injection.d.ts +11 -0
  25. package/src/rules/security/detect-object-injection.js +411 -0
  26. package/src/rules/security/detect-object-injection.js.map +1 -0
  27. package/src/rules/security/no-buffer-overread.d.ts +14 -0
  28. package/src/rules/security/no-buffer-overread.js +519 -0
  29. package/src/rules/security/no-buffer-overread.js.map +1 -0
  30. package/src/rules/security/no-clickjacking.d.ts +10 -0
  31. package/src/rules/security/no-clickjacking.js +381 -0
  32. package/src/rules/security/no-clickjacking.js.map +1 -0
  33. package/src/rules/security/no-directive-injection.d.ts +12 -0
  34. package/src/rules/security/no-directive-injection.js +446 -0
  35. package/src/rules/security/no-directive-injection.js.map +1 -0
  36. package/src/rules/security/no-document-cookie.d.ts +5 -0
  37. package/src/rules/security/no-document-cookie.js +90 -0
  38. package/src/rules/security/no-document-cookie.js.map +1 -0
  39. package/src/rules/security/no-electron-security-issues.d.ts +10 -0
  40. package/src/rules/security/no-electron-security-issues.js +421 -0
  41. package/src/rules/security/no-electron-security-issues.js.map +1 -0
  42. package/src/rules/security/no-exposed-sensitive-data.d.ts +11 -0
  43. package/src/rules/security/no-exposed-sensitive-data.js +341 -0
  44. package/src/rules/security/no-exposed-sensitive-data.js.map +1 -0
  45. package/src/rules/security/no-format-string-injection.d.ts +17 -0
  46. package/src/rules/security/no-format-string-injection.js +653 -0
  47. package/src/rules/security/no-format-string-injection.js.map +1 -0
  48. package/src/rules/security/no-graphql-injection.d.ts +12 -0
  49. package/src/rules/security/no-graphql-injection.js +410 -0
  50. package/src/rules/security/no-graphql-injection.js.map +1 -0
  51. package/src/rules/security/no-hardcoded-credentials.d.ts +26 -0
  52. package/src/rules/security/no-hardcoded-credentials.js +377 -0
  53. package/src/rules/security/no-hardcoded-credentials.js.map +1 -0
  54. package/src/rules/security/no-improper-sanitization.d.ts +12 -0
  55. package/src/rules/security/no-improper-sanitization.js +408 -0
  56. package/src/rules/security/no-improper-sanitization.js.map +1 -0
  57. package/src/rules/security/no-improper-type-validation.d.ts +10 -0
  58. package/src/rules/security/no-improper-type-validation.js +420 -0
  59. package/src/rules/security/no-improper-type-validation.js.map +1 -0
  60. package/src/rules/security/no-insecure-comparison.d.ts +7 -0
  61. package/src/rules/security/no-insecure-comparison.js +125 -0
  62. package/src/rules/security/no-insecure-comparison.js.map +1 -0
  63. package/src/rules/security/no-insecure-cookie-settings.d.ts +9 -0
  64. package/src/rules/security/no-insecure-cookie-settings.js +305 -0
  65. package/src/rules/security/no-insecure-cookie-settings.js.map +1 -0
  66. package/src/rules/security/no-insecure-jwt.d.ts +10 -0
  67. package/src/rules/security/no-insecure-jwt.js +338 -0
  68. package/src/rules/security/no-insecure-jwt.js.map +1 -0
  69. package/src/rules/security/no-insecure-redirects.d.ts +7 -0
  70. package/src/rules/security/no-insecure-redirects.js +215 -0
  71. package/src/rules/security/no-insecure-redirects.js.map +1 -0
  72. package/src/rules/security/no-insufficient-postmessage-validation.d.ts +14 -0
  73. package/src/rules/security/no-insufficient-postmessage-validation.js +390 -0
  74. package/src/rules/security/no-insufficient-postmessage-validation.js.map +1 -0
  75. package/src/rules/security/no-insufficient-random.d.ts +9 -0
  76. package/src/rules/security/no-insufficient-random.js +207 -0
  77. package/src/rules/security/no-insufficient-random.js.map +1 -0
  78. package/src/rules/security/no-ldap-injection.d.ts +10 -0
  79. package/src/rules/security/no-ldap-injection.js +449 -0
  80. package/src/rules/security/no-ldap-injection.js.map +1 -0
  81. package/src/rules/security/no-missing-authentication.d.ts +13 -0
  82. package/src/rules/security/no-missing-authentication.js +322 -0
  83. package/src/rules/security/no-missing-authentication.js.map +1 -0
  84. package/src/rules/security/no-missing-cors-check.d.ts +9 -0
  85. package/src/rules/security/no-missing-cors-check.js +449 -0
  86. package/src/rules/security/no-missing-cors-check.js.map +1 -0
  87. package/src/rules/security/no-missing-csrf-protection.d.ts +11 -0
  88. package/src/rules/security/no-missing-csrf-protection.js +183 -0
  89. package/src/rules/security/no-missing-csrf-protection.js.map +1 -0
  90. package/src/rules/security/no-missing-security-headers.d.ts +7 -0
  91. package/src/rules/security/no-missing-security-headers.js +217 -0
  92. package/src/rules/security/no-missing-security-headers.js.map +1 -0
  93. package/src/rules/security/no-privilege-escalation.d.ts +13 -0
  94. package/src/rules/security/no-privilege-escalation.js +321 -0
  95. package/src/rules/security/no-privilege-escalation.js.map +1 -0
  96. package/src/rules/security/no-redos-vulnerable-regex.d.ts +7 -0
  97. package/src/rules/security/no-redos-vulnerable-regex.js +307 -0
  98. package/src/rules/security/no-redos-vulnerable-regex.js.map +1 -0
  99. package/src/rules/security/no-sensitive-data-exposure.d.ts +11 -0
  100. package/src/rules/security/no-sensitive-data-exposure.js +251 -0
  101. package/src/rules/security/no-sensitive-data-exposure.js.map +1 -0
  102. package/src/rules/security/no-sql-injection.d.ts +10 -0
  103. package/src/rules/security/no-sql-injection.js +332 -0
  104. package/src/rules/security/no-sql-injection.js.map +1 -0
  105. package/src/rules/security/no-timing-attack.d.ts +10 -0
  106. package/src/rules/security/no-timing-attack.js +358 -0
  107. package/src/rules/security/no-timing-attack.js.map +1 -0
  108. package/src/rules/security/no-toctou-vulnerability.d.ts +7 -0
  109. package/src/rules/security/no-toctou-vulnerability.js +165 -0
  110. package/src/rules/security/no-toctou-vulnerability.js.map +1 -0
  111. package/src/rules/security/no-unchecked-loop-condition.d.ts +12 -0
  112. package/src/rules/security/no-unchecked-loop-condition.js +635 -0
  113. package/src/rules/security/no-unchecked-loop-condition.js.map +1 -0
  114. package/src/rules/security/no-unencrypted-transmission.d.ts +11 -0
  115. package/src/rules/security/no-unencrypted-transmission.js +237 -0
  116. package/src/rules/security/no-unencrypted-transmission.js.map +1 -0
  117. package/src/rules/security/no-unescaped-url-parameter.d.ts +9 -0
  118. package/src/rules/security/no-unescaped-url-parameter.js +266 -0
  119. package/src/rules/security/no-unescaped-url-parameter.js.map +1 -0
  120. package/src/rules/security/no-unlimited-resource-allocation.d.ts +12 -0
  121. package/src/rules/security/no-unlimited-resource-allocation.js +659 -0
  122. package/src/rules/security/no-unlimited-resource-allocation.js.map +1 -0
  123. package/src/rules/security/no-unsafe-deserialization.d.ts +10 -0
  124. package/src/rules/security/no-unsafe-deserialization.js +501 -0
  125. package/src/rules/security/no-unsafe-deserialization.js.map +1 -0
  126. package/src/rules/security/no-unsafe-dynamic-require.d.ts +5 -0
  127. package/src/rules/security/no-unsafe-dynamic-require.js +107 -0
  128. package/src/rules/security/no-unsafe-dynamic-require.js.map +1 -0
  129. package/src/rules/security/no-unsafe-regex-construction.d.ts +9 -0
  130. package/src/rules/security/no-unsafe-regex-construction.js +292 -0
  131. package/src/rules/security/no-unsafe-regex-construction.js.map +1 -0
  132. package/src/rules/security/no-unsanitized-html.d.ts +9 -0
  133. package/src/rules/security/no-unsanitized-html.js +347 -0
  134. package/src/rules/security/no-unsanitized-html.js.map +1 -0
  135. package/src/rules/security/no-unvalidated-user-input.d.ts +9 -0
  136. package/src/rules/security/no-unvalidated-user-input.js +418 -0
  137. package/src/rules/security/no-unvalidated-user-input.js.map +1 -0
  138. package/src/rules/security/no-weak-crypto.d.ts +11 -0
  139. package/src/rules/security/no-weak-crypto.js +350 -0
  140. package/src/rules/security/no-weak-crypto.js.map +1 -0
  141. package/src/rules/security/no-weak-password-recovery.d.ts +12 -0
  142. package/src/rules/security/no-weak-password-recovery.js +401 -0
  143. package/src/rules/security/no-weak-password-recovery.js.map +1 -0
  144. package/src/rules/security/no-xpath-injection.d.ts +10 -0
  145. package/src/rules/security/no-xpath-injection.js +487 -0
  146. package/src/rules/security/no-xpath-injection.js.map +1 -0
  147. package/src/rules/security/no-xxe-injection.d.ts +7 -0
  148. package/src/rules/security/no-xxe-injection.js +270 -0
  149. package/src/rules/security/no-xxe-injection.js.map +1 -0
  150. package/src/rules/security/no-zip-slip.d.ts +9 -0
  151. package/src/rules/security/no-zip-slip.js +446 -0
  152. package/src/rules/security/no-zip-slip.js.map +1 -0
  153. package/src/types/index.d.ts +131 -0
  154. package/src/types/index.js +18 -0
  155. package/src/types/index.js.map +1 -0
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noInsecureCookieSettings = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ /**
6
+ * Check if a node is inside a cookie configuration
7
+ */
8
+ function isInsideCookieConfig(node, sourceCode) {
9
+ let current = node;
10
+ // Traverse up the parent chain
11
+ while (current && 'parent' in current && current.parent) {
12
+ current = current.parent;
13
+ // Check for cookie-related method calls
14
+ if (current.type === 'CallExpression') {
15
+ const callExpr = current;
16
+ // Check for res.cookie() calls
17
+ if (callExpr.callee.type === 'MemberExpression') {
18
+ const memberExpr = callExpr.callee;
19
+ if (memberExpr.property.type === 'Identifier' && memberExpr.property.name === 'cookie') {
20
+ // Check if the node is an argument of this call
21
+ if (callExpr.arguments.some((arg) => arg === node || (arg.type === 'ObjectExpression' && sourceCode.getText(arg).includes(sourceCode.getText(node))))) {
22
+ return true;
23
+ }
24
+ }
25
+ }
26
+ // Check for other cookie-related calls using text matching
27
+ const callText = sourceCode.getText(current);
28
+ if (/\b(cookie|cookies|setCookie|res\.cookie|document\.cookie)\b/i.test(callText)) {
29
+ const callee = callExpr.callee;
30
+ // Specific check for cookies.set / cookie.set
31
+ if (callee.type === 'MemberExpression' &&
32
+ callee.property.type === 'Identifier' &&
33
+ callee.property.name === 'set') {
34
+ return true;
35
+ }
36
+ // Check if node is part of this call
37
+ const nodeText = sourceCode.getText(node);
38
+ if (callText.includes(nodeText)) {
39
+ return true;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ return false;
45
+ }
46
+ /**
47
+ * Check if an object expression has secure cookie settings
48
+ */
49
+ function hasSecureCookieSettings(node, sourceCode) {
50
+ const text = sourceCode.getText(node);
51
+ // Check for httpOnly flag (case-insensitive)
52
+ const hasHttpOnly = /\bhttpOnly\s*:\s*(true|'true'|"true")/i.test(text);
53
+ // Check for secure flag (case-insensitive)
54
+ const hasSecure = /\bsecure\s*:\s*(true|'true'|"true")/i.test(text);
55
+ // Check for sameSite flag (should be 'strict', 'lax', or 'none')
56
+ const hasSameSite = /\bsameSite\s*:\s*['"](strict|lax|none)['"]/i.test(text);
57
+ return { hasHttpOnly, hasSecure, hasSameSite };
58
+ }
59
+ /**
60
+ * Check if a string matches any ignore pattern
61
+ */
62
+ function matchesIgnorePattern(text, ignorePatterns) {
63
+ return ignorePatterns.some(pattern => {
64
+ try {
65
+ const regex = new RegExp(pattern, 'i');
66
+ return regex.test(text);
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ });
72
+ }
73
+ exports.noInsecureCookieSettings = (0, eslint_devkit_1.createRule)({
74
+ name: 'no-insecure-cookie-settings',
75
+ meta: {
76
+ type: 'problem',
77
+ docs: {
78
+ description: 'Detects insecure cookie configurations (missing httpOnly, secure, sameSite flags)',
79
+ },
80
+ hasSuggestions: true,
81
+ messages: {
82
+ insecureCookieSettings: (0, eslint_devkit_1.formatLLMMessage)({
83
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
84
+ issueName: 'Insecure Cookie Configuration',
85
+ cwe: 'CWE-614',
86
+ description: 'Insecure cookie settings detected: {{issue}}',
87
+ severity: 'HIGH',
88
+ fix: '{{safeAlternative}}',
89
+ documentationLink: 'https://cwe.mitre.org/data/definitions/614.html',
90
+ }),
91
+ addSecureFlags: (0, eslint_devkit_1.formatLLMMessage)({
92
+ icon: eslint_devkit_1.MessageIcons.INFO,
93
+ issueName: 'Add Secure Flags',
94
+ description: 'Set secure cookie flags',
95
+ severity: 'LOW',
96
+ fix: '{ httpOnly: true, secure: true, sameSite: "strict" }',
97
+ documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security',
98
+ }),
99
+ },
100
+ schema: [
101
+ {
102
+ type: 'object',
103
+ properties: {
104
+ allowInTests: {
105
+ type: 'boolean',
106
+ default: false,
107
+ description: 'Allow insecure cookies in test files',
108
+ },
109
+ cookieLibraries: {
110
+ type: 'array',
111
+ items: { type: 'string' },
112
+ default: [],
113
+ description: 'Cookie library patterns to recognize',
114
+ },
115
+ ignorePatterns: {
116
+ type: 'array',
117
+ items: { type: 'string' },
118
+ default: [],
119
+ description: 'Additional safe patterns to ignore',
120
+ },
121
+ },
122
+ additionalProperties: false,
123
+ },
124
+ ],
125
+ },
126
+ defaultOptions: [
127
+ {
128
+ allowInTests: false,
129
+ cookieLibraries: [],
130
+ ignorePatterns: [],
131
+ },
132
+ ],
133
+ create(context, [options = {}]) {
134
+ const { allowInTests = false, ignorePatterns = [], } = options;
135
+ const filename = context.getFilename();
136
+ const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
137
+ const sourceCode = context.sourceCode || context.sourceCode;
138
+ function checkObjectExpression(node) {
139
+ if (isTestFile) {
140
+ return;
141
+ }
142
+ // Check if this ObjectExpression is the third argument of a cookie call
143
+ // First, check if parent is directly a CallExpression
144
+ if (node.parent && node.parent.type === 'CallExpression') {
145
+ const parentCall = node.parent;
146
+ const callee = parentCall.callee;
147
+ // Check if it's a cookie call
148
+ if (callee.type === 'MemberExpression' &&
149
+ callee.property.type === 'Identifier' &&
150
+ callee.property.name === 'cookie') {
151
+ // Check if this node is the third argument (index 2)
152
+ // Use both reference check and range check for reliability
153
+ const thirdArg = parentCall.arguments.length >= 3 ? parentCall.arguments[2] : null;
154
+ const isThirdArg = thirdArg && (thirdArg === node ||
155
+ (thirdArg.type === 'ObjectExpression' &&
156
+ thirdArg.range[0] === node.range[0] &&
157
+ thirdArg.range[1] === node.range[1]));
158
+ if (isThirdArg) {
159
+ // Check if the parent call is ignored
160
+ const callText = sourceCode.getText(parentCall);
161
+ if (matchesIgnorePattern(callText, ignorePatterns)) {
162
+ return;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ // If not handled above, check if it's inside a cookie config using helper
168
+ if (!isInsideCookieConfig(node, sourceCode)) {
169
+ return;
170
+ }
171
+ // If it's inside a cookie config, check it
172
+ const text = sourceCode.getText(node);
173
+ // Check if it matches any ignore pattern
174
+ if (matchesIgnorePattern(text, ignorePatterns)) {
175
+ return;
176
+ }
177
+ const { hasHttpOnly, hasSecure, hasSameSite } = hasSecureCookieSettings(node, sourceCode);
178
+ const issues = [];
179
+ if (!hasHttpOnly) {
180
+ issues.push('missing httpOnly flag');
181
+ }
182
+ if (!hasSecure) {
183
+ issues.push('missing secure flag');
184
+ }
185
+ if (!hasSameSite) {
186
+ issues.push('missing sameSite flag');
187
+ }
188
+ if (issues.length > 0) {
189
+ const issueDescription = issues.join(', ');
190
+ const safeAlternative = 'Set httpOnly: true, secure: true, sameSite: "strict"';
191
+ context.report({
192
+ node,
193
+ messageId: 'insecureCookieSettings',
194
+ data: {
195
+ issue: issueDescription,
196
+ safeAlternative,
197
+ },
198
+ suggest: [
199
+ {
200
+ messageId: 'addSecureFlags',
201
+ fix(fixer) {
202
+ // Find the last property in the object
203
+ const properties = node.properties;
204
+ if (properties.length === 0) {
205
+ // Empty object - add all flags
206
+ return fixer.replaceText(node, '{ httpOnly: true, secure: true, sameSite: "strict" }');
207
+ }
208
+ const lastProperty = properties[properties.length - 1];
209
+ const lastPropertyText = sourceCode.getText(lastProperty);
210
+ const needsComma = !lastPropertyText.trim().endsWith(',');
211
+ const insertPosition = lastProperty.range[1];
212
+ const missingFlags = [];
213
+ if (!hasHttpOnly)
214
+ missingFlags.push('httpOnly: true');
215
+ if (!hasSecure)
216
+ missingFlags.push('secure: true');
217
+ if (!hasSameSite)
218
+ missingFlags.push('sameSite: "strict"');
219
+ const prefix = needsComma ? ',' : '';
220
+ const insertion = prefix + '\n ' + missingFlags.join(',\n ');
221
+ return fixer.insertTextAfterRange([insertPosition, insertPosition], insertion);
222
+ },
223
+ },
224
+ ],
225
+ });
226
+ }
227
+ }
228
+ function checkCallExpression(node) {
229
+ if (isTestFile) {
230
+ return;
231
+ }
232
+ const callee = node.callee;
233
+ const callText = sourceCode.getText(node);
234
+ // Check if it matches any ignore pattern
235
+ if (matchesIgnorePattern(callText, ignorePatterns)) {
236
+ return;
237
+ }
238
+ // Check for res.cookie() calls or cookies.set() calls
239
+ const isResCookie = callee.type === 'MemberExpression' &&
240
+ callee.property.type === 'Identifier' &&
241
+ callee.property.name === 'cookie';
242
+ const isUniversalCookie = callee.type === 'MemberExpression' &&
243
+ callee.property.type === 'Identifier' &&
244
+ callee.property.name === 'set' &&
245
+ callee.object.type === 'Identifier' &&
246
+ (callee.object.name === 'cookies' || callee.object.name === 'cookie');
247
+ if (isResCookie || isUniversalCookie) {
248
+ // Check if third argument (options) is provided
249
+ if (node.arguments.length < 3) {
250
+ context.report({
251
+ node,
252
+ messageId: 'insecureCookieSettings',
253
+ data: {
254
+ issue: 'missing cookie options with httpOnly, secure, and sameSite flags',
255
+ safeAlternative: 'Add options object: res.cookie(name, value, { httpOnly: true, secure: true, sameSite: "strict" })',
256
+ },
257
+ suggest: [
258
+ {
259
+ messageId: 'addSecureFlags',
260
+ fix(fixer) {
261
+ // Add options as third argument
262
+ const lastArg = node.arguments[node.arguments.length - 1];
263
+ const insertPosition = lastArg.range[1];
264
+ return fixer.insertTextAfterRange([insertPosition, insertPosition], `, { httpOnly: true, secure: true, sameSite: "strict" }`);
265
+ },
266
+ },
267
+ ],
268
+ });
269
+ return;
270
+ }
271
+ }
272
+ }
273
+ function checkAssignmentExpression(node) {
274
+ if (isTestFile) {
275
+ return;
276
+ }
277
+ // Check for document.cookie assignments
278
+ if (node.left.type === 'MemberExpression' &&
279
+ node.left.object.type === 'Identifier' &&
280
+ node.left.object.name === 'document' &&
281
+ node.left.property.type === 'Identifier' &&
282
+ node.left.property.name === 'cookie') {
283
+ const text = sourceCode.getText(node);
284
+ // Check if it matches any ignore pattern
285
+ if (matchesIgnorePattern(text, ignorePatterns)) {
286
+ return;
287
+ }
288
+ context.report({
289
+ node,
290
+ messageId: 'insecureCookieSettings',
291
+ data: {
292
+ issue: 'using document.cookie directly (cannot set httpOnly flag)',
293
+ safeAlternative: 'Use server-side cookie setting with httpOnly: true, secure: true, sameSite: "strict"',
294
+ },
295
+ });
296
+ }
297
+ }
298
+ return {
299
+ ObjectExpression: checkObjectExpression,
300
+ CallExpression: checkCallExpression,
301
+ AssignmentExpression: checkAssignmentExpression,
302
+ };
303
+ },
304
+ });
305
+ //# sourceMappingURL=no-insecure-cookie-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-insecure-cookie-settings.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-secure-coding/src/rules/security/no-insecure-cookie-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type SecurityRuleOptions } from '@interlace/eslint-devkit';
2
+ export interface Options extends SecurityRuleOptions {
3
+ /** Allow specific insecure algorithms for legacy compatibility. Default: [] (strict) */
4
+ allowedInsecureAlgorithms?: string[];
5
+ /** Minimum key length for HMAC secrets. Default: 32 (256 bits) */
6
+ minSecretLength?: number;
7
+ /** JWT libraries to consider safe. Default: jsonwebtoken, jose, jwt */
8
+ trustedJwtLibraries?: string[];
9
+ }
10
+ export declare const noInsecureJwt: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noInsecureJwt = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
+ const eslint_devkit_3 = require("@interlace/eslint-devkit");
7
+ exports.noInsecureJwt = (0, eslint_devkit_1.createRule)({
8
+ name: 'no-insecure-jwt',
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Detects insecure JWT operations and missing signature verification',
13
+ },
14
+ fixable: 'code',
15
+ hasSuggestions: true,
16
+ messages: {
17
+ insecureJwtAlgorithm: (0, eslint_devkit_2.formatLLMMessage)({
18
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
19
+ issueName: 'Insecure JWT Algorithm',
20
+ cwe: 'CWE-347',
21
+ description: 'JWT algorithm confusion vulnerability',
22
+ severity: 'CRITICAL',
23
+ fix: 'Use RS256/ES256 and validate algorithm before verification',
24
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
25
+ }),
26
+ missingSignatureVerification: (0, eslint_devkit_2.formatLLMMessage)({
27
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
28
+ issueName: 'Missing JWT Signature Verification',
29
+ cwe: 'CWE-347',
30
+ description: 'JWT parsed without signature verification',
31
+ severity: 'CRITICAL',
32
+ fix: 'Use jwt.verify() instead of jwt.decode()',
33
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
34
+ }),
35
+ weakJwtSecret: (0, eslint_devkit_2.formatLLMMessage)({
36
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
37
+ issueName: 'Weak JWT Secret',
38
+ cwe: 'CWE-347',
39
+ description: 'JWT signed with weak/insufficient secret',
40
+ severity: 'HIGH',
41
+ fix: 'Use minimum 256-bit secret (32+ characters)',
42
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
43
+ }),
44
+ jwtWithoutValidation: (0, eslint_devkit_2.formatLLMMessage)({
45
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
46
+ issueName: 'JWT Without Validation',
47
+ cwe: 'CWE-347',
48
+ description: 'JWT used without proper validation',
49
+ severity: 'HIGH',
50
+ fix: 'Verify JWT signature before trusting payload',
51
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
52
+ }),
53
+ unsafeJwtParsing: (0, eslint_devkit_2.formatLLMMessage)({
54
+ icon: eslint_devkit_2.MessageIcons.SECURITY,
55
+ issueName: 'Unsafe JWT Parsing',
56
+ cwe: 'CWE-347',
57
+ description: 'Unsafe JWT parsing pattern detected',
58
+ severity: 'MEDIUM',
59
+ fix: 'Use verified JWT libraries with proper error handling',
60
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
61
+ }),
62
+ useSecureJwtLibrary: (0, eslint_devkit_2.formatLLMMessage)({
63
+ icon: eslint_devkit_2.MessageIcons.INFO,
64
+ issueName: 'Use Secure JWT Library',
65
+ description: 'Use jsonwebtoken or jose library',
66
+ severity: 'LOW',
67
+ fix: 'npm install jsonwebtoken && use jwt.verify()',
68
+ documentationLink: 'https://www.npmjs.com/package/jsonwebtoken',
69
+ }),
70
+ verifyBeforeTrust: (0, eslint_devkit_2.formatLLMMessage)({
71
+ icon: eslint_devkit_2.MessageIcons.INFO,
72
+ issueName: 'Verify Before Trust',
73
+ description: 'Always verify JWT signature before using payload',
74
+ severity: 'LOW',
75
+ fix: 'jwt.verify(token, secret, callback)',
76
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
77
+ }),
78
+ strategyUseVerifiedLibrary: (0, eslint_devkit_2.formatLLMMessage)({
79
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
80
+ issueName: 'Verified Library Strategy',
81
+ description: 'Use battle-tested JWT libraries',
82
+ severity: 'LOW',
83
+ fix: 'Use jsonwebtoken, jose, or jwt libraries',
84
+ documentationLink: 'https://www.npmjs.com/package/jsonwebtoken',
85
+ }),
86
+ strategyValidateAlgorithm: (0, eslint_devkit_2.formatLLMMessage)({
87
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
88
+ issueName: 'Algorithm Validation Strategy',
89
+ description: 'Validate algorithms before use',
90
+ severity: 'LOW',
91
+ fix: 'Whitelist allowed algorithms: ["RS256", "ES256"]',
92
+ documentationLink: 'https://tools.ietf.org/html/rfc8725',
93
+ }),
94
+ strategyStrongSecrets: (0, eslint_devkit_2.formatLLMMessage)({
95
+ icon: eslint_devkit_2.MessageIcons.STRATEGY,
96
+ issueName: 'Strong Secrets Strategy',
97
+ description: 'Use cryptographically strong secrets',
98
+ severity: 'LOW',
99
+ fix: 'Generate 256-bit secrets: crypto.randomBytes(32)',
100
+ documentationLink: 'https://nodejs.org/api/crypto.html',
101
+ })
102
+ },
103
+ schema: [
104
+ {
105
+ type: 'object',
106
+ properties: {
107
+ allowedInsecureAlgorithms: {
108
+ type: 'array',
109
+ items: { type: 'string' },
110
+ default: [],
111
+ },
112
+ minSecretLength: {
113
+ type: 'number',
114
+ minimum: 16,
115
+ default: 32,
116
+ },
117
+ trustedJwtLibraries: {
118
+ type: 'array',
119
+ items: { type: 'string' },
120
+ default: ['jsonwebtoken', 'jose', 'jwt'],
121
+ },
122
+ trustedSanitizers: {
123
+ type: 'array',
124
+ items: { type: 'string' },
125
+ default: [],
126
+ description: 'Additional function names to consider as JWT validators',
127
+ },
128
+ trustedAnnotations: {
129
+ type: 'array',
130
+ items: { type: 'string' },
131
+ default: [],
132
+ description: 'Additional JSDoc annotations to consider as safe markers',
133
+ },
134
+ strictMode: {
135
+ type: 'boolean',
136
+ default: false,
137
+ description: 'Disable all false positive detection (strict mode)',
138
+ },
139
+ },
140
+ additionalProperties: false,
141
+ },
142
+ ],
143
+ },
144
+ defaultOptions: [
145
+ {
146
+ allowedInsecureAlgorithms: [],
147
+ minSecretLength: 32,
148
+ trustedJwtLibraries: ['jsonwebtoken', 'jose', 'jwt'],
149
+ trustedSanitizers: [],
150
+ trustedAnnotations: [],
151
+ strictMode: false,
152
+ },
153
+ ],
154
+ create(context) {
155
+ const options = context.options[0] || {};
156
+ const { minSecretLength = 32, trustedJwtLibraries = ['jsonwebtoken', 'jose', 'jwt'], trustedSanitizers = [], trustedAnnotations = [], strictMode = false, } = options;
157
+ const sourceCode = context.sourceCode || context.sourceCode;
158
+ const filename = context.filename || context.getFilename();
159
+ // Create safety checker for false positive detection
160
+ const safetyChecker = (0, eslint_devkit_3.createSafetyChecker)({
161
+ trustedSanitizers,
162
+ trustedAnnotations,
163
+ trustedOrmPatterns: [],
164
+ strictMode,
165
+ });
166
+ /**
167
+ * Check if secret/key is weak
168
+ */
169
+ const isWeakSecret = (secretNode) => {
170
+ if (secretNode.type === 'Literal' && typeof secretNode.value === 'string') {
171
+ return secretNode.value.length < minSecretLength;
172
+ }
173
+ return false; // Can't determine strength of non-literal secrets
174
+ };
175
+ /**
176
+ * Check if JWT operation has signature verification
177
+ */
178
+ const hasSignatureVerification = (jwtCall) => {
179
+ // Check if it's jwt.verify() call
180
+ if (jwtCall.callee.type === 'MemberExpression' &&
181
+ jwtCall.callee.property.type === 'Identifier' &&
182
+ jwtCall.callee.property.name === 'verify') {
183
+ return true;
184
+ }
185
+ // Check for @verified annotation
186
+ return (0, eslint_devkit_3.hasSafeAnnotation)(jwtCall, context, trustedAnnotations);
187
+ };
188
+ /**
189
+ * Check if this is a trusted JWT library call
190
+ */
191
+ const isTrustedJwtLibrary = (node) => {
192
+ // Check if callee is a member expression (library.method)
193
+ if (node.callee.type !== 'MemberExpression') {
194
+ return false;
195
+ }
196
+ // Check if the object is a JWT library
197
+ const object = node.callee.object;
198
+ if (object.type === 'Identifier') {
199
+ return trustedJwtLibraries.includes(object.name.toLowerCase());
200
+ }
201
+ return false;
202
+ };
203
+ /**
204
+ * Extract JWT-related information from a call
205
+ */
206
+ const extractJwtInfo = (node) => {
207
+ const sourceText = sourceCode.getText(node);
208
+ // Check for algorithm specification
209
+ const hasAlgorithmSpec = /\b(algorithms?|alg)\s*:/i.test(sourceText);
210
+ // Check for insecure patterns
211
+ const hasNoneAlgorithm = /\b(alg|algorithms?)\s*:\s*['"`]\s*none\s*['"`]/i.test(sourceText);
212
+ const hasWeakAlgorithm = /\b(alg|algorithms?)\s*:\s*['"`]\s*(HS256|HS384|HS512)\s*['"`]/i.test(sourceText);
213
+ return {
214
+ sourceText,
215
+ hasAlgorithmSpec,
216
+ hasNoneAlgorithm,
217
+ hasWeakAlgorithm,
218
+ isDecodeCall: /\bdecode\b/i.test(sourceText),
219
+ isVerifyCall: /\bverify\b/i.test(sourceText),
220
+ };
221
+ };
222
+ return {
223
+ // Check JWT library method calls
224
+ CallExpression(node) {
225
+ if (!isTrustedJwtLibrary(node)) {
226
+ return;
227
+ }
228
+ const jwtInfo = extractJwtInfo(node);
229
+ // CRITICAL: Algorithm confusion attack (alg: "none")
230
+ if (jwtInfo.hasNoneAlgorithm) {
231
+ context.report({
232
+ node,
233
+ messageId: 'insecureJwtAlgorithm',
234
+ data: {
235
+ filePath: filename,
236
+ line: String(node.loc?.start.line ?? 0),
237
+ },
238
+ });
239
+ return;
240
+ }
241
+ // HIGH: Weak algorithm without proper key validation
242
+ if (jwtInfo.hasWeakAlgorithm && node.arguments.length >= 2) {
243
+ const secretArg = node.arguments[1];
244
+ if (isWeakSecret(secretArg)) {
245
+ context.report({
246
+ node,
247
+ messageId: 'weakJwtSecret',
248
+ data: {
249
+ filePath: filename,
250
+ line: String(node.loc?.start.line ?? 0),
251
+ },
252
+ });
253
+ return;
254
+ }
255
+ }
256
+ // CRITICAL: Using jwt.decode() instead of jwt.verify()
257
+ if (jwtInfo.isDecodeCall && !jwtInfo.isVerifyCall) {
258
+ // FALSE POSITIVE REDUCTION: Skip if annotated as safe
259
+ if (safetyChecker.isSafe(node, context)) {
260
+ return;
261
+ }
262
+ context.report({
263
+ node,
264
+ messageId: 'missingSignatureVerification',
265
+ data: {
266
+ filePath: filename,
267
+ line: String(node.loc?.start.line ?? 0),
268
+ },
269
+ suggest: [
270
+ {
271
+ messageId: 'verifyBeforeTrust',
272
+ fix: () => null // Could be complex to auto-fix
273
+ },
274
+ ],
275
+ });
276
+ }
277
+ },
278
+ // Check for JWT-related variable declarations and assignments
279
+ VariableDeclarator(node) {
280
+ if (!node.init || node.init.type !== 'CallExpression') {
281
+ return;
282
+ }
283
+ const initCall = node.init;
284
+ if (!isTrustedJwtLibrary(initCall)) {
285
+ return;
286
+ }
287
+ const jwtInfo = extractJwtInfo(initCall);
288
+ // Variable assigned with unverified JWT data
289
+ if (jwtInfo.isDecodeCall && !jwtInfo.isVerifyCall) {
290
+ // FALSE POSITIVE REDUCTION
291
+ if (safetyChecker.isSafe(initCall, context)) {
292
+ return;
293
+ }
294
+ context.report({
295
+ node,
296
+ messageId: 'jwtWithoutValidation',
297
+ data: {
298
+ filePath: filename,
299
+ line: String(node.loc?.start.line ?? 0),
300
+ },
301
+ });
302
+ }
303
+ },
304
+ // Check for JWT token usage without verification
305
+ Literal(node) {
306
+ if (typeof node.value !== 'string') {
307
+ return;
308
+ }
309
+ const value = node.value;
310
+ // Look for JWT patterns in strings
311
+ if (value.includes('eyJ') && value.split('.').length === 3) { // JWT structure
312
+ // Check if this JWT is used unsafely
313
+ let current = node;
314
+ let isVerified = false;
315
+ // Walk up to find if this is within a verified JWT operation
316
+ while (current && !isVerified) {
317
+ if (current.type === 'CallExpression' && hasSignatureVerification(current)) {
318
+ isVerified = true;
319
+ break;
320
+ }
321
+ current = current.parent;
322
+ }
323
+ if (!isVerified && !safetyChecker.isSafe(node, context)) {
324
+ context.report({
325
+ node,
326
+ messageId: 'unsafeJwtParsing',
327
+ data: {
328
+ filePath: filename,
329
+ line: String(node.loc?.start.line ?? 0),
330
+ },
331
+ });
332
+ }
333
+ }
334
+ }
335
+ };
336
+ },
337
+ });
338
+ //# sourceMappingURL=no-insecure-jwt.js.map