eslint-plugin-jwt-security 2.3.0 → 2.3.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 (97) hide show
  1. package/README.md +5 -5
  2. package/package.json +16 -24
  3. package/src/index.d.ts +17 -0
  4. package/src/index.js +1 -0
  5. package/src/oxlint.d.ts +17 -0
  6. package/src/oxlint.js +1 -0
  7. package/src/rules/no-algorithm-confusion/index.js +1 -0
  8. package/src/rules/no-algorithm-none/index.js +1 -0
  9. package/src/rules/no-decode-without-verify/index.js +1 -0
  10. package/src/rules/no-hardcoded-secret/index.js +1 -0
  11. package/src/rules/no-sensitive-payload/index.js +1 -0
  12. package/src/rules/no-timestamp-manipulation/index.js +1 -0
  13. package/src/rules/no-weak-secret/index.js +1 -0
  14. package/src/rules/require-algorithm-whitelist/index.js +1 -0
  15. package/src/rules/require-audience-validation/index.js +1 -0
  16. package/src/rules/require-expiration/index.js +1 -0
  17. package/src/rules/require-issued-at/index.js +1 -0
  18. package/src/rules/require-issuer-validation/index.js +1 -0
  19. package/src/rules/require-max-age/index.js +1 -0
  20. package/src/types/index.js +1 -0
  21. package/src/utils/index.js +1 -0
  22. package/AGENTS.md +0 -102
  23. package/CHANGELOG.md +0 -161
  24. package/dist/AGENTS.md +0 -102
  25. package/dist/CHANGELOG.md +0 -161
  26. package/dist/LICENSE +0 -23
  27. package/dist/README.md +0 -158
  28. package/dist/package.json +0 -125
  29. package/dist/src/index.d.ts +0 -101
  30. package/dist/src/index.js +0 -171
  31. package/dist/src/oxlint.d.ts +0 -20
  32. package/dist/src/oxlint.js +0 -21
  33. package/dist/src/rules/no-algorithm-confusion/index.d.ts +0 -24
  34. package/dist/src/rules/no-algorithm-confusion/index.js +0 -165
  35. package/dist/src/rules/no-algorithm-none/index.d.ts +0 -24
  36. package/dist/src/rules/no-algorithm-none/index.js +0 -222
  37. package/dist/src/rules/no-decode-without-verify/index.d.ts +0 -23
  38. package/dist/src/rules/no-decode-without-verify/index.js +0 -131
  39. package/dist/src/rules/no-hardcoded-secret/index.d.ts +0 -23
  40. package/dist/src/rules/no-hardcoded-secret/index.js +0 -189
  41. package/dist/src/rules/no-sensitive-payload/index.d.ts +0 -20
  42. package/dist/src/rules/no-sensitive-payload/index.js +0 -103
  43. package/dist/src/rules/no-timestamp-manipulation/index.d.ts +0 -23
  44. package/dist/src/rules/no-timestamp-manipulation/index.js +0 -97
  45. package/dist/src/rules/no-weak-secret/index.d.ts +0 -23
  46. package/dist/src/rules/no-weak-secret/index.js +0 -165
  47. package/dist/src/rules/require-algorithm-whitelist/index.d.ts +0 -24
  48. package/dist/src/rules/require-algorithm-whitelist/index.js +0 -113
  49. package/dist/src/rules/require-audience-validation/index.d.ts +0 -20
  50. package/dist/src/rules/require-audience-validation/index.js +0 -100
  51. package/dist/src/rules/require-expiration/index.d.ts +0 -23
  52. package/dist/src/rules/require-expiration/index.js +0 -136
  53. package/dist/src/rules/require-issued-at/index.d.ts +0 -23
  54. package/dist/src/rules/require-issued-at/index.js +0 -113
  55. package/dist/src/rules/require-issuer-validation/index.d.ts +0 -20
  56. package/dist/src/rules/require-issuer-validation/index.js +0 -100
  57. package/dist/src/rules/require-max-age/index.d.ts +0 -24
  58. package/dist/src/rules/require-max-age/index.js +0 -93
  59. package/dist/src/types/index.js +0 -7
  60. package/dist/src/utils/index.d.ts +0 -98
  61. package/dist/src/utils/index.js +0 -238
  62. package/src/__compatibility__/jwt-interface.spec.ts +0 -192
  63. package/src/index.test.ts +0 -47
  64. package/src/index.ts +0 -209
  65. package/src/oxlint.test.ts +0 -61
  66. package/src/oxlint.ts +0 -23
  67. package/src/rules/no-algorithm-confusion/index.ts +0 -208
  68. package/src/rules/no-algorithm-confusion/no-algorithm-confusion.test.ts +0 -106
  69. package/src/rules/no-algorithm-none/index.ts +0 -270
  70. package/src/rules/no-algorithm-none/no-algorithm-none.test.ts +0 -146
  71. package/src/rules/no-decode-without-verify/index.ts +0 -162
  72. package/src/rules/no-decode-without-verify/no-decode-without-verify.test.ts +0 -76
  73. package/src/rules/no-hardcoded-secret/index.ts +0 -230
  74. package/src/rules/no-hardcoded-secret/no-hardcoded-secret.test.ts +0 -97
  75. package/src/rules/no-sensitive-payload/index.ts +0 -123
  76. package/src/rules/no-sensitive-payload/no-sensitive-payload.test.ts +0 -157
  77. package/src/rules/no-timestamp-manipulation/index.ts +0 -118
  78. package/src/rules/no-timestamp-manipulation/no-timestamp-manipulation.test.ts +0 -54
  79. package/src/rules/no-weak-secret/index.ts +0 -204
  80. package/src/rules/no-weak-secret/no-weak-secret.test.ts +0 -114
  81. package/src/rules/require-algorithm-whitelist/index.ts +0 -145
  82. package/src/rules/require-algorithm-whitelist/require-algorithm-whitelist.test.ts +0 -72
  83. package/src/rules/require-audience-validation/index.ts +0 -120
  84. package/src/rules/require-audience-validation/require-audience-validation.test.ts +0 -68
  85. package/src/rules/require-expiration/index.ts +0 -170
  86. package/src/rules/require-expiration/require-expiration.test.ts +0 -135
  87. package/src/rules/require-issued-at/index.ts +0 -143
  88. package/src/rules/require-issued-at/require-issued-at.test.ts +0 -64
  89. package/src/rules/require-issuer-validation/index.ts +0 -120
  90. package/src/rules/require-issuer-validation/require-issuer-validation.test.ts +0 -68
  91. package/src/rules/require-max-age/index.ts +0 -117
  92. package/src/rules/require-max-age/require-max-age.test.ts +0 -69
  93. package/src/tests/coverage-dual-layer.test.ts +0 -323
  94. package/src/types/index.ts +0 -107
  95. package/src/utils/index.ts +0 -285
  96. package/src/utils/utils.test.ts +0 -324
  97. /package/{dist/src → src}/types/index.d.ts +0 -0
