eslint-plugin-jwt-security 2.3.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 (79) hide show
  1. package/AGENTS.md +102 -0
  2. package/CHANGELOG.md +161 -0
  3. package/LICENSE +23 -0
  4. package/README.md +158 -0
  5. package/dist/AGENTS.md +102 -0
  6. package/dist/CHANGELOG.md +161 -0
  7. package/dist/LICENSE +23 -0
  8. package/dist/README.md +158 -0
  9. package/dist/package.json +125 -0
  10. package/dist/src/index.d.ts +101 -0
  11. package/dist/src/index.js +171 -0
  12. package/dist/src/oxlint.d.ts +20 -0
  13. package/dist/src/oxlint.js +21 -0
  14. package/dist/src/rules/no-algorithm-confusion/index.d.ts +24 -0
  15. package/dist/src/rules/no-algorithm-confusion/index.js +165 -0
  16. package/dist/src/rules/no-algorithm-none/index.d.ts +24 -0
  17. package/dist/src/rules/no-algorithm-none/index.js +222 -0
  18. package/dist/src/rules/no-decode-without-verify/index.d.ts +23 -0
  19. package/dist/src/rules/no-decode-without-verify/index.js +131 -0
  20. package/dist/src/rules/no-hardcoded-secret/index.d.ts +23 -0
  21. package/dist/src/rules/no-hardcoded-secret/index.js +189 -0
  22. package/dist/src/rules/no-sensitive-payload/index.d.ts +20 -0
  23. package/dist/src/rules/no-sensitive-payload/index.js +103 -0
  24. package/dist/src/rules/no-timestamp-manipulation/index.d.ts +23 -0
  25. package/dist/src/rules/no-timestamp-manipulation/index.js +97 -0
  26. package/dist/src/rules/no-weak-secret/index.d.ts +23 -0
  27. package/dist/src/rules/no-weak-secret/index.js +165 -0
  28. package/dist/src/rules/require-algorithm-whitelist/index.d.ts +24 -0
  29. package/dist/src/rules/require-algorithm-whitelist/index.js +113 -0
  30. package/dist/src/rules/require-audience-validation/index.d.ts +20 -0
  31. package/dist/src/rules/require-audience-validation/index.js +100 -0
  32. package/dist/src/rules/require-expiration/index.d.ts +23 -0
  33. package/dist/src/rules/require-expiration/index.js +136 -0
  34. package/dist/src/rules/require-issued-at/index.d.ts +23 -0
  35. package/dist/src/rules/require-issued-at/index.js +113 -0
  36. package/dist/src/rules/require-issuer-validation/index.d.ts +20 -0
  37. package/dist/src/rules/require-issuer-validation/index.js +100 -0
  38. package/dist/src/rules/require-max-age/index.d.ts +24 -0
  39. package/dist/src/rules/require-max-age/index.js +93 -0
  40. package/dist/src/types/index.d.ts +82 -0
  41. package/dist/src/types/index.js +7 -0
  42. package/dist/src/utils/index.d.ts +98 -0
  43. package/dist/src/utils/index.js +238 -0
  44. package/package.json +125 -0
  45. package/src/__compatibility__/jwt-interface.spec.ts +192 -0
  46. package/src/index.test.ts +47 -0
  47. package/src/index.ts +209 -0
  48. package/src/oxlint.test.ts +61 -0
  49. package/src/oxlint.ts +23 -0
  50. package/src/rules/no-algorithm-confusion/index.ts +208 -0
  51. package/src/rules/no-algorithm-confusion/no-algorithm-confusion.test.ts +106 -0
  52. package/src/rules/no-algorithm-none/index.ts +270 -0
  53. package/src/rules/no-algorithm-none/no-algorithm-none.test.ts +146 -0
  54. package/src/rules/no-decode-without-verify/index.ts +162 -0
  55. package/src/rules/no-decode-without-verify/no-decode-without-verify.test.ts +76 -0
  56. package/src/rules/no-hardcoded-secret/index.ts +230 -0
  57. package/src/rules/no-hardcoded-secret/no-hardcoded-secret.test.ts +97 -0
  58. package/src/rules/no-sensitive-payload/index.ts +123 -0
  59. package/src/rules/no-sensitive-payload/no-sensitive-payload.test.ts +157 -0
  60. package/src/rules/no-timestamp-manipulation/index.ts +118 -0
  61. package/src/rules/no-timestamp-manipulation/no-timestamp-manipulation.test.ts +54 -0
  62. package/src/rules/no-weak-secret/index.ts +204 -0
  63. package/src/rules/no-weak-secret/no-weak-secret.test.ts +114 -0
  64. package/src/rules/require-algorithm-whitelist/index.ts +145 -0
  65. package/src/rules/require-algorithm-whitelist/require-algorithm-whitelist.test.ts +72 -0
  66. package/src/rules/require-audience-validation/index.ts +120 -0
  67. package/src/rules/require-audience-validation/require-audience-validation.test.ts +68 -0
  68. package/src/rules/require-expiration/index.ts +170 -0
  69. package/src/rules/require-expiration/require-expiration.test.ts +135 -0
  70. package/src/rules/require-issued-at/index.ts +143 -0
  71. package/src/rules/require-issued-at/require-issued-at.test.ts +64 -0
  72. package/src/rules/require-issuer-validation/index.ts +120 -0
  73. package/src/rules/require-issuer-validation/require-issuer-validation.test.ts +68 -0
  74. package/src/rules/require-max-age/index.ts +117 -0
  75. package/src/rules/require-max-age/require-max-age.test.ts +69 -0
  76. package/src/tests/coverage-dual-layer.test.ts +323 -0
  77. package/src/types/index.ts +107 -0
  78. package/src/utils/index.ts +285 -0
  79. package/src/utils/utils.test.ts +324 -0
