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,204 +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 Rule: no-weak-secret
9
- *
10
- * Detects weak secrets used for HMAC-based JWT signing.
11
- * Weak secrets can be brute-forced, allowing attackers to forge tokens.
12
- *
13
- * CWE-326: Inadequate Encryption Strength
14
- *
15
- * @see https://tools.ietf.org/html/rfc8725
16
- */
17
- import type { TSESLint, TSESTree } from '@interlace/eslint-devkit';
18
- import { createRule, formatLLMMessage, MessageIcons } from '@interlace/eslint-devkit';
19
- import {
20
- isSignOperation,
21
- isVerifyOperation,
22
- isEnvVariable,
23
- } from '../../utils';
24
- import type { NoWeakSecretOptions } from '../../types';
25
-
26
- type MessageIds =
27
- | 'weakSecret'
28
- | 'shortSecret'
29
- | 'useStrongSecret';
30
-
31
- type RuleOptions = [NoWeakSecretOptions?];
32
-
33
- // Common weak secret patterns
34
- const WEAK_SECRET_PATTERNS = [
35
- /^secret$/i,
36
- /^password$/i,
37
- /^123456/,
38
- /^test/i,
39
- /^demo/i,
40
- /^example/i,
41
- /^changeme/i,
42
- /^default/i,
43
- ];
44
-
45
- export const noWeakSecret = createRule<RuleOptions, MessageIds>({
46
- name: 'no-weak-secret',
47
- meta: {
48
- type: 'problem',
49
- docs: {
50
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-weak-secret.md',
51
- description:
52
- 'Require strong secrets (256+ bits) for HMAC-based JWT signing',
53
- cwe: 'CWE-326',
54
- cvss: 5.9,
55
- },
56
- fixable: undefined,
57
- hasSuggestions: false,
58
- messages: {
59
- weakSecret: formatLLMMessage({
60
- icon: MessageIcons.SECURITY,
61
- issueName: 'Weak JWT Secret',
62
- cwe: 'CWE-326',
63
- description:
64
- 'JWT secret is too weak and can be brute-forced',
65
- severity: 'HIGH',
66
- fix: 'Use crypto.randomBytes(32).toString("hex") for secrets',
67
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
68
- }),
69
- shortSecret: formatLLMMessage({
70
- icon: MessageIcons.SECURITY,
71
- issueName: 'Short JWT Secret',
72
- cwe: 'CWE-326',
73
- description:
74
- 'JWT secret must be at least 32 characters (256 bits) for HS256',
75
- severity: 'HIGH',
76
- fix: 'Generate a secret with: crypto.randomBytes(32).toString("hex")',
77
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
78
- }),
79
- useStrongSecret: formatLLMMessage({
80
- icon: MessageIcons.INFO,
81
- issueName: 'Use Strong Secret',
82
- description: 'Replace with cryptographically strong secret',
83
- severity: 'LOW',
84
- fix: 'Use process.env.JWT_SECRET with a 256-bit random value',
85
- documentationLink: 'https://nodejs.org/api/crypto.html',
86
- }),
87
- },
88
- schema: [
89
- {
90
- type: 'object',
91
- properties: {
92
- minSecretLength: {
93
- type: 'integer',
94
- default: 32,
95
- minimum: 16,
96
- description: 'Minimum secret length in characters',
97
- },
98
- trustedSanitizers: {
99
- type: 'array',
100
- items: { type: 'string' },
101
- default: [],
102
- },
103
- trustedAnnotations: {
104
- type: 'array',
105
- items: { type: 'string' },
106
- default: [],
107
- },
108
- strictMode: {
109
- type: 'boolean',
110
- default: false,
111
- },
112
- },
113
- additionalProperties: false,
114
- },
115
- ],
116
- },
117
- defaultOptions: [
118
- {
119
- minSecretLength: 32,
120
- trustedSanitizers: [],
121
- trustedAnnotations: [],
122
- strictMode: false,
123
- },
124
- ],
125
- create(context: TSESLint.RuleContext<MessageIds, RuleOptions>) {
126
- const options = context.options[0] ?? {};
127
- const { minSecretLength = 32 } = options;
128
-
129
- /**
130
- * Check if a value matches known weak patterns
131
- */
132
- const isKnownWeakPattern = (value: string): boolean => {
133
- return WEAK_SECRET_PATTERNS.some((pattern) => pattern.test(value));
134
- };
135
-
136
- /**
137
- * Check the secret argument for weakness
138
- */
139
- const checkSecret = (
140
- secretNode: TSESTree.Node
141
- ): void => {
142
- // Environment variables are considered safe (configuration)
143
- if (isEnvVariable(secretNode)) {
144
- return;
145
- }
146
-
147
- // Check string literals
148
- if (
149
- secretNode.type === 'Literal' &&
150
- typeof secretNode.value === 'string'
151
- ) {
152
- const secretValue = secretNode.value;
153
-
154
- // Check for known weak patterns
155
- if (isKnownWeakPattern(secretValue)) {
156
- context.report({
157
- node: secretNode,
158
- messageId: 'weakSecret',
159
- });
160
- return;
161
- }
162
-
163
- // Check for short secrets
164
- if (secretValue.length < minSecretLength) {
165
- context.report({
166
- node: secretNode,
167
- messageId: 'shortSecret',
168
- data: {
169
- length: String(secretValue.length),
170
- minLength: String(minSecretLength),
171
- },
172
- });
173
- }
174
- }
175
-
176
- // Template literals with obvious weak values
177
- if (secretNode.type === 'TemplateLiteral' && secretNode.quasis.length === 1) {
178
- const rawValue = secretNode.quasis[0].value.raw;
179
- if (isKnownWeakPattern(rawValue) || rawValue.length < minSecretLength) {
180
- context.report({
181
- node: secretNode,
182
- messageId: 'weakSecret',
183
- });
184
- }
185
- }
186
- };
187
-
188
- return {
189
- CallExpression(node: TSESTree.CallExpression) {
190
- // Check both sign and verify operations
191
- if (!isSignOperation(node) && !isVerifyOperation(node)) {
192
- return;
193
- }
194
-
195
- // Secret is usually the second argument
196
- if (node.arguments.length >= 2) {
197
- checkSecret(node.arguments[1]);
198
- }
199
- },
200
- };
201
- },
202
- });
203
-
204
- export default noWeakSecret;
@@ -1,114 +0,0 @@
1
- /**
2
- * Tests for no-weak-secret rule
3
- * Security: CWE-326 (Encryption Strength)
4
- */
5
- import { RuleTester } from '@typescript-eslint/rule-tester';
6
- import { describe, it, afterAll } from 'vitest';
7
- import parser from '@typescript-eslint/parser';
8
- import { noWeakSecret } from './index';
9
-
10
- RuleTester.afterAll = afterAll;
11
- RuleTester.it = it;
12
- RuleTester.itOnly = it.only;
13
- RuleTester.describe = describe;
14
-
15
- const ruleTester = new RuleTester({
16
- languageOptions: {
17
- parser,
18
- ecmaVersion: 2022,
19
- sourceType: 'module',
20
- },
21
- });
22
-
23
- // 32+ character strong secret for testing
24
- const STRONG_SECRET = 'ThisIsAVeryStrongSecretThatIs32Ch+';
25
-
26
- describe('no-weak-secret', () => {
27
- describe('Valid Code - Strong Secrets', () => {
28
- ruleTester.run('valid - strong secrets', noWeakSecret, {
29
- valid: [
30
- // Environment variable
31
- { code: `jwt.sign(payload, process.env.JWT_SECRET);` },
32
- // Long secret (32+ chars)
33
- { code: `jwt.sign(payload, '${STRONG_SECRET}');` },
34
- // Non-literal (function call)
35
- { code: `jwt.sign(payload, getSecret());` },
36
- // Variable reference
37
- { code: `jwt.sign(payload, secret);` },
38
- // crypto.randomBytes
39
- { code: `jwt.sign(payload, crypto.randomBytes(32).toString('hex'));` },
40
- // Verify with env
41
- { code: `jwt.verify(token, process.env.JWT_SECRET);` },
42
- ],
43
- invalid: [],
44
- });
45
- });
46
-
47
- describe('Invalid Code - Weak Secrets', () => {
48
- ruleTester.run('invalid - weak secrets', noWeakSecret, {
49
- valid: [],
50
- invalid: [
51
- // Known weak pattern: "secret"
52
- {
53
- code: `jwt.sign(payload, 'secret');`,
54
- errors: [{ messageId: 'weakSecret' }],
55
- },
56
- // Known weak pattern: "password"
57
- {
58
- code: `jwt.sign(payload, 'password');`,
59
- errors: [{ messageId: 'weakSecret' }],
60
- },
61
- // Short secret
62
- {
63
- code: `jwt.sign(payload, 'short');`,
64
- errors: [{ messageId: 'shortSecret' }],
65
- },
66
- // Just under minimum (31 chars)
67
- {
68
- code: `jwt.sign(payload, 'abcdefghijklmnopqrstuvwxyz12345');`,
69
- errors: [{ messageId: 'shortSecret' }],
70
- },
71
- // Known weak: test
72
- {
73
- code: `jwt.sign(payload, 'testkey');`,
74
- errors: [{ messageId: 'weakSecret' }],
75
- },
76
- // Known weak: demo
77
- {
78
- code: `jwt.verify(token, 'demo123');`,
79
- errors: [{ messageId: 'weakSecret' }],
80
- },
81
- // Template literal with weak value
82
- {
83
- code: 'jwt.sign(payload, `secret`);',
84
- errors: [{ messageId: 'weakSecret' }],
85
- },
86
- // Known weak: changeme
87
- {
88
- code: `jwt.sign(payload, 'changeme');`,
89
- errors: [{ messageId: 'weakSecret' }],
90
- },
91
- ],
92
- });
93
- });
94
-
95
- describe('Configuration Options', () => {
96
- ruleTester.run('config - custom min length', noWeakSecret, {
97
- valid: [
98
- // 16 char secret with minSecretLength: 16
99
- {
100
- code: `jwt.sign(payload, 'sixteen_chars!!!');`,
101
- options: [{ minSecretLength: 16 }],
102
- },
103
- ],
104
- invalid: [
105
- // 15 chars with minSecretLength: 16
106
- {
107
- code: `jwt.sign(payload, 'fifteen_chars!!');`,
108
- options: [{ minSecretLength: 16 }],
109
- errors: [{ messageId: 'shortSecret' }],
110
- },
111
- ],
112
- });
113
- });
114
- });
@@ -1,145 +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 Rule: require-algorithm-whitelist
9
- *
10
- * Enforces explicit algorithm specification in JWT verify operations.
11
- * Without explicit algorithms, the token's header algorithm is trusted,
12
- * enabling algorithm substitution attacks.
13
- *
14
- * CWE-757: Selection of Less-Secure Algorithm During Negotiation
15
- *
16
- * @see https://tools.ietf.org/html/rfc8725
17
- */
18
- import type { TSESLint, TSESTree } from '@interlace/eslint-devkit';
19
- import { createRule, formatLLMMessage, MessageIcons } from '@interlace/eslint-devkit';
20
- import {
21
- isVerifyOperation,
22
- getOptionsArgument,
23
- hasOption,
24
- } from '../../utils';
25
- import type { RequireAlgorithmWhitelistOptions } from '../../types';
26
-
27
- type MessageIds =
28
- | 'missingAlgorithmWhitelist'
29
- | 'addAlgorithmWhitelist';
30
-
31
- type RuleOptions = [RequireAlgorithmWhitelistOptions?];
32
-
33
- export const requireAlgorithmWhitelist = createRule<RuleOptions, MessageIds>({
34
- name: 'require-algorithm-whitelist',
35
- meta: {
36
- type: 'problem',
37
- docs: {
38
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-algorithm-whitelist.md',
39
- description:
40
- 'Require explicit algorithm specification in JWT verify operations',
41
- cwe: 'CWE-757',
42
- cvss: 7.5,
43
- },
44
- fixable: undefined,
45
- hasSuggestions: false,
46
- messages: {
47
- missingAlgorithmWhitelist: formatLLMMessage({
48
- icon: MessageIcons.SECURITY,
49
- issueName: 'Missing Algorithm Whitelist',
50
- cwe: 'CWE-757',
51
- description:
52
- 'JWT verification without explicit algorithms trusts the token header',
53
- severity: 'HIGH',
54
- fix: 'Add algorithms: ["RS256"] or algorithms: ["ES256"] to options',
55
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
56
- }),
57
- addAlgorithmWhitelist: formatLLMMessage({
58
- icon: MessageIcons.INFO,
59
- issueName: 'Add Algorithm Whitelist',
60
- description: 'Specify allowed algorithms explicitly',
61
- severity: 'LOW',
62
- fix: 'jwt.verify(token, key, { algorithms: ["RS256"] })',
63
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
64
- }),
65
- },
66
- schema: [
67
- {
68
- type: 'object',
69
- properties: {
70
- recommendedAlgorithms: {
71
- type: 'array',
72
- items: { type: 'string' },
73
- default: ['RS256', 'ES256'],
74
- description: 'Algorithms to suggest in auto-fix',
75
- },
76
- trustedSanitizers: {
77
- type: 'array',
78
- items: { type: 'string' },
79
- default: [],
80
- },
81
- trustedAnnotations: {
82
- type: 'array',
83
- items: { type: 'string' },
84
- default: [],
85
- },
86
- strictMode: {
87
- type: 'boolean',
88
- default: false,
89
- },
90
- },
91
- additionalProperties: false,
92
- },
93
- ],
94
- },
95
- defaultOptions: [
96
- {
97
- recommendedAlgorithms: ['RS256', 'ES256'],
98
- trustedSanitizers: [],
99
- trustedAnnotations: [],
100
- strictMode: false,
101
- },
102
- ],
103
- create(context: TSESLint.RuleContext<MessageIds, RuleOptions>) {
104
-
105
- return {
106
- CallExpression(node: TSESTree.CallExpression) {
107
- // Only check verify operations
108
- if (!isVerifyOperation(node)) {
109
- return;
110
- }
111
-
112
- // Need at least token and key arguments
113
- if (node.arguments.length < 2) {
114
- return;
115
- }
116
-
117
- const optionsArg = getOptionsArgument(node, 2);
118
-
119
- // No options at all - definitely missing algorithms
120
- if (!optionsArg) {
121
- context.report({
122
- node,
123
- messageId: 'missingAlgorithmWhitelist',
124
- });
125
- return;
126
- }
127
-
128
- // Options exist but no algorithms property
129
- const hasAlgorithms =
130
- hasOption(optionsArg, 'algorithms') ||
131
- hasOption(optionsArg, 'algorithm') ||
132
- hasOption(optionsArg, 'alg');
133
-
134
- if (!hasAlgorithms) {
135
- context.report({
136
- node: optionsArg,
137
- messageId: 'missingAlgorithmWhitelist',
138
- });
139
- }
140
- },
141
- };
142
- },
143
- });
144
-
145
- export default requireAlgorithmWhitelist;
@@ -1,72 +0,0 @@
1
- /**
2
- * Tests for require-algorithm-whitelist rule
3
- */
4
- import { RuleTester } from '@typescript-eslint/rule-tester';
5
- import { describe, it, afterAll } from 'vitest';
6
- import parser from '@typescript-eslint/parser';
7
- import { requireAlgorithmWhitelist } from './index';
8
-
9
- RuleTester.afterAll = afterAll;
10
- RuleTester.it = it;
11
- RuleTester.itOnly = it.only;
12
- RuleTester.describe = describe;
13
-
14
- const ruleTester = new RuleTester({
15
- languageOptions: {
16
- parser,
17
- ecmaVersion: 2022,
18
- sourceType: 'module',
19
- },
20
- });
21
-
22
- describe('require-algorithm-whitelist', () => {
23
- describe('Valid Code', () => {
24
- ruleTester.run('valid - with algorithms', requireAlgorithmWhitelist, {
25
- valid: [
26
- { code: `jwt.verify(token, secret, { algorithms: ['RS256'] });` },
27
- { code: `jwt.verify(token, secret, { algorithms: ['RS256', 'ES256'] });` },
28
- { code: `jwt.verify(token, secret, { algorithm: 'RS256' });` },
29
- { code: `jwt.verify(token, secret, { alg: 'RS256' });` },
30
- { code: `jwt.sign(payload, secret);` }, // sign not checked
31
- // Only one argument - edge case
32
- { code: `jwt.verify(token);` },
33
- // jwtVerify with algorithms
34
- { code: `jwtVerify(token, key, { algorithms: ['RS256'] });` },
35
- ],
36
- invalid: [],
37
- });
38
- });
39
-
40
- describe('Invalid Code', () => {
41
- ruleTester.run('invalid - no algorithms', requireAlgorithmWhitelist, {
42
- valid: [],
43
- invalid: [
44
- {
45
- code: `jwt.verify(token, secret);`,
46
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
47
- },
48
- {
49
- code: `jwt.verify(token, secret, {});`,
50
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
51
- },
52
- {
53
- code: `jwt.verify(token, secret, { complete: true });`,
54
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
55
- },
56
- {
57
- code: `jwt.verify(token, secret, { issuer: 'auth.example.com' });`,
58
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
59
- },
60
- {
61
- code: `jwtVerify(token, key);`,
62
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
63
- },
64
- // verifyJWT without algorithms
65
- {
66
- code: `verifyJWT(token, key, { issuer: 'auth.example.com' });`,
67
- errors: [{ messageId: 'missingAlgorithmWhitelist' }],
68
- },
69
- ],
70
- });
71
- });
72
- });
@@ -1,120 +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 Rule: require-audience-validation
9
- *
10
- * Mandates audience (aud) claim validation in JWT verify operations.
11
- * Without audience validation, tokens intended for other services are accepted.
12
- *
13
- * CWE-287: Improper Authentication
14
- */
15
- import type { TSESLint, TSESTree } from '@interlace/eslint-devkit';
16
- import { createRule, formatLLMMessage, MessageIcons } from '@interlace/eslint-devkit';
17
- import { isVerifyOperation, getOptionsArgument, hasOption } from '../../utils';
18
- import type { RequireAudienceValidationOptions } from '../../types';
19
-
20
- type MessageIds = 'missingAudienceValidation';
21
-
22
- type RuleOptions = [RequireAudienceValidationOptions?];
23
-
24
- export const requireAudienceValidation = createRule<RuleOptions, MessageIds>({
25
- name: 'require-audience-validation',
26
- meta: {
27
- type: 'problem',
28
- docs: {
29
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-audience-validation.md',
30
- description:
31
- 'Require audience (aud) claim validation in JWT verify operations',
32
- cwe: 'CWE-287',
33
- cvss: 9.8,
34
- },
35
- fixable: undefined,
36
- hasSuggestions: false,
37
- messages: {
38
- missingAudienceValidation: formatLLMMessage({
39
- icon: MessageIcons.SECURITY,
40
- issueName: 'Missing Audience Validation',
41
- cwe: 'CWE-287',
42
- description:
43
- 'JWT verification without audience validation accepts tokens intended for other services',
44
- severity: 'MEDIUM',
45
- fix: 'Add audience option: { audience: "https://api.example.com" }',
46
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
47
- }),
48
- },
49
- schema: [
50
- {
51
- type: 'object',
52
- properties: {
53
- knownAudiences: {
54
- type: 'array',
55
- items: { type: 'string' },
56
- default: [],
57
- description: 'Known valid audiences to suggest',
58
- },
59
- trustedSanitizers: {
60
- type: 'array',
61
- items: { type: 'string' },
62
- default: [],
63
- },
64
- trustedAnnotations: {
65
- type: 'array',
66
- items: { type: 'string' },
67
- default: [],
68
- },
69
- strictMode: {
70
- type: 'boolean',
71
- default: false,
72
- },
73
- },
74
- additionalProperties: false,
75
- },
76
- ],
77
- },
78
- defaultOptions: [
79
- {
80
- knownAudiences: [],
81
- trustedSanitizers: [],
82
- trustedAnnotations: [],
83
- strictMode: false,
84
- },
85
- ],
86
- create(context: TSESLint.RuleContext<MessageIds, RuleOptions>) {
87
- return {
88
- CallExpression(node: TSESTree.CallExpression) {
89
- if (!isVerifyOperation(node)) {
90
- return;
91
- }
92
-
93
- if (node.arguments.length < 2) {
94
- return;
95
- }
96
-
97
- const optionsArg = getOptionsArgument(node, 2);
98
-
99
- // No options at all
100
- if (!optionsArg) {
101
- context.report({
102
- node,
103
- messageId: 'missingAudienceValidation',
104
- });
105
- return;
106
- }
107
-
108
- // Check for audience option
109
- if (!hasOption(optionsArg, 'audience') && !hasOption(optionsArg, 'aud')) {
110
- context.report({
111
- node: optionsArg,
112
- messageId: 'missingAudienceValidation',
113
- });
114
- }
115
- },
116
- };
117
- },
118
- });
119
-
120
- export default requireAudienceValidation;