@@ -1,238 +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.JWT_METHODS = exports.SENSITIVE_PAYLOAD_FIELDS = exports.SECURE_ALGORITHMS = exports.SYMMETRIC_ALGORITHMS = exports.INSECURE_ALGORITHMS = exports.JWT_LIBRARIES = void 0;
9
- exports.isJwtLibraryCall = isJwtLibraryCall;
10
- exports.isStringLiteral = isStringLiteral;
11
- exports.extractAlgorithms = extractAlgorithms;
12
- exports.hasOption = hasOption;
13
- exports.getOptionValue = getOptionValue;
14
- exports.isWeakSecret = isWeakSecret;
15
- exports.isEnvVariable = isEnvVariable;
16
- exports.isSignOperation = isSignOperation;
17
- exports.isVerifyOperation = isVerifyOperation;
18
- exports.isDecodeOperation = isDecodeOperation;
19
- exports.getOptionsArgument = getOptionsArgument;
20
- exports.isTestFile = isTestFile;
21
- /**
22
- * Supported JWT libraries
23
- */
24
- exports.JWT_LIBRARIES = {
25
- JSONWEBTOKEN: 'jsonwebtoken',
26
- JOSE: 'jose',
27
- EXPRESS_JWT: 'express-jwt',
28
- NESTJS_JWT: '@nestjs/jwt',
29
- JWKS_RSA: 'jwks-rsa',
30
- JWT_DECODE: 'jwt-decode',
31
- };
32
- /**
33
- * Insecure algorithms that should be flagged
34
- */
35
- exports.INSECURE_ALGORITHMS = new Set([
36
- 'none',
37
- 'None',
38
- 'NONE',
39
- 'HS256', // Only insecure when used with public keys
40
- 'HS384',
41
- 'HS512',
42
- ]);
43
- /**
44
- * Algorithms vulnerable to confusion attacks when used with asymmetric keys
45
- */
46
- exports.SYMMETRIC_ALGORITHMS = new Set(['HS256', 'HS384', 'HS512']);
47
- /**
48
- * Recommended secure algorithms
49
- */
50
- exports.SECURE_ALGORITHMS = new Set([
51
- 'RS256',
52
- 'RS384',
53
- 'RS512',
54
- 'ES256',
55
- 'ES384',
56
- 'ES512',
57
- 'PS256',
58
- 'PS384',
59
- 'PS512',
60
- 'EdDSA',
61
- ]);
62
- /**
63
- * Sensitive field names that should not be in JWT payload
64
- * All lowercase for case-insensitive matching
65
- */
66
- exports.SENSITIVE_PAYLOAD_FIELDS = new Set([
67
- // Passwords
68
- 'password', 'passwd', 'pwd', 'pass', 'secret',
69
- // API Keys
70
- 'apikey', 'api_key', 'api-key',
71
- 'apisecret', 'api_secret', 'api-secret',
72
- // Tokens
73
- 'accesstoken', 'access_token', 'access-token',
74
- 'refreshtoken', 'refresh_token', 'refresh-token',
75
- 'bearertoken', 'bearer_token', 'bearer-token',
76
- // Keys
77
- 'privatekey', 'private_key', 'private-key',
78
- 'secretkey', 'secret_key', 'secret-key',
79
- // PII - Personal Identifiable Information
80
- 'email', 'emailaddress', 'email_address', 'email-address',
81
- 'phone', 'phonenumber', 'phone_number', 'phone-number',
82
- 'ssn', 'socialsecuritynumber', 'social_security_number',
83
- 'dob', 'dateofbirth', 'date_of_birth', 'birthdate',
84
- 'address', 'streetaddress', 'street_address',
85
- // Financial
86
- 'creditcard', 'credit_card', 'credit-card',
87
- 'cardnumber', 'card_number', 'card-number',
88
- 'cvv', 'cvc', 'securitycode', 'security_code',
89
- 'pin', 'pincode', 'pin_code',
90
- 'bankaccount', 'bank_account', 'bank-account',
91
- 'accountnumber', 'account_number', 'account-number',
92
- 'routingnumber', 'routing_number', 'routing-number',
93
- ]);
94
- /**
95
- * JWT method patterns for different operations
96
- */
97
- exports.JWT_METHODS = {
98
- SIGN: new Set(['sign', 'signJWT', 'SignJWT']),
99
- VERIFY: new Set(['verify', 'verifyJWT', 'jwtVerify']),
100
- DECODE: new Set(['decode', 'jwtDecode', 'decodeJWT']),
101
- };
102
- /**
103
- * Check if a node represents a call to a JWT library method
104
- */
105
- function isJwtLibraryCall(node, targetMethods) {
106
- // Check member expression: jwt.verify(), jose.jwtVerify()
107
- if (node.callee.type === 'MemberExpression') {
108
- const property = node.callee.property;
109
- if (property.type === 'Identifier') {
110
- return targetMethods.has(property.name);
111
- }
112
- }
113
- // Check direct calls: jwtVerify(), jwtDecode()
114
- if (node.callee.type === 'Identifier') {
115
- return targetMethods.has(node.callee.name);
116
- }
117
- return false;
118
- }
119
- /**
120
- * Check if a node is a string literal with a specific value
121
- */
122
- function isStringLiteral(node, value) {
123
- if (node.type !== 'Literal' || typeof node.value !== 'string') {
124
- return false;
125
- }
126
- return value === undefined || node.value === value;
127
- }
128
- /**
129
- * Extract algorithm from options object
130
- */
131
- function extractAlgorithms(optionsNode) {
132
- const algorithms = [];
133
- for (const prop of optionsNode.properties) {
134
- if (prop.type !== 'Property' || prop.key.type !== 'Identifier') {
135
- continue;
136
- }
137
- const keyName = prop.key.name;
138
- if (keyName !== 'algorithms' && keyName !== 'algorithm' && keyName !== 'alg') {
139
- continue;
140
- }
141
- // Single algorithm: { algorithm: 'HS256' }
142
- if (prop.value.type === 'Literal' && typeof prop.value.value === 'string') {
143
- algorithms.push(prop.value.value);
144
- }
145
- // Array of algorithms: { algorithms: ['RS256', 'ES256'] }
146
- if (prop.value.type === 'ArrayExpression') {
147
- for (const elem of prop.value.elements) {
148
- if (elem && elem.type === 'Literal' && typeof elem.value === 'string') {
149
- algorithms.push(elem.value);
150
- }
151
- }
152
- }
153
- }
154
- return algorithms;
155
- }
156
- /**
157
- * Check if options object has a specific property set
158
- */
159
- function hasOption(optionsNode, optionName) {
160
- return optionsNode.properties.some((prop) => prop.type === 'Property' &&
161
- prop.key.type === 'Identifier' &&
162
- prop.key.name === optionName);
163
- }
164
- /**
165
- * Get the value of a specific option from options object
166
- */
167
- function getOptionValue(optionsNode, optionName) {
168
- for (const prop of optionsNode.properties) {
169
- if (prop.type === 'Property' &&
170
- prop.key.type === 'Identifier' &&
171
- prop.key.name === optionName) {
172
- return prop.value;
173
- }
174
- }
175
- return undefined;
176
- }
177
- /**
178
- * Check if a literal appears to be a weak secret (short string)
179
- */
180
- function isWeakSecret(node, minLength = 32) {
181
- if (node.type === 'Literal' && typeof node.value === 'string') {
182
- return node.value.length < minLength;
183
- }
184
- return false;
185
- }
186
- /**
187
- * Check if a node is an environment variable access (safe pattern)
188
- */
189
- function isEnvVariable(node) {
190
- // process.env.JWT_SECRET
191
- if (node.type === 'MemberExpression' &&
192
- node.object.type === 'MemberExpression' &&
193
- node.object.object.type === 'Identifier' &&
194
- node.object.object.name === 'process' &&
195
- node.object.property.type === 'Identifier' &&
196
- node.object.property.name === 'env') {
197
- return true;
198
- }
199
- return false;
200
- }
201
- /**
202
- * Check if this call looks like a JWT sign operation
203
- */
204
- function isSignOperation(node) {
205
- return isJwtLibraryCall(node, exports.JWT_METHODS.SIGN);
206
- }
207
- /**
208
- * Check if this call looks like a JWT verify operation
209
- */
210
- function isVerifyOperation(node) {
211
- return isJwtLibraryCall(node, exports.JWT_METHODS.VERIFY);
212
- }
213
- /**
214
- * Check if this call looks like a JWT decode operation (no verification)
215
- */
216
- function isDecodeOperation(node) {
217
- return isJwtLibraryCall(node, exports.JWT_METHODS.DECODE);
218
- }
219
- /**
220
- * Get the options argument from a JWT call
221
- * For jwt.verify(token, secret, options) -> returns options
222
- * For jwt.sign(payload, secret, options) -> returns options
223
- */
224
- function getOptionsArgument(node, optionsIndex = 2) {
225
- const arg = node.arguments[optionsIndex];
226
- if (arg && arg.type === 'ObjectExpression') {
227
- return arg;
228
- }
229
- return undefined;
230
- }
231
- /**
232
- * Check if file is a test file
233
- */
234
- function isTestFile(filename) {
235
- return /\.(test|spec)\.[jt]sx?$/.test(filename) ||
236
- /__(tests?|mocks?)__/.test(filename) ||
237
- /\/(tests?|specs?|__tests__)\//.test(filename);
238
- }
@@ -1,192 +0,0 @@
1
- /**
2
- * JWT Ecosystem Interface Compatibility Tests
3
- *
4
- * Verifies that JWT-related packages export the interfaces our ESLint rules depend on.
5
- *
6
- * @sdk jsonwebtoken, jose, express-jwt, jwks-rsa, jwt-decode
7
- * @last-updated 2026-01-02
8
- */
9
-
10
- import { describe, it, expect, beforeAll } from 'vitest';
11
-
12
- // Use unknown types to avoid ESM/CJS type conflicts at compile time
13
- // Runtime checks handle the actual exports
14
- let jsonwebtokenModule: unknown;
15
- let joseModule: unknown;
16
- let jwtDecodeModule: unknown;
17
-
18
- // Helper to safely access properties on unknown types
19
- const getProp = (obj: unknown, key: string): unknown => {
20
- if (obj && typeof obj === 'object' && key in obj) {
21
- return (obj as Record<string, unknown>)[key];
22
- }
23
- return undefined;
24
- };
25
-
26
- beforeAll(async () => {
27
- try {
28
- jsonwebtokenModule = await import('jsonwebtoken');
29
- } catch {
30
- console.warn('jsonwebtoken not installed');
31
- }
32
- try {
33
- joseModule = await import('jose');
34
- } catch {
35
- console.warn('jose not installed');
36
- }
37
- try {
38
- jwtDecodeModule = await import('jwt-decode');
39
- } catch {
40
- console.warn('jwt-decode not installed');
41
- }
42
- }, 30_000); // native-addon packages can take >10s to load cold
43
-
44
- // ═══════════════════════════════════════════════════════════════════════════════
45
- // JSONWEBTOKEN INTERFACE
46
- // Primary package for JWT operations in Node.js
47
- // ═══════════════════════════════════════════════════════════════════════════════
48
-
49
- describe('jsonwebtoken Interface Compatibility', () => {
50
- describe('Core Exports', () => {
51
- it('exports sign function', () => {
52
- const sign = getProp(jsonwebtokenModule, 'sign');
53
- expect(sign).toBeDefined();
54
- expect(typeof sign).toBe('function');
55
- });
56
-
57
- it('exports verify function', () => {
58
- const verify = getProp(jsonwebtokenModule, 'verify');
59
- expect(verify).toBeDefined();
60
- expect(typeof verify).toBe('function');
61
- });
62
-
63
- it('exports decode function', () => {
64
- const decode = getProp(jsonwebtokenModule, 'decode');
65
- expect(decode).toBeDefined();
66
- expect(typeof decode).toBe('function');
67
- });
68
- });
69
-
70
- describe('Sign Options (Critical for Security Rules)', () => {
71
- it('sign accepts algorithm option', () => {
72
- // Rules check: no-algorithm-none, require-algorithm-whitelist
73
- expect(getProp(jsonwebtokenModule, 'sign')).toBeDefined();
74
- });
75
-
76
- it('sign accepts expiresIn option', () => {
77
- // Rules check: require-expiration
78
- expect(getProp(jsonwebtokenModule, 'sign')).toBeDefined();
79
- });
80
-
81
- it('sign accepts audience option', () => {
82
- // Rules check: require-audience
83
- expect(getProp(jsonwebtokenModule, 'sign')).toBeDefined();
84
- });
85
-
86
- it('sign accepts issuer option', () => {
87
- // Rules check: require-issuer
88
- expect(getProp(jsonwebtokenModule, 'sign')).toBeDefined();
89
- });
90
- });
91
-
92
- describe('Verify Options (Critical for Security Rules)', () => {
93
- it('verify accepts algorithms option (array)', () => {
94
- // Rules check: verify-algorithm-explicit
95
- expect(getProp(jsonwebtokenModule, 'verify')).toBeDefined();
96
- });
97
-
98
- it('verify accepts complete option', () => {
99
- // Returns full decoded token with header
100
- expect(getProp(jsonwebtokenModule, 'verify')).toBeDefined();
101
- });
102
- });
103
- });
104
-
105
- // ═══════════════════════════════════════════════════════════════════════════════
106
- // JOSE INTERFACE
107
- // Modern JWT library with async/await support
108
- // ═══════════════════════════════════════════════════════════════════════════════
109
-
110
- describe('jose Interface Compatibility', () => {
111
- describe('Core Exports', () => {
112
- it('exports SignJWT class', () => {
113
- expect(getProp(joseModule, 'SignJWT')).toBeDefined();
114
- });
115
-
116
- it('exports jwtVerify function', () => {
117
- const jwtVerify = getProp(joseModule, 'jwtVerify');
118
- expect(jwtVerify).toBeDefined();
119
- expect(typeof jwtVerify).toBe('function');
120
- });
121
-
122
- it('exports jwtDecrypt function', () => {
123
- const jwtDecrypt = getProp(joseModule, 'jwtDecrypt');
124
- expect(jwtDecrypt).toBeDefined();
125
- expect(typeof jwtDecrypt).toBe('function');
126
- });
127
-
128
- it('exports createRemoteJWKSet function', () => {
129
- // Used for JWKS validation
130
- const createRemoteJWKSet = getProp(joseModule, 'createRemoteJWKSet');
131
- expect(createRemoteJWKSet).toBeDefined();
132
- expect(typeof createRemoteJWKSet).toBe('function');
133
- });
134
- });
135
-
136
- describe('Key Generation', () => {
137
- it('exports generateKeyPair function', () => {
138
- expect(getProp(joseModule, 'generateKeyPair')).toBeDefined();
139
- });
140
-
141
- it('exports generateSecret function', () => {
142
- expect(getProp(joseModule, 'generateSecret')).toBeDefined();
143
- });
144
- });
145
- });
146
-
147
- // ═══════════════════════════════════════════════════════════════════════════════
148
- // JWT-DECODE INTERFACE
149
- // Client-side JWT decoding (no verification)
150
- // ═══════════════════════════════════════════════════════════════════════════════
151
-
152
- describe('jwt-decode Interface Compatibility', () => {
153
- describe('Core Exports', () => {
154
- it('exports jwtDecode function', () => {
155
- // Can be either named export or default export depending on version
156
- const jwtDecode = getProp(jwtDecodeModule, 'jwtDecode') ?? getProp(jwtDecodeModule, 'default');
157
- expect(jwtDecode).toBeDefined();
158
- });
159
-
160
- it('exports InvalidTokenError', () => {
161
- expect(getProp(jwtDecodeModule, 'InvalidTokenError')).toBeDefined();
162
- });
163
- });
164
- });
165
-
166
- // ═══════════════════════════════════════════════════════════════════════════════
167
- // PACKAGE METADATA
168
- // ═══════════════════════════════════════════════════════════════════════════════
169
-
170
- describe('Package Metadata', () => {
171
- it('jsonwebtoken has discoverable version', async () => {
172
- try {
173
- const pkgPath = require.resolve('jsonwebtoken/package.json');
174
- const pkg = await import(pkgPath, { with: { type: 'json' } }).then(m => m.default);
175
- expect(pkg.version).toBeDefined();
176
- console.log(`📦 jsonwebtoken version: ${pkg.version}`);
177
- } catch {
178
- console.log('📦 jsonwebtoken not installed');
179
- }
180
- });
181
-
182
- it('jose has discoverable version', async () => {
183
- try {
184
- const pkgPath = require.resolve('jose/package.json');
185
- const pkg = await import(pkgPath, { with: { type: 'json' } }).then(m => m.default);
186
- expect(pkg.version).toBeDefined();
187
- console.log(`📦 jose version: ${pkg.version}`);
188
- } catch {
189
- console.log('📦 jose not installed');
190
- }
191
- });
192
- });
package/src/index.test.ts DELETED
@@ -1,47 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import plugin from './index';
3
- import { rules, configs } from './index';
4
-
5
- describe('eslint-plugin-jwt-security plugin interface', () => {
6
- it('should export correct meta information', () => {
7
- expect(plugin.meta).toBeDefined();
8
- expect(plugin.meta?.name).toBe('eslint-plugin-jwt-security');
9
- expect(plugin.meta?.version).toBeDefined();
10
- });
11
-
12
- it('should export all JWT security rules', () => {
13
- expect(plugin.rules).toBeDefined();
14
- const ruleNames = Object.keys(plugin.rules || {});
15
- expect(ruleNames).toContain('no-algorithm-none');
16
- expect(ruleNames).toContain('no-algorithm-confusion');
17
- expect(ruleNames).toContain('no-timestamp-manipulation');
18
- expect(ruleNames.length).toBe(13);
19
- });
20
-
21
- describe('configurations', () => {
22
- it('should provide recommended configuration', () => {
23
- expect(configs.recommended).toBeDefined();
24
- expect(configs.recommended.plugins?.jwt).toBeDefined();
25
-
26
- const recommendedRules = configs.recommended.rules || {};
27
- Object.keys(recommendedRules).forEach(ruleName => {
28
- expect(ruleName).toMatch(/^jwt\//);
29
- });
30
-
31
- expect(recommendedRules['jwt/no-algorithm-none']).toBe('error');
32
- });
33
-
34
- it('should provide strict configuration', () => {
35
- expect(configs.strict).toBeDefined();
36
- expect(configs.strict.plugins?.jwt).toBeDefined();
37
-
38
- const strictRules = configs.strict.rules || {};
39
- Object.keys(strictRules).forEach(ruleName => {
40
- expect(ruleName).toMatch(/^jwt\//);
41
- });
42
-
43
- expect(strictRules['jwt/no-algorithm-none']).toBe('error');
44
- expect(Object.keys(strictRules).length).toBe(Object.keys(rules).length);
45
- });
46
- });
47
- });
package/src/index.ts DELETED
@@ -1,209 +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
- /**
8
- * eslint-plugin-jwt-security
9
- *
10
- * Comprehensive JWT security ESLint plugin for Node.js applications.
11
- * Detects vulnerabilities across jsonwebtoken, jose, express-jwt,
12
- * @nestjs/jwt, jwks-rsa, and jwt-decode libraries.
13
- *
14
- * Features:
15
- * - Algorithm confusion attack detection (CVE-2022-23540)
16
- * - "Back to the Future" replay attack prevention (LightSEC 2025)
17
- * - Signature verification enforcement
18
- * - Weak secret detection
19
- * - Expiration and claim validation
20
- * - AI-assisted fix guidance with CWE references
21
- *
22
- * @see https://tools.ietf.org/html/rfc8725
23
- * @see https://securitypattern.com/post/jwt-back-to-the-future
24
- */
25
- import type { TSESLint } from '@interlace/eslint-devkit';
26
-
27
- // Import rules - Core Security
28
- import { noAlgorithmNone } from './rules/no-algorithm-none';
29
- import { noAlgorithmConfusion } from './rules/no-algorithm-confusion';
30
- import { requireAlgorithmWhitelist } from './rules/require-algorithm-whitelist';
31
- import { noDecodeWithoutVerify } from './rules/no-decode-without-verify';
32
- import { noWeakSecret } from './rules/no-weak-secret';
33
- import { requireExpiration } from './rules/require-expiration';
34
- import { noHardcodedSecret } from './rules/no-hardcoded-secret';
35
-
36
- // Import rules - 2025 Vulnerability Research
37
- import { requireIssuedAt } from './rules/require-issued-at';
38
- import { requireIssuerValidation } from './rules/require-issuer-validation';
39
- import { requireAudienceValidation } from './rules/require-audience-validation';
40
- import { noTimestampManipulation } from './rules/no-timestamp-manipulation';
41
- import { requireMaxAge } from './rules/require-max-age';
42
- import { noSensitivePayload } from './rules/no-sensitive-payload';
43
-
44
- /**
45
- * Collection of all JWT security ESLint rules (13 rules)
46
- */
47
- export const rules: Record<string, TSESLint.RuleModule<string, readonly unknown[]>> = {
48
- // Core Security Rules
49
- 'no-algorithm-none': noAlgorithmNone,
50
- 'no-algorithm-confusion': noAlgorithmConfusion,
51
- 'require-algorithm-whitelist': requireAlgorithmWhitelist,
52
- 'no-decode-without-verify': noDecodeWithoutVerify,
53
- 'no-weak-secret': noWeakSecret,
54
- 'require-expiration': requireExpiration,
55
- 'no-hardcoded-secret': noHardcodedSecret,
56
-
57
- // 2025 Vulnerability Research - "Back to the Future" & Replay Prevention
58
- 'require-issued-at': requireIssuedAt,
59
- 'require-issuer-validation': requireIssuerValidation,
60
- 'require-audience-validation': requireAudienceValidation,
61
- 'no-timestamp-manipulation': noTimestampManipulation,
62
- 'require-max-age': requireMaxAge,
63
- 'no-sensitive-payload': noSensitivePayload,
64
- } satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>;
65
-
66
- /**
67
- * ESLint Plugin object
68
- */
69
- export const plugin: TSESLint.FlatConfig.Plugin = {
70
- meta: {
71
- name: 'eslint-plugin-jwt-security',
72
- version: '2.2.9',
73
- },
74
- rules,
75
- } satisfies TSESLint.FlatConfig.Plugin;
76
-
77
- /**
78
- * Recommended configuration - baseline security
79
- * Critical rules as errors, high as warnings
80
- */
81
- const recommendedRules: Record<string, TSESLint.FlatConfig.RuleEntry> = {
82
- // CRITICAL - Algorithm attacks
83
- 'jwt/no-algorithm-none': 'error',
84
- 'jwt/no-algorithm-confusion': 'error',
85
-
86
- // HIGH - Verification and secrets
87
- 'jwt/require-algorithm-whitelist': 'error',
88
- 'jwt/no-decode-without-verify': 'error',
89
- 'jwt/no-weak-secret': 'error',
90
- 'jwt/no-hardcoded-secret': 'error',
91
- 'jwt/no-timestamp-manipulation': 'error', // LightSEC 2025
92
-
93
- // MEDIUM - Best practices
94
- 'jwt/require-expiration': 'error',
95
- 'jwt/no-sensitive-payload': 'warn',
96
- };
97
-
98
- /**
99
- * Strict configuration - maximum security (2025 research)
100
- * All rules as errors - includes replay attack prevention
101
- */
102
- const strictRules: Record<string, TSESLint.FlatConfig.RuleEntry> = {
103
- // All core rules as errors
104
- 'jwt/no-algorithm-none': 'error',
105
- 'jwt/no-algorithm-confusion': 'error',
106
- 'jwt/require-algorithm-whitelist': 'error',
107
- 'jwt/no-decode-without-verify': 'error',
108
- 'jwt/no-weak-secret': 'error',
109
- 'jwt/no-hardcoded-secret': 'error',
110
- 'jwt/require-expiration': 'error',
111
-
112
- // 2025 Research - Full replay attack prevention
113
- 'jwt/require-issued-at': 'error',
114
- 'jwt/require-issuer-validation': 'error',
115
- 'jwt/require-audience-validation': 'error',
116
- 'jwt/no-timestamp-manipulation': 'error',
117
- 'jwt/require-max-age': 'error',
118
- 'jwt/no-sensitive-payload': 'error',
119
- };
120
-
121
- /**
122
- * Legacy configuration - migration mode
123
- * Only critical rules enabled
124
- */
125
- const legacyRules: Record<string, TSESLint.FlatConfig.RuleEntry> = {
126
- // Only most critical rules
127
- 'jwt/no-algorithm-none': 'error',
128
- 'jwt/no-algorithm-confusion': 'error',
129
- 'jwt/no-hardcoded-secret': 'warn',
130
- };
131
-
132
- /**
133
- * Configuration presets for flat config
134
- */
135
- export const configs = {
136
- /**
137
- * Flagship preset — the rule from this plugin in the ecosystem-wide
138
- * flagship list (`.agent/flagship-rules.md`). Use this when you want
139
- * the highest-signal JWT subset shippable in CI gates.
140
- */
141
- flagship: {
142
- plugins: {
143
- jwt: plugin,
144
- },
145
- rules: {
146
- 'jwt/no-algorithm-none': 'error',
147
- },
148
- } satisfies TSESLint.FlatConfig.Config,
149
-
150
- /**
151
- * Recommended preset - balanced security
152
- */
153
- recommended: {
154
- plugins: {
155
- jwt: plugin,
156
- },
157
- rules: recommendedRules,
158
- } satisfies TSESLint.FlatConfig.Config,
159
-
160
- /**
161
- * Strict preset - maximum security (includes 2025 research)
162
- */
163
- strict: {
164
- plugins: {
165
- jwt: plugin,
166
- },
167
- rules: strictRules,
168
- } satisfies TSESLint.FlatConfig.Config,
169
-
170
- /**
171
- * Legacy preset - migration mode
172
- */
173
- legacy: {
174
- plugins: {
175
- jwt: plugin,
176
- },
177
- rules: legacyRules,
178
- } satisfies TSESLint.FlatConfig.Config,
179
-
180
- /**
181
- * All rules preset
182
- */
183
- all: {
184
- plugins: {
185
- jwt: plugin,
186
- },
187
- rules: strictRules,
188
- } satisfies TSESLint.FlatConfig.Config,
189
- };
190
-
191
- // Default export for flat config usage
192
- export default plugin;
193
-
194
- // Named exports for flexibility
195
- export {
196
- noAlgorithmNone,
197
- noAlgorithmConfusion,
198
- requireAlgorithmWhitelist,
199
- noDecodeWithoutVerify,
200
- noWeakSecret,
201
- requireExpiration,
202
- noHardcodedSecret,
203
- requireIssuedAt,
204
- requireIssuerValidation,
205
- requireAudienceValidation,
206
- noTimestampManipulation,
207
- requireMaxAge,
208
- noSensitivePayload,
209
- };