eslint-plugin-jwt-security 2.3.0 → 2.3.1
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/{dist/src → src}/index.js +16 -69
- package/src/oxlint.d.ts +17 -0
- package/src/oxlint.js +3 -0
- package/{dist/src → src}/rules/no-algorithm-confusion/index.js +0 -20
- package/{dist/src → src}/rules/no-algorithm-none/index.js +0 -38
- package/{dist/src → src}/rules/no-decode-without-verify/index.js +0 -14
- package/{dist/src → src}/rules/no-hardcoded-secret/index.js +0 -39
- package/{dist/src → src}/rules/no-sensitive-payload/index.js +0 -6
- package/{dist/src → src}/rules/no-timestamp-manipulation/index.js +0 -6
- package/{dist/src → src}/rules/no-weak-secret/index.js +0 -19
- package/{dist/src → src}/rules/require-algorithm-whitelist/index.js +0 -9
- package/{dist/src → src}/rules/require-audience-validation/index.js +0 -7
- package/{dist/src → src}/rules/require-expiration/index.js +0 -16
- package/{dist/src → src}/rules/require-issued-at/index.js +0 -21
- package/{dist/src → src}/rules/require-issuer-validation/index.js +0 -7
- package/{dist/src → src}/rules/require-max-age/index.js +0 -7
- package/src/types/index.js +2 -0
- package/{dist/src → src}/utils/index.js +1 -74
- 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/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-none/index.d.ts +0 -24
- package/dist/src/rules/no-decode-without-verify/index.d.ts +0 -23
- package/dist/src/rules/no-hardcoded-secret/index.d.ts +0 -23
- package/dist/src/rules/no-sensitive-payload/index.d.ts +0 -20
- package/dist/src/rules/no-timestamp-manipulation/index.d.ts +0 -23
- package/dist/src/rules/no-weak-secret/index.d.ts +0 -23
- package/dist/src/rules/require-algorithm-whitelist/index.d.ts +0 -24
- package/dist/src/rules/require-audience-validation/index.d.ts +0 -20
- package/dist/src/rules/require-expiration/index.d.ts +0 -23
- package/dist/src/rules/require-issued-at/index.d.ts +0 -23
- package/dist/src/rules/require-issuer-validation/index.d.ts +0 -20
- package/dist/src/rules/require-max-age/index.d.ts +0 -24
- package/dist/src/types/index.js +0 -7
- package/dist/src/utils/index.d.ts +0 -98
- 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
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security" target="blank"><img src="https://eslint.interlace.tools/
|
|
2
|
+
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security" target="blank"><img src="https://eslint.interlace.tools/logos/interlace.svg" alt="Interlace" height="90" /></a>
|
|
3
3
|
|
|
4
|
-
<a href="https://
|
|
4
|
+
<a href="https://jwt.io" target="_blank"><img src="https://eslint.interlace.tools/logos/jwt.svg" alt="JSON Web Tokens" height="90" /></a>
|
|
5
5
|
|
|
6
|
-
<a href="https://oxc.rs" target="_blank"><img src="https://eslint.interlace.tools/oxlint
|
|
6
|
+
<a href="https://oxc.rs" target="_blank"><img src="https://eslint.interlace.tools/logos/oxlint.svg" alt="oxlint" height="90" /></a>
|
|
7
7
|
|
|
8
|
-
<a href="https://
|
|
8
|
+
<a href="https://eslint.org" target="_blank"><img src="https://eslint.interlace.tools/logos/eslint.svg" alt="ESLint" height="90" /></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -150,7 +150,7 @@ If this plugin caught a real bug for you, **[star the repo](https://github.com/o
|
|
|
150
150
|
MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
|
151
151
|
|
|
152
152
|
<p align="center">
|
|
153
|
-
<a href="https://eslint.interlace.tools/docs/security/plugin-jwt?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security"><img src="https://eslint.interlace.tools/images/og-jwt.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
153
|
+
<a href="https://eslint.interlace.tools/docs/security/plugin-jwt?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security"><img src="https://eslint.interlace.tools/images/og-jwt-security.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
154
154
|
</p>
|
|
155
155
|
|
|
156
156
|
<p align="center">
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-jwt-security",
|
|
3
|
-
"version": "2.3.
|
|
4
|
-
"description": "ESLint plugin for JWT security
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "ESLint plugin for JWT security — detects algorithm confusion (CVE-2022-23540), alg:none, weak or hardcoded secrets, and decode-without-verify.",
|
|
5
5
|
"type": "commonjs",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"default": "./
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./types": {
|
|
14
14
|
"types": "./src/types/index.d.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"homepage": "https://eslint.interlace.tools/docs/security/plugin-jwt-security?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security-security",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/ofri-peretz/eslint",
|
|
27
|
+
"url": "git+https://github.com/ofri-peretz/eslint.git",
|
|
28
28
|
"directory": "packages/eslint-plugin-jwt-security"
|
|
29
29
|
},
|
|
30
30
|
"bugs": {
|
|
@@ -35,11 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"src/",
|
|
38
|
-
"dist/",
|
|
39
38
|
"README.md",
|
|
40
|
-
"LICENSE"
|
|
41
|
-
"CHANGELOG.md",
|
|
42
|
-
"AGENTS.md"
|
|
39
|
+
"LICENSE"
|
|
43
40
|
],
|
|
44
41
|
"keywords": [
|
|
45
42
|
"eslint",
|
|
@@ -82,11 +79,16 @@
|
|
|
82
79
|
"node": ">=18.0.0"
|
|
83
80
|
},
|
|
84
81
|
"dependencies": {
|
|
85
|
-
"
|
|
86
|
-
"@interlace/eslint-devkit": "^1.4.3"
|
|
82
|
+
"@interlace/eslint-devkit": "^1.8.0"
|
|
87
83
|
},
|
|
88
84
|
"peerDependencies": {
|
|
89
|
-
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
|
85
|
+
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
|
86
|
+
"jsonwebtoken": "^8.0.0 || ^9.0.0",
|
|
87
|
+
"@nestjs/jwt": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
88
|
+
"express-jwt": "^7.0.0 || ^8.0.0",
|
|
89
|
+
"jose": "^4.0.0 || ^5.0.0 || ^6.0.0",
|
|
90
|
+
"jwks-rsa": "^3.0.0 || ^4.0.0",
|
|
91
|
+
"jwt-decode": "^3.0.0 || ^4.0.0"
|
|
90
92
|
},
|
|
91
93
|
"peerDependenciesMeta": {
|
|
92
94
|
"jsonwebtoken": {
|
|
@@ -108,16 +110,6 @@
|
|
|
108
110
|
"optional": true
|
|
109
111
|
}
|
|
110
112
|
},
|
|
111
|
-
"devDependencies": {
|
|
112
|
-
"@typescript-eslint/parser": "^8.46.2",
|
|
113
|
-
"@typescript-eslint/rule-tester": "^8.46.2"
|
|
114
|
-
},
|
|
115
|
-
"scripts": {
|
|
116
|
-
"build": "tsx ../../scripts/build-package.ts",
|
|
117
|
-
"test": "vitest run",
|
|
118
|
-
"test:coverage": "vitest run --coverage",
|
|
119
|
-
"typecheck": "tsc -p tsconfig.lib.json --noEmit"
|
|
120
|
-
},
|
|
121
113
|
"funding": {
|
|
122
114
|
"type": "github",
|
|
123
115
|
"url": "https://github.com/ofri-peretz/eslint"
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Minimal declaration written by scripts/build-package.ts.
|
|
2
|
+
//
|
|
3
|
+
// tsc's generated entry declaration inlined every inferred rule-option type
|
|
4
|
+
// (up to 166 kB per plugin) for an API no consumer calls directly. This types
|
|
5
|
+
// what a flat config actually touches. Per-rule option types remain available
|
|
6
|
+
// from the "./types" subpath export where a plugin provides one.
|
|
7
|
+
import type { Linter, Rule } from 'eslint';
|
|
8
|
+
|
|
9
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
10
|
+
export declare const configs: Record<string, Linter.Config>;
|
|
11
|
+
export declare const plugin: {
|
|
12
|
+
meta: { name: string; version: string };
|
|
13
|
+
rules: Record<string, Rule.RuleModule>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare const _default: typeof plugin;
|
|
17
|
+
export default _default;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noSensitivePayload = exports.requireMaxAge = exports.noTimestampManipulation = exports.requireAudienceValidation = exports.requireIssuerValidation = exports.requireIssuedAt = exports.noHardcodedSecret = exports.requireExpiration = exports.noWeakSecret = exports.noDecodeWithoutVerify = exports.requireAlgorithmWhitelist = exports.noAlgorithmConfusion = exports.noAlgorithmNone = exports.configs = exports.plugin = exports.rules = void 0;
|
|
9
|
-
// Import rules - Core Security
|
|
10
4
|
const no_algorithm_none_1 = require("./rules/no-algorithm-none");
|
|
11
5
|
Object.defineProperty(exports, "noAlgorithmNone", { enumerable: true, get: function () { return no_algorithm_none_1.noAlgorithmNone; } });
|
|
12
6
|
const no_algorithm_confusion_1 = require("./rules/no-algorithm-confusion");
|
|
@@ -21,7 +15,6 @@ const require_expiration_1 = require("./rules/require-expiration");
|
|
|
21
15
|
Object.defineProperty(exports, "requireExpiration", { enumerable: true, get: function () { return require_expiration_1.requireExpiration; } });
|
|
22
16
|
const no_hardcoded_secret_1 = require("./rules/no-hardcoded-secret");
|
|
23
17
|
Object.defineProperty(exports, "noHardcodedSecret", { enumerable: true, get: function () { return no_hardcoded_secret_1.noHardcodedSecret; } });
|
|
24
|
-
// Import rules - 2025 Vulnerability Research
|
|
25
18
|
const require_issued_at_1 = require("./rules/require-issued-at");
|
|
26
19
|
Object.defineProperty(exports, "requireIssuedAt", { enumerable: true, get: function () { return require_issued_at_1.requireIssuedAt; } });
|
|
27
20
|
const require_issuer_validation_1 = require("./rules/require-issuer-validation");
|
|
@@ -34,60 +27,41 @@ const require_max_age_1 = require("./rules/require-max-age");
|
|
|
34
27
|
Object.defineProperty(exports, "requireMaxAge", { enumerable: true, get: function () { return require_max_age_1.requireMaxAge; } });
|
|
35
28
|
const no_sensitive_payload_1 = require("./rules/no-sensitive-payload");
|
|
36
29
|
Object.defineProperty(exports, "noSensitivePayload", { enumerable: true, get: function () { return no_sensitive_payload_1.noSensitivePayload; } });
|
|
37
|
-
/**
|
|
38
|
-
* Collection of all JWT security ESLint rules (13 rules)
|
|
39
|
-
*/
|
|
40
30
|
exports.rules = {
|
|
41
|
-
|
|
42
|
-
'no-algorithm-
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'no-
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
'require-
|
|
51
|
-
'
|
|
52
|
-
'require-
|
|
53
|
-
'no-
|
|
54
|
-
|
|
55
|
-
'no-sensitive-payload': no_sensitive_payload_1.noSensitivePayload,
|
|
31
|
+
get 'no-algorithm-none'() { return require("./rules/no-algorithm-none").noAlgorithmNone; },
|
|
32
|
+
get 'no-algorithm-confusion'() { return require("./rules/no-algorithm-confusion").noAlgorithmConfusion; },
|
|
33
|
+
get 'require-algorithm-whitelist'() { return require("./rules/require-algorithm-whitelist").requireAlgorithmWhitelist; },
|
|
34
|
+
get 'no-decode-without-verify'() { return require("./rules/no-decode-without-verify").noDecodeWithoutVerify; },
|
|
35
|
+
get 'no-weak-secret'() { return require("./rules/no-weak-secret").noWeakSecret; },
|
|
36
|
+
get 'require-expiration'() { return require("./rules/require-expiration").requireExpiration; },
|
|
37
|
+
get 'no-hardcoded-secret'() { return require("./rules/no-hardcoded-secret").noHardcodedSecret; },
|
|
38
|
+
get 'require-issued-at'() { return require("./rules/require-issued-at").requireIssuedAt; },
|
|
39
|
+
get 'require-issuer-validation'() { return require("./rules/require-issuer-validation").requireIssuerValidation; },
|
|
40
|
+
get 'require-audience-validation'() { return require("./rules/require-audience-validation").requireAudienceValidation; },
|
|
41
|
+
get 'no-timestamp-manipulation'() { return require("./rules/no-timestamp-manipulation").noTimestampManipulation; },
|
|
42
|
+
get 'require-max-age'() { return require("./rules/require-max-age").requireMaxAge; },
|
|
43
|
+
get 'no-sensitive-payload'() { return require("./rules/no-sensitive-payload").noSensitivePayload; },
|
|
44
|
+
|
|
56
45
|
};
|
|
57
|
-
/**
|
|
58
|
-
* ESLint Plugin object
|
|
59
|
-
*/
|
|
60
46
|
exports.plugin = {
|
|
61
47
|
meta: {
|
|
62
48
|
name: 'eslint-plugin-jwt-security',
|
|
63
|
-
version: '2.
|
|
49
|
+
version: '2.3.1',
|
|
64
50
|
},
|
|
65
51
|
rules: exports.rules,
|
|
66
52
|
};
|
|
67
|
-
/**
|
|
68
|
-
* Recommended configuration - baseline security
|
|
69
|
-
* Critical rules as errors, high as warnings
|
|
70
|
-
*/
|
|
71
53
|
const recommendedRules = {
|
|
72
|
-
// CRITICAL - Algorithm attacks
|
|
73
54
|
'jwt/no-algorithm-none': 'error',
|
|
74
55
|
'jwt/no-algorithm-confusion': 'error',
|
|
75
|
-
// HIGH - Verification and secrets
|
|
76
56
|
'jwt/require-algorithm-whitelist': 'error',
|
|
77
57
|
'jwt/no-decode-without-verify': 'error',
|
|
78
58
|
'jwt/no-weak-secret': 'error',
|
|
79
59
|
'jwt/no-hardcoded-secret': 'error',
|
|
80
|
-
'jwt/no-timestamp-manipulation': 'error',
|
|
81
|
-
// MEDIUM - Best practices
|
|
60
|
+
'jwt/no-timestamp-manipulation': 'error',
|
|
82
61
|
'jwt/require-expiration': 'error',
|
|
83
62
|
'jwt/no-sensitive-payload': 'warn',
|
|
84
63
|
};
|
|
85
|
-
/**
|
|
86
|
-
* Strict configuration - maximum security (2025 research)
|
|
87
|
-
* All rules as errors - includes replay attack prevention
|
|
88
|
-
*/
|
|
89
64
|
const strictRules = {
|
|
90
|
-
// All core rules as errors
|
|
91
65
|
'jwt/no-algorithm-none': 'error',
|
|
92
66
|
'jwt/no-algorithm-confusion': 'error',
|
|
93
67
|
'jwt/require-algorithm-whitelist': 'error',
|
|
@@ -95,7 +69,6 @@ const strictRules = {
|
|
|
95
69
|
'jwt/no-weak-secret': 'error',
|
|
96
70
|
'jwt/no-hardcoded-secret': 'error',
|
|
97
71
|
'jwt/require-expiration': 'error',
|
|
98
|
-
// 2025 Research - Full replay attack prevention
|
|
99
72
|
'jwt/require-issued-at': 'error',
|
|
100
73
|
'jwt/require-issuer-validation': 'error',
|
|
101
74
|
'jwt/require-audience-validation': 'error',
|
|
@@ -103,25 +76,12 @@ const strictRules = {
|
|
|
103
76
|
'jwt/require-max-age': 'error',
|
|
104
77
|
'jwt/no-sensitive-payload': 'error',
|
|
105
78
|
};
|
|
106
|
-
/**
|
|
107
|
-
* Legacy configuration - migration mode
|
|
108
|
-
* Only critical rules enabled
|
|
109
|
-
*/
|
|
110
79
|
const legacyRules = {
|
|
111
|
-
// Only most critical rules
|
|
112
80
|
'jwt/no-algorithm-none': 'error',
|
|
113
81
|
'jwt/no-algorithm-confusion': 'error',
|
|
114
82
|
'jwt/no-hardcoded-secret': 'warn',
|
|
115
83
|
};
|
|
116
|
-
/**
|
|
117
|
-
* Configuration presets for flat config
|
|
118
|
-
*/
|
|
119
84
|
exports.configs = {
|
|
120
|
-
/**
|
|
121
|
-
* Flagship preset — the rule from this plugin in the ecosystem-wide
|
|
122
|
-
* flagship list (`.agent/flagship-rules.md`). Use this when you want
|
|
123
|
-
* the highest-signal JWT subset shippable in CI gates.
|
|
124
|
-
*/
|
|
125
85
|
flagship: {
|
|
126
86
|
plugins: {
|
|
127
87
|
jwt: exports.plugin,
|
|
@@ -130,36 +90,24 @@ exports.configs = {
|
|
|
130
90
|
'jwt/no-algorithm-none': 'error',
|
|
131
91
|
},
|
|
132
92
|
},
|
|
133
|
-
/**
|
|
134
|
-
* Recommended preset - balanced security
|
|
135
|
-
*/
|
|
136
93
|
recommended: {
|
|
137
94
|
plugins: {
|
|
138
95
|
jwt: exports.plugin,
|
|
139
96
|
},
|
|
140
97
|
rules: recommendedRules,
|
|
141
98
|
},
|
|
142
|
-
/**
|
|
143
|
-
* Strict preset - maximum security (includes 2025 research)
|
|
144
|
-
*/
|
|
145
99
|
strict: {
|
|
146
100
|
plugins: {
|
|
147
101
|
jwt: exports.plugin,
|
|
148
102
|
},
|
|
149
103
|
rules: strictRules,
|
|
150
104
|
},
|
|
151
|
-
/**
|
|
152
|
-
* Legacy preset - migration mode
|
|
153
|
-
*/
|
|
154
105
|
legacy: {
|
|
155
106
|
plugins: {
|
|
156
107
|
jwt: exports.plugin,
|
|
157
108
|
},
|
|
158
109
|
rules: legacyRules,
|
|
159
110
|
},
|
|
160
|
-
/**
|
|
161
|
-
* All rules preset
|
|
162
|
-
*/
|
|
163
111
|
all: {
|
|
164
112
|
plugins: {
|
|
165
113
|
jwt: exports.plugin,
|
|
@@ -167,5 +115,4 @@ exports.configs = {
|
|
|
167
115
|
rules: strictRules,
|
|
168
116
|
},
|
|
169
117
|
};
|
|
170
|
-
// Default export for flat config usage
|
|
171
118
|
exports.default = exports.plugin;
|
package/src/oxlint.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Minimal declaration written by scripts/build-package.ts.
|
|
2
|
+
//
|
|
3
|
+
// tsc's generated entry declaration inlined every inferred rule-option type
|
|
4
|
+
// (up to 166 kB per plugin) for an API no consumer calls directly. This types
|
|
5
|
+
// what a flat config actually touches. Per-rule option types remain available
|
|
6
|
+
// from the "./types" subpath export where a plugin provides one.
|
|
7
|
+
import type { Linter, Rule } from 'eslint';
|
|
8
|
+
|
|
9
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
10
|
+
export declare const configs: Record<string, Linter.Config>;
|
|
11
|
+
export declare const plugin: {
|
|
12
|
+
meta: { name: string; version: string };
|
|
13
|
+
rules: Record<string, Rule.RuleModule>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare const _default: typeof plugin;
|
|
17
|
+
export default _default;
|
package/src/oxlint.js
ADDED
|
@@ -1,14 +1,8 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noAlgorithmConfusion = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
5
|
const utils_1 = require("../../utils");
|
|
11
|
-
// Patterns that indicate a public key
|
|
12
6
|
const PUBLIC_KEY_PATTERNS = [
|
|
13
7
|
/public/i,
|
|
14
8
|
/\.pub$/,
|
|
@@ -101,16 +95,10 @@ exports.noAlgorithmConfusion = (0, eslint_devkit_1.createRule)({
|
|
|
101
95
|
const options = context.options[0] ?? {};
|
|
102
96
|
const symmetricAlgSet = new Set(options.symmetricAlgorithms ?? ['HS256', 'HS384', 'HS512']);
|
|
103
97
|
const sourceCode = context.sourceCode;
|
|
104
|
-
/**
|
|
105
|
-
* Check if a node looks like a public key reference
|
|
106
|
-
*/
|
|
107
98
|
const looksLikePublicKey = (node) => {
|
|
108
99
|
const text = sourceCode.getText(node);
|
|
109
100
|
return PUBLIC_KEY_PATTERNS.some((pattern) => pattern.test(text));
|
|
110
101
|
};
|
|
111
|
-
/**
|
|
112
|
-
* Check if algorithms include symmetric algorithms
|
|
113
|
-
*/
|
|
114
102
|
const hasSymmetricAlgorithm = (algorithms) => {
|
|
115
103
|
for (const alg of algorithms) {
|
|
116
104
|
if (symmetricAlgSet.has(alg)) {
|
|
@@ -121,26 +109,21 @@ exports.noAlgorithmConfusion = (0, eslint_devkit_1.createRule)({
|
|
|
121
109
|
};
|
|
122
110
|
return {
|
|
123
111
|
CallExpression(node) {
|
|
124
|
-
// Only check verify operations
|
|
125
112
|
if (!(0, utils_1.isVerifyOperation)(node)) {
|
|
126
113
|
return;
|
|
127
114
|
}
|
|
128
|
-
// Need at least 2 arguments: token, key
|
|
129
115
|
if (node.arguments.length < 2) {
|
|
130
116
|
return;
|
|
131
117
|
}
|
|
132
118
|
const keyArg = node.arguments[1];
|
|
133
119
|
const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
|
|
134
|
-
// Check if key looks like a public key
|
|
135
120
|
if (!looksLikePublicKey(keyArg)) {
|
|
136
121
|
return;
|
|
137
122
|
}
|
|
138
|
-
// If there are options, check the algorithms
|
|
139
123
|
if (optionsArg) {
|
|
140
124
|
const algorithms = (0, utils_1.extractAlgorithms)(optionsArg);
|
|
141
125
|
const symmetricAlg = hasSymmetricAlgorithm(algorithms);
|
|
142
126
|
if (symmetricAlg) {
|
|
143
|
-
// Find the algorithm node for precise error location
|
|
144
127
|
for (const prop of optionsArg.properties) {
|
|
145
128
|
if (prop.type === 'Property' &&
|
|
146
129
|
prop.key.type === 'Identifier' &&
|
|
@@ -155,9 +138,6 @@ exports.noAlgorithmConfusion = (0, eslint_devkit_1.createRule)({
|
|
|
155
138
|
}
|
|
156
139
|
}
|
|
157
140
|
}
|
|
158
|
-
// If no options specified but key looks like public key,
|
|
159
|
-
// and we're in verify, flag as potential issue
|
|
160
|
-
// (verification without explicit algorithm with public key is risky)
|
|
161
141
|
},
|
|
162
142
|
};
|
|
163
143
|
},
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noAlgorithmNone = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -107,20 +102,12 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
107
102
|
const options = context.options[0] ?? {};
|
|
108
103
|
const { allowInTests = false } = options;
|
|
109
104
|
const filename = context.filename;
|
|
110
|
-
// Skip test files if configured
|
|
111
105
|
if (allowInTests && (0, utils_1.isTestFile)(filename)) {
|
|
112
106
|
return {};
|
|
113
107
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Check if an algorithm value is 'none' (case insensitive)
|
|
116
|
-
*/
|
|
117
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
118
108
|
const isNoneAlgorithm = (value) => {
|
|
119
109
|
return value.toLowerCase() === 'none';
|
|
120
110
|
};
|
|
121
|
-
/**
|
|
122
|
-
* Check options object for 'none' algorithm
|
|
123
|
-
*/
|
|
124
111
|
const checkOptionsForNone = (optionsNode) => {
|
|
125
112
|
for (const prop of optionsNode.properties) {
|
|
126
113
|
if (prop.type !== 'Property' || prop.key.type !== 'Identifier') {
|
|
@@ -132,7 +119,6 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
132
119
|
keyName !== 'alg') {
|
|
133
120
|
continue;
|
|
134
121
|
}
|
|
135
|
-
// Single algorithm: { algorithm: 'none' }
|
|
136
122
|
if (prop.value.type === 'Literal' &&
|
|
137
123
|
typeof prop.value.value === 'string') {
|
|
138
124
|
if (isNoneAlgorithm(prop.value.value)) {
|
|
@@ -142,9 +128,7 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
142
128
|
});
|
|
143
129
|
}
|
|
144
130
|
}
|
|
145
|
-
// Array of algorithms: { algorithms: ['none'] } or { algorithms: [] }
|
|
146
131
|
if (prop.value.type === 'ArrayExpression') {
|
|
147
|
-
// Check for empty array
|
|
148
132
|
if (prop.value.elements.length === 0) {
|
|
149
133
|
context.report({
|
|
150
134
|
node: prop.value,
|
|
@@ -152,7 +136,6 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
152
136
|
});
|
|
153
137
|
continue;
|
|
154
138
|
}
|
|
155
|
-
// Check each element for 'none'
|
|
156
139
|
for (const elem of prop.value.elements) {
|
|
157
140
|
if (elem &&
|
|
158
141
|
elem.type === 'Literal' &&
|
|
@@ -167,20 +150,6 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
167
150
|
}
|
|
168
151
|
}
|
|
169
152
|
};
|
|
170
|
-
/**
|
|
171
|
-
* Detect `jwt.decode()` calls — closes the audit FN where
|
|
172
|
-
* `jwt.decode(token)` followed by trusting the decoded payload was
|
|
173
|
-
* silent, despite being functionally equivalent to algorithm:"none".
|
|
174
|
-
* See benchmarks/AUDIT_PATTERNS.md §3.8 ("verify-then-trust mismatch").
|
|
175
|
-
*
|
|
176
|
-
* The check is conservative: we flag every `jwt.decode()` call. In
|
|
177
|
-
* the rare case where `decode` is used legitimately (e.g. logging
|
|
178
|
-
* the issuer of an inbound token without authentication semantics),
|
|
179
|
-
* the user can disable the rule on that line. The default-on
|
|
180
|
-
* behaviour is correct — most production uses of `jwt.decode` are
|
|
181
|
-
* authentication mistakes.
|
|
182
|
-
*/
|
|
183
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
184
153
|
function isDecodeCall(node) {
|
|
185
154
|
const callee = node.callee;
|
|
186
155
|
if (callee.type !== 'MemberExpression')
|
|
@@ -189,8 +158,6 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
189
158
|
return false;
|
|
190
159
|
if (callee.property.name !== 'decode')
|
|
191
160
|
return false;
|
|
192
|
-
// Object should be `jwt` (or imported as such). Check both
|
|
193
|
-
// `jwt.decode` and `JWT.decode` patterns.
|
|
194
161
|
if (callee.object.type === 'Identifier') {
|
|
195
162
|
const name = callee.object.name.toLowerCase();
|
|
196
163
|
return name === 'jwt' || name === 'jsonwebtoken';
|
|
@@ -199,18 +166,13 @@ exports.noAlgorithmNone = (0, eslint_devkit_1.createRule)({
|
|
|
199
166
|
}
|
|
200
167
|
return {
|
|
201
168
|
CallExpression(node) {
|
|
202
|
-
// Detect `jwt.decode(...)` — equivalent to algorithm:"none" if
|
|
203
|
-
// its result is trusted. We always flag (with critical severity)
|
|
204
|
-
// because the FN risk far outweighs the FP risk on real code.
|
|
205
169
|
if (isDecodeCall(node)) {
|
|
206
170
|
context.report({ node, messageId: 'decodeWithoutVerify' });
|
|
207
171
|
return;
|
|
208
172
|
}
|
|
209
|
-
// Check both verify and sign operations
|
|
210
173
|
if (!(0, utils_1.isVerifyOperation)(node) && !(0, utils_1.isSignOperation)(node)) {
|
|
211
174
|
return;
|
|
212
175
|
}
|
|
213
|
-
// Get options argument (usually 3rd argument)
|
|
214
176
|
const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
|
|
215
177
|
if (optionsArg) {
|
|
216
178
|
checkOptionsForNone(optionsArg);
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noDecodeWithoutVerify = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -87,12 +82,7 @@ exports.noDecodeWithoutVerify = (0, eslint_devkit_1.createRule)({
|
|
|
87
82
|
create(context) {
|
|
88
83
|
const options = context.options[0] ?? {};
|
|
89
84
|
const { trustedAnnotations = [] } = options;
|
|
90
|
-
/**
|
|
91
|
-
* Check if this is a jwt-decode import usage
|
|
92
|
-
*/
|
|
93
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
94
85
|
const isJwtDecodeLibrary = (node) => {
|
|
95
|
-
// Direct call: jwtDecode(token)
|
|
96
86
|
if (node.callee.type === 'Identifier' &&
|
|
97
87
|
(node.callee.name === 'jwtDecode' || node.callee.name === 'jwt_decode')) {
|
|
98
88
|
return true;
|
|
@@ -101,9 +91,7 @@ exports.noDecodeWithoutVerify = (0, eslint_devkit_1.createRule)({
|
|
|
101
91
|
};
|
|
102
92
|
return {
|
|
103
93
|
CallExpression(node) {
|
|
104
|
-
// Check for jwt.decode() pattern
|
|
105
94
|
if ((0, utils_1.isDecodeOperation)(node)) {
|
|
106
|
-
// Check for safe annotations
|
|
107
95
|
if ((0, eslint_devkit_1.hasSafeAnnotation)(node, context, trustedAnnotations)) {
|
|
108
96
|
return;
|
|
109
97
|
}
|
|
@@ -113,9 +101,7 @@ exports.noDecodeWithoutVerify = (0, eslint_devkit_1.createRule)({
|
|
|
113
101
|
});
|
|
114
102
|
return;
|
|
115
103
|
}
|
|
116
|
-
// Check for jwt-decode library usage
|
|
117
104
|
if (isJwtDecodeLibrary(node)) {
|
|
118
|
-
// Check for safe annotations
|
|
119
105
|
if ((0, eslint_devkit_1.hasSafeAnnotation)(node, context, trustedAnnotations)) {
|
|
120
106
|
return;
|
|
121
107
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noHardcodedSecret = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -77,29 +72,16 @@ exports.noHardcodedSecret = (0, eslint_devkit_1.createRule)({
|
|
|
77
72
|
},
|
|
78
73
|
],
|
|
79
74
|
create(context) {
|
|
80
|
-
/**
|
|
81
|
-
* Check if a node is a hardcoded string literal
|
|
82
|
-
*/
|
|
83
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
84
75
|
const isHardcodedString = (node) => {
|
|
85
|
-
// String literal
|
|
86
76
|
if (node.type === 'Literal' && typeof node.value === 'string') {
|
|
87
77
|
return true;
|
|
88
78
|
}
|
|
89
|
-
// Template literal without expressions
|
|
90
79
|
if (node.type === 'TemplateLiteral' &&
|
|
91
80
|
node.expressions.length === 0) {
|
|
92
81
|
return true;
|
|
93
82
|
}
|
|
94
83
|
return false;
|
|
95
84
|
};
|
|
96
|
-
/**
|
|
97
|
-
* Resolve an Identifier node to its initializer (one frame of indirection).
|
|
98
|
-
* Closes the audit FN where `const SECRET = 'x'; jwt.sign(p, SECRET)`
|
|
99
|
-
* was treated as safe — the literal-via-const pattern hides the
|
|
100
|
-
* hardcoded secret behind one declaration. See benchmarks/AUDIT_PATTERNS.md
|
|
101
|
-
* §3.2 ("Indirection through one level of variable").
|
|
102
|
-
*/
|
|
103
85
|
const resolveConstLiteralValue = (node) => {
|
|
104
86
|
if (node.type !== 'Identifier')
|
|
105
87
|
return null;
|
|
@@ -115,45 +97,28 @@ exports.noHardcodedSecret = (0, eslint_devkit_1.createRule)({
|
|
|
115
97
|
return null;
|
|
116
98
|
return def.node.init ?? null;
|
|
117
99
|
};
|
|
118
|
-
/**
|
|
119
|
-
* Check if node is a safe key source
|
|
120
|
-
*/
|
|
121
100
|
const isSafeKeySource = (node) => {
|
|
122
|
-
// Environment variable
|
|
123
101
|
if ((0, utils_1.isEnvVariable)(node)) {
|
|
124
102
|
return true;
|
|
125
103
|
}
|
|
126
|
-
// Function call (getSecret(), loadKey(), etc.)
|
|
127
104
|
if (node.type === 'CallExpression') {
|
|
128
105
|
return true;
|
|
129
106
|
}
|
|
130
|
-
// await expression (async key loading)
|
|
131
107
|
if (node.type === 'AwaitExpression') {
|
|
132
108
|
return true;
|
|
133
109
|
}
|
|
134
|
-
// Variable reference: resolve `const X = ...` one level. If X is a
|
|
135
|
-
// hardcoded literal, treat as unsafe — fall through to the
|
|
136
|
-
// hardcoded check. Otherwise (env var, call, identifier chain)
|
|
137
|
-
// it's safe.
|
|
138
110
|
if (node.type === 'Identifier') {
|
|
139
111
|
const init = resolveConstLiteralValue(node);
|
|
140
112
|
if (init && isHardcodedString(init)) {
|
|
141
|
-
// Unsafe — let the caller's `isHardcodedString` (with the
|
|
142
|
-
// resolved init) flag it.
|
|
143
113
|
return false;
|
|
144
114
|
}
|
|
145
115
|
return true;
|
|
146
116
|
}
|
|
147
|
-
// Member expression but not literal (config.secret, etc.)
|
|
148
117
|
if (node.type === 'MemberExpression' && !isHardcodedString(node)) {
|
|
149
118
|
return true;
|
|
150
119
|
}
|
|
151
120
|
return false;
|
|
152
121
|
};
|
|
153
|
-
/**
|
|
154
|
-
* Like isHardcodedString but resolves single-level `const X = '...'`
|
|
155
|
-
* indirection so the caller fires on `const SECRET = 'x'; jwt.sign(p, SECRET)`.
|
|
156
|
-
*/
|
|
157
122
|
const isHardcodedStringOrResolvedConst = (node) => {
|
|
158
123
|
if (isHardcodedString(node))
|
|
159
124
|
return true;
|
|
@@ -162,20 +127,16 @@ exports.noHardcodedSecret = (0, eslint_devkit_1.createRule)({
|
|
|
162
127
|
};
|
|
163
128
|
return {
|
|
164
129
|
CallExpression(node) {
|
|
165
|
-
// Check both sign and verify operations
|
|
166
130
|
if (!(0, utils_1.isSignOperation)(node) && !(0, utils_1.isVerifyOperation)(node)) {
|
|
167
131
|
return;
|
|
168
132
|
}
|
|
169
|
-
// Secret/key is the second argument
|
|
170
133
|
if (node.arguments.length < 2) {
|
|
171
134
|
return;
|
|
172
135
|
}
|
|
173
136
|
const secretArg = node.arguments[1];
|
|
174
|
-
// Skip if safe source
|
|
175
137
|
if (isSafeKeySource(secretArg)) {
|
|
176
138
|
return;
|
|
177
139
|
}
|
|
178
|
-
// Flag hardcoded strings (also follows single-frame `const X = '...'`).
|
|
179
140
|
if (isHardcodedStringOrResolvedConst(secretArg)) {
|
|
180
141
|
context.report({
|
|
181
142
|
node: secretArg,
|
|
@@ -1,9 +1,4 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noSensitivePayload = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -81,7 +76,6 @@ exports.noSensitivePayload = (0, eslint_devkit_1.createRule)({
|
|
|
81
76
|
return;
|
|
82
77
|
}
|
|
83
78
|
const payloadArg = node.arguments[0];
|
|
84
|
-
// Check object literal payload
|
|
85
79
|
if (payloadArg.type === 'ObjectExpression') {
|
|
86
80
|
for (const prop of payloadArg.properties) {
|
|
87
81
|
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|