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,23 +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
- * ESLint Rule: require-expiration
8
- *
9
- * Mandates expiration claim (exp) or expiresIn option in JWT sign operations.
10
- * Tokens without expiration are valid forever, creating security risks.
11
- *
12
- * CWE-613: Insufficient Session Expiration
13
- *
14
- * @see https://tools.ietf.org/html/rfc8725
15
- */
16
- import type { TSESLint } from '@interlace/eslint-devkit';
17
- import type { RequireExpirationOptions } from '../../types';
18
- type MessageIds = 'missingExpiration' | 'addExpiration';
19
- type RuleOptions = [RequireExpirationOptions?];
20
- export declare const requireExpiration: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
21
- name: string;
22
- };
23
- export default requireExpiration;
@@ -1,136 +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.requireExpiration = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const utils_1 = require("../../utils");
11
- exports.requireExpiration = (0, eslint_devkit_1.createRule)({
12
- name: 'require-expiration',
13
- meta: {
14
- type: 'problem',
15
- docs: {
16
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-expiration.md',
17
- description: 'Require expiration claim (exp) or expiresIn option in JWT signing',
18
- cwe: 'CWE-613',
19
- cvss: 5.4,
20
- },
21
- hasSuggestions: true,
22
- messages: {
23
- missingExpiration: (0, eslint_devkit_1.formatLLMMessage)({
24
- icon: eslint_devkit_1.MessageIcons.SECURITY,
25
- issueName: 'Missing JWT Expiration',
26
- cwe: 'CWE-613',
27
- description: 'JWT without expiration is valid forever, increasing exposure window',
28
- severity: 'MEDIUM',
29
- fix: 'Add expiresIn: "1h" or exp claim to payload',
30
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
31
- }),
32
- addExpiration: (0, eslint_devkit_1.formatLLMMessage)({
33
- icon: eslint_devkit_1.MessageIcons.INFO,
34
- issueName: 'Add Expiration',
35
- description: 'Add expiration to limit token lifetime',
36
- severity: 'LOW',
37
- fix: 'jwt.sign(payload, secret, { expiresIn: "1h" })',
38
- documentationLink: 'https://www.npmjs.com/package/jsonwebtoken',
39
- }),
40
- },
41
- schema: [
42
- {
43
- type: 'object',
44
- properties: {
45
- maxExpirationSeconds: {
46
- type: 'integer',
47
- default: 86400,
48
- description: 'Maximum allowed expiration time in seconds (24h default)',
49
- },
50
- trustedSanitizers: {
51
- type: 'array',
52
- items: { type: 'string' },
53
- default: [],
54
- },
55
- trustedAnnotations: {
56
- type: 'array',
57
- items: { type: 'string' },
58
- default: [],
59
- },
60
- strictMode: {
61
- type: 'boolean',
62
- default: false,
63
- },
64
- },
65
- additionalProperties: false,
66
- },
67
- ],
68
- },
69
- defaultOptions: [
70
- {
71
- maxExpirationSeconds: 86400,
72
- trustedSanitizers: [],
73
- trustedAnnotations: [],
74
- strictMode: false,
75
- },
76
- ],
77
- create(context) {
78
- /**
79
- * Check if payload contains exp claim
80
- */
81
- // oxlint-disable-next-line consistent-function-scoping
82
- const payloadHasExp = (payloadNode) => {
83
- if (payloadNode.type !== 'ObjectExpression') {
84
- return false;
85
- }
86
- return payloadNode.properties.some((prop) => prop.type === 'Property' &&
87
- prop.key.type === 'Identifier' &&
88
- prop.key.name === 'exp');
89
- };
90
- return {
91
- CallExpression(node) {
92
- // Only check sign operations
93
- if (!(0, utils_1.isSignOperation)(node)) {
94
- return;
95
- }
96
- // Need at least payload argument
97
- if (node.arguments.length < 1) {
98
- return;
99
- }
100
- const payloadArg = node.arguments[0];
101
- const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
102
- // Check if payload has exp claim
103
- if (payloadHasExp(payloadArg)) {
104
- return;
105
- }
106
- // Check if options has expiresIn
107
- if (optionsArg && (0, utils_1.hasOption)(optionsArg, 'expiresIn')) {
108
- return;
109
- }
110
- // Report missing expiration — suggest adding expiresIn to the options object
111
- const optionsArg3 = node.arguments[2];
112
- context.report({
113
- node,
114
- messageId: 'missingExpiration',
115
- suggest: [
116
- {
117
- messageId: 'addExpiration',
118
- fix(fixer) {
119
- const sourceCode = context.sourceCode;
120
- if (optionsArg3 && optionsArg3.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression) {
121
- // Options object exists but lacks expiresIn — insert property
122
- const openBrace = sourceCode.getFirstToken(optionsArg3);
123
- return fixer.insertTextAfter(openBrace, ' expiresIn: \'1h\',');
124
- }
125
- // No options arg — insert { expiresIn: '1h' } as third argument
126
- const lastArg = node.arguments[node.arguments.length - 1];
127
- return fixer.insertTextAfter(lastArg, ', { expiresIn: \'1h\' }');
128
- },
129
- },
130
- ],
131
- });
132
- },
133
- };
134
- },
135
- });
136
- exports.default = exports.requireExpiration;
@@ -1,23 +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
- * ESLint Rule: require-issued-at
8
- *
9
- * Mandates the `iat` (issued at) claim in JWT tokens for freshness validation.
10
- * Without `iat`, tokens cannot be validated for age, enabling the
11
- * "Back to the Future" replay attack (LightSEC 2025).
12
- *
13
- * CWE-294: Authentication Bypass by Capture-replay
14
- *
15
- * @see https://securitypattern.com/post/jwt-back-to-the-future
16
- */
17
- import type { TSESLint } from '@interlace/eslint-devkit';
18
- import type { JwtRuleOptions } from '../../types';
19
- type RuleOptions = [JwtRuleOptions?];
20
- export declare const requireIssuedAt: TSESLint.RuleModule<"missingIssuedAt", RuleOptions, unknown, TSESLint.RuleListener> & {
21
- name: string;
22
- };
23
- export default requireIssuedAt;
@@ -1,113 +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.requireIssuedAt = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const utils_1 = require("../../utils");
11
- exports.requireIssuedAt = (0, eslint_devkit_1.createRule)({
12
- name: 'require-issued-at',
13
- meta: {
14
- type: 'problem',
15
- docs: {
16
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issued-at.md',
17
- description: 'Require iat (issued at) claim for token freshness validation',
18
- cwe: 'CWE-294',
19
- cvss: 5,
20
- },
21
- fixable: undefined,
22
- hasSuggestions: false,
23
- messages: {
24
- missingIssuedAt: (0, eslint_devkit_1.formatLLMMessage)({
25
- icon: eslint_devkit_1.MessageIcons.SECURITY,
26
- issueName: 'Missing Issued At Claim',
27
- cwe: 'CWE-294',
28
- description: 'JWT without iat claim cannot be validated for freshness, enabling replay attacks',
29
- severity: 'MEDIUM',
30
- fix: 'Add iat claim to payload or use library option that adds it automatically',
31
- documentationLink: 'https://securitypattern.com/post/jwt-back-to-the-future',
32
- }),
33
- },
34
- schema: [
35
- {
36
- type: 'object',
37
- properties: {
38
- trustedSanitizers: {
39
- type: 'array',
40
- items: { type: 'string' },
41
- default: [],
42
- },
43
- trustedAnnotations: {
44
- type: 'array',
45
- items: { type: 'string' },
46
- default: [],
47
- },
48
- strictMode: {
49
- type: 'boolean',
50
- default: false,
51
- },
52
- },
53
- additionalProperties: false,
54
- },
55
- ],
56
- },
57
- defaultOptions: [
58
- {
59
- trustedSanitizers: [],
60
- trustedAnnotations: [],
61
- strictMode: false,
62
- },
63
- ],
64
- create(context) {
65
- /**
66
- * Check if payload contains iat claim
67
- */
68
- // oxlint-disable-next-line consistent-function-scoping
69
- const payloadHasIat = (payloadNode) => {
70
- if (payloadNode.type !== 'ObjectExpression') {
71
- return false;
72
- }
73
- return payloadNode.properties.some((prop) => prop.type === 'Property' &&
74
- prop.key.type === 'Identifier' &&
75
- prop.key.name === 'iat');
76
- };
77
- return {
78
- CallExpression(node) {
79
- // Only check sign operations
80
- if (!(0, utils_1.isSignOperation)(node)) {
81
- return;
82
- }
83
- // Need at least payload argument
84
- if (node.arguments.length < 1) {
85
- return;
86
- }
87
- const payloadArg = node.arguments[0];
88
- const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
89
- // Check if payload has iat claim
90
- if (payloadHasIat(payloadArg)) {
91
- return;
92
- }
93
- // Check if options has noTimestamp: false (meaning iat IS added)
94
- // jsonwebtoken adds iat by default unless noTimestamp: true
95
- if (optionsArg && (0, utils_1.hasOption)(optionsArg, 'noTimestamp')) {
96
- // If noTimestamp is explicitly set, we flag it (they're disabling iat)
97
- context.report({
98
- node: optionsArg,
99
- messageId: 'missingIssuedAt',
100
- });
101
- return;
102
- }
103
- // For explicit payload objects without iat we intentionally do not
104
- // report — jsonwebtoken adds iat by default. (A dead empty `if` that
105
- // re-checked !payloadHasIat(payloadArg) — always true after the early
106
- // return above — was removed as unreachable-by-construction.)
107
- // Note: jsonwebtoken adds iat by default, so we don't flag by default
108
- // This rule primarily catches explicit noTimestamp: true usage
109
- },
110
- };
111
- },
112
- });
113
- exports.default = exports.requireIssuedAt;
@@ -1,20 +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
- * ESLint Rule: require-issuer-validation
8
- *
9
- * Mandates issuer (iss) claim validation in JWT verify operations.
10
- * Without issuer validation, tokens from any issuer are accepted.
11
- *
12
- * CWE-287: Improper Authentication
13
- */
14
- import type { TSESLint } from '@interlace/eslint-devkit';
15
- import type { RequireIssuerValidationOptions } from '../../types';
16
- type RuleOptions = [RequireIssuerValidationOptions?];
17
- export declare const requireIssuerValidation: TSESLint.RuleModule<"missingIssuerValidation", RuleOptions, unknown, TSESLint.RuleListener> & {
18
- name: string;
19
- };
20
- export default requireIssuerValidation;
@@ -1,100 +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.requireIssuerValidation = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const utils_1 = require("../../utils");
11
- exports.requireIssuerValidation = (0, eslint_devkit_1.createRule)({
12
- name: 'require-issuer-validation',
13
- meta: {
14
- type: 'problem',
15
- docs: {
16
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issuer-validation.md',
17
- description: 'Require issuer (iss) claim validation in JWT verify operations',
18
- cwe: 'CWE-287',
19
- cvss: 9.8,
20
- },
21
- fixable: undefined,
22
- hasSuggestions: false,
23
- messages: {
24
- missingIssuerValidation: (0, eslint_devkit_1.formatLLMMessage)({
25
- icon: eslint_devkit_1.MessageIcons.SECURITY,
26
- issueName: 'Missing Issuer Validation',
27
- cwe: 'CWE-287',
28
- description: 'JWT verification without issuer validation accepts tokens from any source',
29
- severity: 'MEDIUM',
30
- fix: 'Add issuer option: { issuer: "https://auth.example.com" }',
31
- documentationLink: 'https://tools.ietf.org/html/rfc8725',
32
- }),
33
- },
34
- schema: [
35
- {
36
- type: 'object',
37
- properties: {
38
- knownIssuers: {
39
- type: 'array',
40
- items: { type: 'string' },
41
- default: [],
42
- description: 'Known valid issuers to suggest',
43
- },
44
- trustedSanitizers: {
45
- type: 'array',
46
- items: { type: 'string' },
47
- default: [],
48
- },
49
- trustedAnnotations: {
50
- type: 'array',
51
- items: { type: 'string' },
52
- default: [],
53
- },
54
- strictMode: {
55
- type: 'boolean',
56
- default: false,
57
- },
58
- },
59
- additionalProperties: false,
60
- },
61
- ],
62
- },
63
- defaultOptions: [
64
- {
65
- knownIssuers: [],
66
- trustedSanitizers: [],
67
- trustedAnnotations: [],
68
- strictMode: false,
69
- },
70
- ],
71
- create(context) {
72
- return {
73
- CallExpression(node) {
74
- if (!(0, utils_1.isVerifyOperation)(node)) {
75
- return;
76
- }
77
- if (node.arguments.length < 2) {
78
- return;
79
- }
80
- const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
81
- // No options at all
82
- if (!optionsArg) {
83
- context.report({
84
- node,
85
- messageId: 'missingIssuerValidation',
86
- });
87
- return;
88
- }
89
- // Check for issuer option
90
- if (!(0, utils_1.hasOption)(optionsArg, 'issuer') && !(0, utils_1.hasOption)(optionsArg, 'iss')) {
91
- context.report({
92
- node: optionsArg,
93
- messageId: 'missingIssuerValidation',
94
- });
95
- }
96
- },
97
- };
98
- },
99
- });
100
- exports.default = exports.requireIssuerValidation;
@@ -1,24 +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
- * ESLint Rule: require-max-age
8
- *
9
- * Mandates maxAge option in JWT verify operations to enforce token freshness.
10
- * This prevents the "Back to the Future" replay attack where tokens are
11
- * captured and replayed months or years later.
12
- *
13
- * CWE-294: Authentication Bypass by Capture-replay
14
- *
15
- * @see https://securitypattern.com/post/jwt-back-to-the-future
16
- * @see LightSEC 2025 - "JWT Back to the future: On the (ab)use of JWTs"
17
- */
18
- import type { TSESLint } from '@interlace/eslint-devkit';
19
- import type { JwtRuleOptions } from '../../types';
20
- type RuleOptions = [JwtRuleOptions?];
21
- export declare const requireMaxAge: TSESLint.RuleModule<"missingMaxAge", RuleOptions, unknown, TSESLint.RuleListener> & {
22
- name: string;
23
- };
24
- export default requireMaxAge;
@@ -1,93 +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.requireMaxAge = void 0;
9
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- const utils_1 = require("../../utils");
11
- exports.requireMaxAge = (0, eslint_devkit_1.createRule)({
12
- name: 'require-max-age',
13
- meta: {
14
- type: 'problem',
15
- docs: {
16
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-max-age.md',
17
- description: 'Require maxAge option in verify operations to enforce token freshness',
18
- cwe: 'CWE-294',
19
- cvss: 5,
20
- },
21
- fixable: undefined,
22
- hasSuggestions: false,
23
- messages: {
24
- missingMaxAge: (0, eslint_devkit_1.formatLLMMessage)({
25
- icon: eslint_devkit_1.MessageIcons.SECURITY,
26
- issueName: 'Missing Token Age Validation',
27
- cwe: 'CWE-294',
28
- description: 'Without maxAge, tokens can be replayed long after issuance (Back to the Future attack)',
29
- severity: 'MEDIUM',
30
- fix: 'Add maxAge option: { maxAge: "1h" } to limit token lifetime',
31
- documentationLink: 'https://securitypattern.com/post/jwt-back-to-the-future',
32
- }),
33
- },
34
- schema: [
35
- {
36
- type: 'object',
37
- properties: {
38
- trustedSanitizers: {
39
- type: 'array',
40
- items: { type: 'string' },
41
- default: [],
42
- },
43
- trustedAnnotations: {
44
- type: 'array',
45
- items: { type: 'string' },
46
- default: [],
47
- },
48
- strictMode: {
49
- type: 'boolean',
50
- default: false,
51
- },
52
- },
53
- additionalProperties: false,
54
- },
55
- ],
56
- },
57
- defaultOptions: [
58
- {
59
- trustedSanitizers: [],
60
- trustedAnnotations: [],
61
- strictMode: false,
62
- },
63
- ],
64
- create(context) {
65
- return {
66
- CallExpression(node) {
67
- if (!(0, utils_1.isVerifyOperation)(node)) {
68
- return;
69
- }
70
- if (node.arguments.length < 2) {
71
- return;
72
- }
73
- const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
74
- // No options at all
75
- if (!optionsArg) {
76
- context.report({
77
- node,
78
- messageId: 'missingMaxAge',
79
- });
80
- return;
81
- }
82
- // Check for maxAge option
83
- if (!(0, utils_1.hasOption)(optionsArg, 'maxAge') && !(0, utils_1.hasOption)(optionsArg, 'clockTolerance')) {
84
- context.report({
85
- node: optionsArg,
86
- messageId: 'missingMaxAge',
87
- });
88
- }
89
- },
90
- };
91
- },
92
- });
93
- exports.default = exports.requireMaxAge;
@@ -1,7 +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 });
@@ -1,98 +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
- * Shared utilities for JWT security rules
8
- *
9
- * Library detection, pattern matching, and common helpers.
10
- */
11
- import type { TSESTree } from '@interlace/eslint-devkit';
12
- /**
13
- * Supported JWT libraries
14
- */
15
- export declare const JWT_LIBRARIES: {
16
- readonly JSONWEBTOKEN: "jsonwebtoken";
17
- readonly JOSE: "jose";
18
- readonly EXPRESS_JWT: "express-jwt";
19
- readonly NESTJS_JWT: "@nestjs/jwt";
20
- readonly JWKS_RSA: "jwks-rsa";
21
- readonly JWT_DECODE: "jwt-decode";
22
- };
23
- export type JwtLibrary = (typeof JWT_LIBRARIES)[keyof typeof JWT_LIBRARIES];
24
- /**
25
- * Insecure algorithms that should be flagged
26
- */
27
- export declare const INSECURE_ALGORITHMS: Set<string>;
28
- /**
29
- * Algorithms vulnerable to confusion attacks when used with asymmetric keys
30
- */
31
- export declare const SYMMETRIC_ALGORITHMS: Set<string>;
32
- /**
33
- * Recommended secure algorithms
34
- */
35
- export declare const SECURE_ALGORITHMS: Set<string>;
36
- /**
37
- * Sensitive field names that should not be in JWT payload
38
- * All lowercase for case-insensitive matching
39
- */
40
- export declare const SENSITIVE_PAYLOAD_FIELDS: Set<string>;
41
- /**
42
- * JWT method patterns for different operations
43
- */
44
- export declare const JWT_METHODS: {
45
- readonly SIGN: Set<string>;
46
- readonly VERIFY: Set<string>;
47
- readonly DECODE: Set<string>;
48
- };
49
- /**
50
- * Check if a node represents a call to a JWT library method
51
- */
52
- export declare function isJwtLibraryCall(node: TSESTree.CallExpression, targetMethods: Set<string>): boolean;
53
- /**
54
- * Check if a node is a string literal with a specific value
55
- */
56
- export declare function isStringLiteral(node: TSESTree.Node, value?: string): node is TSESTree.Literal;
57
- /**
58
- * Extract algorithm from options object
59
- */
60
- export declare function extractAlgorithms(optionsNode: TSESTree.ObjectExpression): string[];
61
- /**
62
- * Check if options object has a specific property set
63
- */
64
- export declare function hasOption(optionsNode: TSESTree.ObjectExpression, optionName: string): boolean;
65
- /**
66
- * Get the value of a specific option from options object
67
- */
68
- export declare function getOptionValue(optionsNode: TSESTree.ObjectExpression, optionName: string): TSESTree.Node | undefined;
69
- /**
70
- * Check if a literal appears to be a weak secret (short string)
71
- */
72
- export declare function isWeakSecret(node: TSESTree.Node, minLength?: number): boolean;
73
- /**
74
- * Check if a node is an environment variable access (safe pattern)
75
- */
76
- export declare function isEnvVariable(node: TSESTree.Node): boolean;
77
- /**
78
- * Check if this call looks like a JWT sign operation
79
- */
80
- export declare function isSignOperation(node: TSESTree.CallExpression): boolean;
81
- /**
82
- * Check if this call looks like a JWT verify operation
83
- */
84
- export declare function isVerifyOperation(node: TSESTree.CallExpression): boolean;
85
- /**
86
- * Check if this call looks like a JWT decode operation (no verification)
87
- */
88
- export declare function isDecodeOperation(node: TSESTree.CallExpression): boolean;
89
- /**
90
- * Get the options argument from a JWT call
91
- * For jwt.verify(token, secret, options) -> returns options
92
- * For jwt.sign(payload, secret, options) -> returns options
93
- */
94
- export declare function getOptionsArgument(node: TSESTree.CallExpression, optionsIndex?: number): TSESTree.ObjectExpression | undefined;
95
- /**
96
- * Check if file is a test file
97
- */
98
- export declare function isTestFile(filename: string): boolean;