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.
- package/README.md +5 -5
- package/package.json +16 -24
- package/src/index.d.ts +17 -0
- package/src/index.js +1 -0
- package/src/oxlint.d.ts +17 -0
- package/src/oxlint.js +1 -0
- package/src/rules/no-algorithm-confusion/index.js +1 -0
- package/src/rules/no-algorithm-none/index.js +1 -0
- package/src/rules/no-decode-without-verify/index.js +1 -0
- package/src/rules/no-hardcoded-secret/index.js +1 -0
- package/src/rules/no-sensitive-payload/index.js +1 -0
- package/src/rules/no-timestamp-manipulation/index.js +1 -0
- package/src/rules/no-weak-secret/index.js +1 -0
- package/src/rules/require-algorithm-whitelist/index.js +1 -0
- package/src/rules/require-audience-validation/index.js +1 -0
- package/src/rules/require-expiration/index.js +1 -0
- package/src/rules/require-issued-at/index.js +1 -0
- package/src/rules/require-issuer-validation/index.js +1 -0
- package/src/rules/require-max-age/index.js +1 -0
- package/src/types/index.js +1 -0
- package/src/utils/index.js +1 -0
- package/AGENTS.md +0 -102
- package/CHANGELOG.md +0 -161
- package/dist/AGENTS.md +0 -102
- package/dist/CHANGELOG.md +0 -161
- package/dist/LICENSE +0 -23
- package/dist/README.md +0 -158
- package/dist/package.json +0 -125
- package/dist/src/index.d.ts +0 -101
- package/dist/src/index.js +0 -171
- package/dist/src/oxlint.d.ts +0 -20
- package/dist/src/oxlint.js +0 -21
- package/dist/src/rules/no-algorithm-confusion/index.d.ts +0 -24
- package/dist/src/rules/no-algorithm-confusion/index.js +0 -165
- package/dist/src/rules/no-algorithm-none/index.d.ts +0 -24
- package/dist/src/rules/no-algorithm-none/index.js +0 -222
- package/dist/src/rules/no-decode-without-verify/index.d.ts +0 -23
- package/dist/src/rules/no-decode-without-verify/index.js +0 -131
- package/dist/src/rules/no-hardcoded-secret/index.d.ts +0 -23
- package/dist/src/rules/no-hardcoded-secret/index.js +0 -189
- package/dist/src/rules/no-sensitive-payload/index.d.ts +0 -20
- package/dist/src/rules/no-sensitive-payload/index.js +0 -103
- package/dist/src/rules/no-timestamp-manipulation/index.d.ts +0 -23
- package/dist/src/rules/no-timestamp-manipulation/index.js +0 -97
- package/dist/src/rules/no-weak-secret/index.d.ts +0 -23
- package/dist/src/rules/no-weak-secret/index.js +0 -165
- package/dist/src/rules/require-algorithm-whitelist/index.d.ts +0 -24
- package/dist/src/rules/require-algorithm-whitelist/index.js +0 -113
- package/dist/src/rules/require-audience-validation/index.d.ts +0 -20
- package/dist/src/rules/require-audience-validation/index.js +0 -100
- package/dist/src/rules/require-expiration/index.d.ts +0 -23
- package/dist/src/rules/require-expiration/index.js +0 -136
- package/dist/src/rules/require-issued-at/index.d.ts +0 -23
- package/dist/src/rules/require-issued-at/index.js +0 -113
- package/dist/src/rules/require-issuer-validation/index.d.ts +0 -20
- package/dist/src/rules/require-issuer-validation/index.js +0 -100
- package/dist/src/rules/require-max-age/index.d.ts +0 -24
- package/dist/src/rules/require-max-age/index.js +0 -93
- package/dist/src/types/index.js +0 -7
- package/dist/src/utils/index.d.ts +0 -98
- package/dist/src/utils/index.js +0 -238
- package/src/__compatibility__/jwt-interface.spec.ts +0 -192
- package/src/index.test.ts +0 -47
- package/src/index.ts +0 -209
- package/src/oxlint.test.ts +0 -61
- package/src/oxlint.ts +0 -23
- package/src/rules/no-algorithm-confusion/index.ts +0 -208
- package/src/rules/no-algorithm-confusion/no-algorithm-confusion.test.ts +0 -106
- package/src/rules/no-algorithm-none/index.ts +0 -270
- package/src/rules/no-algorithm-none/no-algorithm-none.test.ts +0 -146
- package/src/rules/no-decode-without-verify/index.ts +0 -162
- package/src/rules/no-decode-without-verify/no-decode-without-verify.test.ts +0 -76
- package/src/rules/no-hardcoded-secret/index.ts +0 -230
- package/src/rules/no-hardcoded-secret/no-hardcoded-secret.test.ts +0 -97
- package/src/rules/no-sensitive-payload/index.ts +0 -123
- package/src/rules/no-sensitive-payload/no-sensitive-payload.test.ts +0 -157
- package/src/rules/no-timestamp-manipulation/index.ts +0 -118
- package/src/rules/no-timestamp-manipulation/no-timestamp-manipulation.test.ts +0 -54
- package/src/rules/no-weak-secret/index.ts +0 -204
- package/src/rules/no-weak-secret/no-weak-secret.test.ts +0 -114
- package/src/rules/require-algorithm-whitelist/index.ts +0 -145
- package/src/rules/require-algorithm-whitelist/require-algorithm-whitelist.test.ts +0 -72
- package/src/rules/require-audience-validation/index.ts +0 -120
- package/src/rules/require-audience-validation/require-audience-validation.test.ts +0 -68
- package/src/rules/require-expiration/index.ts +0 -170
- package/src/rules/require-expiration/require-expiration.test.ts +0 -135
- package/src/rules/require-issued-at/index.ts +0 -143
- package/src/rules/require-issued-at/require-issued-at.test.ts +0 -64
- package/src/rules/require-issuer-validation/index.ts +0 -120
- package/src/rules/require-issuer-validation/require-issuer-validation.test.ts +0 -68
- package/src/rules/require-max-age/index.ts +0 -117
- package/src/rules/require-max-age/require-max-age.test.ts +0 -69
- package/src/tests/coverage-dual-layer.test.ts +0 -323
- package/src/types/index.ts +0 -107
- package/src/utils/index.ts +0 -285
- package/src/utils/utils.test.ts +0 -324
- /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;
|
package/dist/src/types/index.js
DELETED
|
@@ -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;
|