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.
- package/AGENTS.md +102 -0
- package/CHANGELOG.md +161 -0
- package/LICENSE +23 -0
- package/README.md +158 -0
- package/dist/AGENTS.md +102 -0
- package/dist/CHANGELOG.md +161 -0
- package/dist/LICENSE +23 -0
- package/dist/README.md +158 -0
- package/dist/package.json +125 -0
- package/dist/src/index.d.ts +101 -0
- package/dist/src/index.js +171 -0
- package/dist/src/oxlint.d.ts +20 -0
- package/dist/src/oxlint.js +21 -0
- package/dist/src/rules/no-algorithm-confusion/index.d.ts +24 -0
- package/dist/src/rules/no-algorithm-confusion/index.js +165 -0
- package/dist/src/rules/no-algorithm-none/index.d.ts +24 -0
- package/dist/src/rules/no-algorithm-none/index.js +222 -0
- package/dist/src/rules/no-decode-without-verify/index.d.ts +23 -0
- package/dist/src/rules/no-decode-without-verify/index.js +131 -0
- package/dist/src/rules/no-hardcoded-secret/index.d.ts +23 -0
- package/dist/src/rules/no-hardcoded-secret/index.js +189 -0
- package/dist/src/rules/no-sensitive-payload/index.d.ts +20 -0
- package/dist/src/rules/no-sensitive-payload/index.js +103 -0
- package/dist/src/rules/no-timestamp-manipulation/index.d.ts +23 -0
- package/dist/src/rules/no-timestamp-manipulation/index.js +97 -0
- package/dist/src/rules/no-weak-secret/index.d.ts +23 -0
- package/dist/src/rules/no-weak-secret/index.js +165 -0
- package/dist/src/rules/require-algorithm-whitelist/index.d.ts +24 -0
- package/dist/src/rules/require-algorithm-whitelist/index.js +113 -0
- package/dist/src/rules/require-audience-validation/index.d.ts +20 -0
- package/dist/src/rules/require-audience-validation/index.js +100 -0
- package/dist/src/rules/require-expiration/index.d.ts +23 -0
- package/dist/src/rules/require-expiration/index.js +136 -0
- package/dist/src/rules/require-issued-at/index.d.ts +23 -0
- package/dist/src/rules/require-issued-at/index.js +113 -0
- package/dist/src/rules/require-issuer-validation/index.d.ts +20 -0
- package/dist/src/rules/require-issuer-validation/index.js +100 -0
- package/dist/src/rules/require-max-age/index.d.ts +24 -0
- package/dist/src/rules/require-max-age/index.js +93 -0
- package/dist/src/types/index.d.ts +82 -0
- package/dist/src/types/index.js +7 -0
- package/dist/src/utils/index.d.ts +98 -0
- package/dist/src/utils/index.js +238 -0
- package/package.json +125 -0
- package/src/__compatibility__/jwt-interface.spec.ts +192 -0
- package/src/index.test.ts +47 -0
- package/src/index.ts +209 -0
- package/src/oxlint.test.ts +61 -0
- package/src/oxlint.ts +23 -0
- package/src/rules/no-algorithm-confusion/index.ts +208 -0
- package/src/rules/no-algorithm-confusion/no-algorithm-confusion.test.ts +106 -0
- package/src/rules/no-algorithm-none/index.ts +270 -0
- package/src/rules/no-algorithm-none/no-algorithm-none.test.ts +146 -0
- package/src/rules/no-decode-without-verify/index.ts +162 -0
- package/src/rules/no-decode-without-verify/no-decode-without-verify.test.ts +76 -0
- package/src/rules/no-hardcoded-secret/index.ts +230 -0
- package/src/rules/no-hardcoded-secret/no-hardcoded-secret.test.ts +97 -0
- package/src/rules/no-sensitive-payload/index.ts +123 -0
- package/src/rules/no-sensitive-payload/no-sensitive-payload.test.ts +157 -0
- package/src/rules/no-timestamp-manipulation/index.ts +118 -0
- package/src/rules/no-timestamp-manipulation/no-timestamp-manipulation.test.ts +54 -0
- package/src/rules/no-weak-secret/index.ts +204 -0
- package/src/rules/no-weak-secret/no-weak-secret.test.ts +114 -0
- package/src/rules/require-algorithm-whitelist/index.ts +145 -0
- package/src/rules/require-algorithm-whitelist/require-algorithm-whitelist.test.ts +72 -0
- package/src/rules/require-audience-validation/index.ts +120 -0
- package/src/rules/require-audience-validation/require-audience-validation.test.ts +68 -0
- package/src/rules/require-expiration/index.ts +170 -0
- package/src/rules/require-expiration/require-expiration.test.ts +135 -0
- package/src/rules/require-issued-at/index.ts +143 -0
- package/src/rules/require-issued-at/require-issued-at.test.ts +64 -0
- package/src/rules/require-issuer-validation/index.ts +120 -0
- package/src/rules/require-issuer-validation/require-issuer-validation.test.ts +68 -0
- package/src/rules/require-max-age/index.ts +117 -0
- package/src/rules/require-max-age/require-max-age.test.ts +69 -0
- package/src/tests/coverage-dual-layer.test.ts +323 -0
- package/src/types/index.ts +107 -0
- package/src/utils/index.ts +285 -0
- package/src/utils/utils.test.ts +324 -0
|
@@ -0,0 +1,113 @@
|
|
|
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;
|
|
@@ -0,0 +1,20 @@
|
|
|
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;
|
|
@@ -0,0 +1,100 @@
|
|
|
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;
|
|
@@ -0,0 +1,24 @@
|
|
|
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;
|
|
@@ -0,0 +1,93 @@
|
|
|
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;
|
|
@@ -0,0 +1,82 @@
|
|
|
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-plugin-jwt-security Type Exports
|
|
8
|
+
*
|
|
9
|
+
* Barrel file that exports all JWT security rule Options types.
|
|
10
|
+
*/
|
|
11
|
+
import type { SecurityRuleOptions } from '@interlace/eslint-devkit';
|
|
12
|
+
export interface JwtRuleOptions extends SecurityRuleOptions {
|
|
13
|
+
/** JWT libraries to consider for detection. Default: all supported libraries */
|
|
14
|
+
targetLibraries?: Array<'jsonwebtoken' | 'jose' | 'express-jwt' | '@nestjs/jwt' | 'jwks-rsa' | 'jwt-decode'>;
|
|
15
|
+
}
|
|
16
|
+
export interface NoAlgorithmNoneOptions extends JwtRuleOptions {
|
|
17
|
+
/** Allow 'none' algorithm in test files. Default: false */
|
|
18
|
+
allowInTests?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface NoAlgorithmConfusionOptions extends JwtRuleOptions {
|
|
21
|
+
/** Symmetric algorithms that should not be used with public keys */
|
|
22
|
+
symmetricAlgorithms?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface RequireAlgorithmWhitelistOptions extends JwtRuleOptions {
|
|
25
|
+
/** Recommended algorithms to suggest. Default: ['RS256', 'ES256'] */
|
|
26
|
+
recommendedAlgorithms?: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface NoDecodeWithoutVerifyOptions extends JwtRuleOptions {
|
|
29
|
+
/** Allow decode() for header inspection before verify. Default: false */
|
|
30
|
+
allowHeaderInspection?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface NoWeakSecretOptions extends JwtRuleOptions {
|
|
33
|
+
/** Minimum secret length in characters. Default: 32 (256 bits) */
|
|
34
|
+
minSecretLength?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface RequireExpirationOptions extends JwtRuleOptions {
|
|
37
|
+
/** Maximum allowed expiration time in seconds. Default: 86400 (24 hours) */
|
|
38
|
+
maxExpirationSeconds?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface RequireIssuerValidationOptions extends JwtRuleOptions {
|
|
41
|
+
/** Known valid issuers to suggest. Default: [] */
|
|
42
|
+
knownIssuers?: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface RequireAudienceValidationOptions extends JwtRuleOptions {
|
|
45
|
+
/** Known valid audiences to suggest. Default: [] */
|
|
46
|
+
knownAudiences?: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface NoSensitivePayloadOptions extends JwtRuleOptions {
|
|
49
|
+
/** Additional field names to flag as sensitive. Default: [] */
|
|
50
|
+
additionalSensitiveFields?: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface NoHardcodedSecretOptions extends JwtRuleOptions {
|
|
53
|
+
/** Patterns that indicate environment variable usage (safe). Default: process.env */
|
|
54
|
+
envPatterns?: string[];
|
|
55
|
+
}
|
|
56
|
+
export interface RequireJwksValidationOptions extends JwtRuleOptions {
|
|
57
|
+
/** Require cache configuration. Default: true */
|
|
58
|
+
requireCache?: boolean;
|
|
59
|
+
/** Require rate limiting. Default: true */
|
|
60
|
+
requireRateLimit?: boolean;
|
|
61
|
+
}
|
|
62
|
+
export interface NoInsecureKidOptions extends JwtRuleOptions {
|
|
63
|
+
/** Allow custom KID validation functions. Default: [] */
|
|
64
|
+
trustedKidValidators?: string[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Combined type for all JWT rule options
|
|
68
|
+
*/
|
|
69
|
+
export type AllJwtRulesOptions = {
|
|
70
|
+
'no-algorithm-none'?: NoAlgorithmNoneOptions;
|
|
71
|
+
'no-algorithm-confusion'?: NoAlgorithmConfusionOptions;
|
|
72
|
+
'require-algorithm-whitelist'?: RequireAlgorithmWhitelistOptions;
|
|
73
|
+
'no-decode-without-verify'?: NoDecodeWithoutVerifyOptions;
|
|
74
|
+
'no-weak-secret'?: NoWeakSecretOptions;
|
|
75
|
+
'require-expiration'?: RequireExpirationOptions;
|
|
76
|
+
'require-issuer-validation'?: RequireIssuerValidationOptions;
|
|
77
|
+
'require-audience-validation'?: RequireAudienceValidationOptions;
|
|
78
|
+
'no-sensitive-payload'?: NoSensitivePayloadOptions;
|
|
79
|
+
'no-hardcoded-secret'?: NoHardcodedSecretOptions;
|
|
80
|
+
'require-jwks-validation'?: RequireJwksValidationOptions;
|
|
81
|
+
'no-insecure-kid'?: NoInsecureKidOptions;
|
|
82
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
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;
|