@@ -0,0 +1,107 @@
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
+ /**
8
+ * eslint-plugin-jwt-security Type Exports
9
+ *
10
+ * Barrel file that exports all JWT security rule Options types.
11
+ */
12
+
13
+ import type { SecurityRuleOptions } from '@interlace/eslint-devkit';
14
+
15
+ // Base options interface for JWT rules
16
+ export interface JwtRuleOptions extends SecurityRuleOptions {
17
+ /** JWT libraries to consider for detection. Default: all supported libraries */
18
+ targetLibraries?: Array<
19
+ | 'jsonwebtoken'
20
+ | 'jose'
21
+ | 'express-jwt'
22
+ | '@nestjs/jwt'
23
+ | 'jwks-rsa'
24
+ | 'jwt-decode'
25
+ >;
26
+ }
27
+
28
+ // Rule-specific options
29
+ export interface NoAlgorithmNoneOptions extends JwtRuleOptions {
30
+ /** Allow 'none' algorithm in test files. Default: false */
31
+ allowInTests?: boolean;
32
+ }
33
+
34
+ export interface NoAlgorithmConfusionOptions extends JwtRuleOptions {
35
+ /** Symmetric algorithms that should not be used with public keys */
36
+ symmetricAlgorithms?: string[];
37
+ }
38
+
39
+ export interface RequireAlgorithmWhitelistOptions extends JwtRuleOptions {
40
+ /** Recommended algorithms to suggest. Default: ['RS256', 'ES256'] */
41
+ recommendedAlgorithms?: string[];
42
+ }
43
+
44
+ export interface NoDecodeWithoutVerifyOptions extends JwtRuleOptions {
45
+ /** Allow decode() for header inspection before verify. Default: false */
46
+ allowHeaderInspection?: boolean;
47
+ }
48
+
49
+ export interface NoWeakSecretOptions extends JwtRuleOptions {
50
+ /** Minimum secret length in characters. Default: 32 (256 bits) */
51
+ minSecretLength?: number;
52
+ }
53
+
54
+ export interface RequireExpirationOptions extends JwtRuleOptions {
55
+ /** Maximum allowed expiration time in seconds. Default: 86400 (24 hours) */
56
+ maxExpirationSeconds?: number;
57
+ }
58
+
59
+ export interface RequireIssuerValidationOptions extends JwtRuleOptions {
60
+ /** Known valid issuers to suggest. Default: [] */
61
+ knownIssuers?: string[];
62
+ }
63
+
64
+ export interface RequireAudienceValidationOptions extends JwtRuleOptions {
65
+ /** Known valid audiences to suggest. Default: [] */
66
+ knownAudiences?: string[];
67
+ }
68
+
69
+ export interface NoSensitivePayloadOptions extends JwtRuleOptions {
70
+ /** Additional field names to flag as sensitive. Default: [] */
71
+ additionalSensitiveFields?: string[];
72
+ }
73
+
74
+ export interface NoHardcodedSecretOptions extends JwtRuleOptions {
75
+ /** Patterns that indicate environment variable usage (safe). Default: process.env */
76
+ envPatterns?: string[];
77
+ }
78
+
79
+ export interface RequireJwksValidationOptions extends JwtRuleOptions {
80
+ /** Require cache configuration. Default: true */
81
+ requireCache?: boolean;
82
+ /** Require rate limiting. Default: true */
83
+ requireRateLimit?: boolean;
84
+ }
85
+
86
+ export interface NoInsecureKidOptions extends JwtRuleOptions {
87
+ /** Allow custom KID validation functions. Default: [] */
88
+ trustedKidValidators?: string[];
89
+ }
90
+
91
+ /**
92
+ * Combined type for all JWT rule options
93
+ */
94
+ export type AllJwtRulesOptions = {
95
+ 'no-algorithm-none'?: NoAlgorithmNoneOptions;
96
+ 'no-algorithm-confusion'?: NoAlgorithmConfusionOptions;
97
+ 'require-algorithm-whitelist'?: RequireAlgorithmWhitelistOptions;
98
+ 'no-decode-without-verify'?: NoDecodeWithoutVerifyOptions;
99
+ 'no-weak-secret'?: NoWeakSecretOptions;
100
+ 'require-expiration'?: RequireExpirationOptions;
101
+ 'require-issuer-validation'?: RequireIssuerValidationOptions;
102
+ 'require-audience-validation'?: RequireAudienceValidationOptions;
103
+ 'no-sensitive-payload'?: NoSensitivePayloadOptions;
104
+ 'no-hardcoded-secret'?: NoHardcodedSecretOptions;
105
+ 'require-jwks-validation'?: RequireJwksValidationOptions;
106
+ 'no-insecure-kid'?: NoInsecureKidOptions;
107
+ };
@@ -0,0 +1,285 @@
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
+ /**
8
+ * Shared utilities for JWT security rules
9
+ *
10
+ * Library detection, pattern matching, and common helpers.
11
+ */
12
+ import type { TSESTree } from '@interlace/eslint-devkit';
13
+
14
+ /**
15
+ * Supported JWT libraries
16
+ */
17
+ export const JWT_LIBRARIES = {
18
+ JSONWEBTOKEN: 'jsonwebtoken',
19
+ JOSE: 'jose',
20
+ EXPRESS_JWT: 'express-jwt',
21
+ NESTJS_JWT: '@nestjs/jwt',
22
+ JWKS_RSA: 'jwks-rsa',
23
+ JWT_DECODE: 'jwt-decode',
24
+ } as const;
25
+
26
+ export type JwtLibrary = (typeof JWT_LIBRARIES)[keyof typeof JWT_LIBRARIES];
27
+
28
+ /**
29
+ * Insecure algorithms that should be flagged
30
+ */
31
+ export const INSECURE_ALGORITHMS = new Set([
32
+ 'none',
33
+ 'None',
34
+ 'NONE',
35
+ 'HS256', // Only insecure when used with public keys
36
+ 'HS384',
37
+ 'HS512',
38
+ ]);
39
+
40
+ /**
41
+ * Algorithms vulnerable to confusion attacks when used with asymmetric keys
42
+ */
43
+ export const SYMMETRIC_ALGORITHMS = new Set(['HS256', 'HS384', 'HS512']);
44
+
45
+ /**
46
+ * Recommended secure algorithms
47
+ */
48
+ export const SECURE_ALGORITHMS = new Set([
49
+ 'RS256',
50
+ 'RS384',
51
+ 'RS512',
52
+ 'ES256',
53
+ 'ES384',
54
+ 'ES512',
55
+ 'PS256',
56
+ 'PS384',
57
+ 'PS512',
58
+ 'EdDSA',
59
+ ]);
60
+
61
+ /**
62
+ * Sensitive field names that should not be in JWT payload
63
+ * All lowercase for case-insensitive matching
64
+ */
65
+ export const SENSITIVE_PAYLOAD_FIELDS = new Set([
66
+ // Passwords
67
+ 'password', 'passwd', 'pwd', 'pass', 'secret',
68
+ // API Keys
69
+ 'apikey', 'api_key', 'api-key',
70
+ 'apisecret', 'api_secret', 'api-secret',
71
+ // Tokens
72
+ 'accesstoken', 'access_token', 'access-token',
73
+ 'refreshtoken', 'refresh_token', 'refresh-token',
74
+ 'bearertoken', 'bearer_token', 'bearer-token',
75
+ // Keys
76
+ 'privatekey', 'private_key', 'private-key',
77
+ 'secretkey', 'secret_key', 'secret-key',
78
+ // PII - Personal Identifiable Information
79
+ 'email', 'emailaddress', 'email_address', 'email-address',
80
+ 'phone', 'phonenumber', 'phone_number', 'phone-number',
81
+ 'ssn', 'socialsecuritynumber', 'social_security_number',
82
+ 'dob', 'dateofbirth', 'date_of_birth', 'birthdate',
83
+ 'address', 'streetaddress', 'street_address',
84
+ // Financial
85
+ 'creditcard', 'credit_card', 'credit-card',
86
+ 'cardnumber', 'card_number', 'card-number',
87
+ 'cvv', 'cvc', 'securitycode', 'security_code',
88
+ 'pin', 'pincode', 'pin_code',
89
+ 'bankaccount', 'bank_account', 'bank-account',
90
+ 'accountnumber', 'account_number', 'account-number',
91
+ 'routingnumber', 'routing_number', 'routing-number',
92
+ ]);
93
+
94
+ /**
95
+ * JWT method patterns for different operations
96
+ */
97
+ export const JWT_METHODS = {
98
+ SIGN: new Set(['sign', 'signJWT', 'SignJWT']),
99
+ VERIFY: new Set(['verify', 'verifyJWT', 'jwtVerify']),
100
+ DECODE: new Set(['decode', 'jwtDecode', 'decodeJWT']),
101
+ } as const;
102
+
103
+ /**
104
+ * Check if a node represents a call to a JWT library method
105
+ */
106
+ export function isJwtLibraryCall(
107
+ node: TSESTree.CallExpression,
108
+ targetMethods: Set<string>
109
+ ): boolean {
110
+ // Check member expression: jwt.verify(), jose.jwtVerify()
111
+ if (node.callee.type === 'MemberExpression') {
112
+ const property = node.callee.property;
113
+ if (property.type === 'Identifier') {
114
+ return targetMethods.has(property.name);
115
+ }
116
+ }
117
+
118
+ // Check direct calls: jwtVerify(), jwtDecode()
119
+ if (node.callee.type === 'Identifier') {
120
+ return targetMethods.has(node.callee.name);
121
+ }
122
+
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Check if a node is a string literal with a specific value
128
+ */
129
+ export function isStringLiteral(
130
+ node: TSESTree.Node,
131
+ value?: string
132
+ ): node is TSESTree.Literal {
133
+ if (node.type !== 'Literal' || typeof node.value !== 'string') {
134
+ return false;
135
+ }
136
+ return value === undefined || node.value === value;
137
+ }
138
+
139
+ /**
140
+ * Extract algorithm from options object
141
+ */
142
+ export function extractAlgorithms(
143
+ optionsNode: TSESTree.ObjectExpression
144
+ ): string[] {
145
+ const algorithms: string[] = [];
146
+
147
+ for (const prop of optionsNode.properties) {
148
+ if (prop.type !== 'Property' || prop.key.type !== 'Identifier') {
149
+ continue;
150
+ }
151
+
152
+ const keyName = prop.key.name;
153
+ if (keyName !== 'algorithms' && keyName !== 'algorithm' && keyName !== 'alg') {
154
+ continue;
155
+ }
156
+
157
+ // Single algorithm: { algorithm: 'HS256' }
158
+ if (prop.value.type === 'Literal' && typeof prop.value.value === 'string') {
159
+ algorithms.push(prop.value.value);
160
+ }
161
+
162
+ // Array of algorithms: { algorithms: ['RS256', 'ES256'] }
163
+ if (prop.value.type === 'ArrayExpression') {
164
+ for (const elem of prop.value.elements) {
165
+ if (elem && elem.type === 'Literal' && typeof elem.value === 'string') {
166
+ algorithms.push(elem.value);
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return algorithms;
173
+ }
174
+
175
+ /**
176
+ * Check if options object has a specific property set
177
+ */
178
+ export function hasOption(
179
+ optionsNode: TSESTree.ObjectExpression,
180
+ optionName: string
181
+ ): boolean {
182
+ return optionsNode.properties.some(
183
+ (prop): prop is TSESTree.Property =>
184
+ prop.type === 'Property' &&
185
+ prop.key.type === 'Identifier' &&
186
+ prop.key.name === optionName
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Get the value of a specific option from options object
192
+ */
193
+ export function getOptionValue(
194
+ optionsNode: TSESTree.ObjectExpression,
195
+ optionName: string
196
+ ): TSESTree.Node | undefined {
197
+ for (const prop of optionsNode.properties) {
198
+ if (
199
+ prop.type === 'Property' &&
200
+ prop.key.type === 'Identifier' &&
201
+ prop.key.name === optionName
202
+ ) {
203
+ return prop.value;
204
+ }
205
+ }
206
+ return undefined;
207
+ }
208
+
209
+ /**
210
+ * Check if a literal appears to be a weak secret (short string)
211
+ */
212
+ export function isWeakSecret(
213
+ node: TSESTree.Node,
214
+ minLength = 32
215
+ ): boolean {
216
+ if (node.type === 'Literal' && typeof node.value === 'string') {
217
+ return node.value.length < minLength;
218
+ }
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Check if a node is an environment variable access (safe pattern)
224
+ */
225
+ export function isEnvVariable(node: TSESTree.Node): boolean {
226
+ // process.env.JWT_SECRET
227
+ if (
228
+ node.type === 'MemberExpression' &&
229
+ node.object.type === 'MemberExpression' &&
230
+ node.object.object.type === 'Identifier' &&
231
+ node.object.object.name === 'process' &&
232
+ node.object.property.type === 'Identifier' &&
233
+ node.object.property.name === 'env'
234
+ ) {
235
+ return true;
236
+ }
237
+
238
+ return false;
239
+ }
240
+
241
+ /**
242
+ * Check if this call looks like a JWT sign operation
243
+ */
244
+ export function isSignOperation(node: TSESTree.CallExpression): boolean {
245
+ return isJwtLibraryCall(node, JWT_METHODS.SIGN);
246
+ }
247
+
248
+ /**
249
+ * Check if this call looks like a JWT verify operation
250
+ */
251
+ export function isVerifyOperation(node: TSESTree.CallExpression): boolean {
252
+ return isJwtLibraryCall(node, JWT_METHODS.VERIFY);
253
+ }
254
+
255
+ /**
256
+ * Check if this call looks like a JWT decode operation (no verification)
257
+ */
258
+ export function isDecodeOperation(node: TSESTree.CallExpression): boolean {
259
+ return isJwtLibraryCall(node, JWT_METHODS.DECODE);
260
+ }
261
+
262
+ /**
263
+ * Get the options argument from a JWT call
264
+ * For jwt.verify(token, secret, options) -> returns options
265
+ * For jwt.sign(payload, secret, options) -> returns options
266
+ */
267
+ export function getOptionsArgument(
268
+ node: TSESTree.CallExpression,
269
+ optionsIndex = 2
270
+ ): TSESTree.ObjectExpression | undefined {
271
+ const arg = node.arguments[optionsIndex];
272
+ if (arg && arg.type === 'ObjectExpression') {
273
+ return arg;
274
+ }
275
+ return undefined;
276
+ }
277
+
278
+ /**
279
+ * Check if file is a test file
280
+ */
281
+ export function isTestFile(filename: string): boolean {
282
+ return /\.(test|spec)\.[jt]sx?$/.test(filename) ||
283
+ /__(tests?|mocks?)__/.test(filename) ||
284
+ /\/(tests?|specs?|__tests__)\//.test(filename);
285
+ }