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,238 @@
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
+ }
package/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "eslint-plugin-jwt-security",
3
+ "version": "2.3.0",
4
+ "description": "ESLint plugin for JWT security \u2014 detects algorithm confusion (CVE-2022-23540), alg:none, weak or hardcoded secrets, and decode-without-verify.",
5
+ "type": "commonjs",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/src/index.d.ts",
11
+ "default": "./dist/src/index.js"
12
+ },
13
+ "./types": {
14
+ "types": "./src/types/index.d.ts",
15
+ "default": "./src/types/index.js"
16
+ },
17
+ "./oxlint": {
18
+ "types": "./src/oxlint.d.ts",
19
+ "default": "./src/oxlint.js"
20
+ }
21
+ },
22
+ "author": "Ofri Peretz <ofriperetzdev@gmail.com>",
23
+ "license": "MIT",
24
+ "homepage": "https://eslint.interlace.tools/docs/security/plugin-jwt-security?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security-security",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/ofri-peretz/eslint",
28
+ "directory": "packages/eslint-plugin-jwt-security"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/ofri-peretz/eslint/issues"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "files": [
37
+ "src/",
38
+ "dist/",
39
+ "README.md",
40
+ "LICENSE",
41
+ "CHANGELOG.md",
42
+ "AGENTS.md"
43
+ ],
44
+ "keywords": [
45
+ "eslint",
46
+ "eslintplugin",
47
+ "eslint-plugin",
48
+ "static-analysis",
49
+ "linting",
50
+ "code-quality",
51
+ "security",
52
+ "sast",
53
+ "appsec",
54
+ "vulnerability",
55
+ "owasp",
56
+ "cwe",
57
+ "interlace-security",
58
+ "jwt",
59
+ "json-web-token",
60
+ "jwt-security",
61
+ "jsonwebtoken",
62
+ "jose",
63
+ "express-jwt",
64
+ "nestjs-jwt",
65
+ "jwks-rsa",
66
+ "jwt-decode",
67
+ "algorithm-confusion",
68
+ "cve-2022-23540",
69
+ "cwe-347",
70
+ "cwe-757",
71
+ "token",
72
+ "authentication",
73
+ "authorization",
74
+ "llm-optimized",
75
+ "ai-assistant",
76
+ "typescript",
77
+ "auto-fix",
78
+ "auth-security",
79
+ "token-security"
80
+ ],
81
+ "engines": {
82
+ "node": ">=18.0.0"
83
+ },
84
+ "dependencies": {
85
+ "tslib": "^2.3.0",
86
+ "@interlace/eslint-devkit": "^1.4.3"
87
+ },
88
+ "peerDependencies": {
89
+ "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "jsonwebtoken": {
93
+ "optional": true
94
+ },
95
+ "@nestjs/jwt": {
96
+ "optional": true
97
+ },
98
+ "express-jwt": {
99
+ "optional": true
100
+ },
101
+ "jose": {
102
+ "optional": true
103
+ },
104
+ "jwks-rsa": {
105
+ "optional": true
106
+ },
107
+ "jwt-decode": {
108
+ "optional": true
109
+ }
110
+ },
111
+ "devDependencies": {
112
+ "@typescript-eslint/parser": "^8.46.2",
113
+ "@typescript-eslint/rule-tester": "^8.46.2"
114
+ },
115
+ "scripts": {
116
+ "build": "tsx ../../scripts/build-package.ts",
117
+ "test": "vitest run",
118
+ "test:coverage": "vitest run --coverage",
119
+ "typecheck": "tsc -p tsconfig.lib.json --noEmit"
120
+ },
121
+ "funding": {
122
+ "type": "github",
123
+ "url": "https://github.com/ofri-peretz/eslint"
124
+ }
125
+ }
@@ -0,0 +1,192 @@
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
+ });
@@ -0,0 +1,47 @@
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
+ });