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.
Files changed (82) hide show
  1. package/README.md +5 -5
  2. package/package.json +16 -24
  3. package/src/index.d.ts +17 -0
  4. package/{dist/src → src}/index.js +16 -69
  5. package/src/oxlint.d.ts +17 -0
  6. package/src/oxlint.js +3 -0
  7. package/{dist/src → src}/rules/no-algorithm-confusion/index.js +0 -20
  8. package/{dist/src → src}/rules/no-algorithm-none/index.js +0 -38
  9. package/{dist/src → src}/rules/no-decode-without-verify/index.js +0 -14
  10. package/{dist/src → src}/rules/no-hardcoded-secret/index.js +0 -39
  11. package/{dist/src → src}/rules/no-sensitive-payload/index.js +0 -6
  12. package/{dist/src → src}/rules/no-timestamp-manipulation/index.js +0 -6
  13. package/{dist/src → src}/rules/no-weak-secret/index.js +0 -19
  14. package/{dist/src → src}/rules/require-algorithm-whitelist/index.js +0 -9
  15. package/{dist/src → src}/rules/require-audience-validation/index.js +0 -7
  16. package/{dist/src → src}/rules/require-expiration/index.js +0 -16
  17. package/{dist/src → src}/rules/require-issued-at/index.js +0 -21
  18. package/{dist/src → src}/rules/require-issuer-validation/index.js +0 -7
  19. package/{dist/src → src}/rules/require-max-age/index.js +0 -7
  20. package/src/types/index.js +2 -0
  21. package/{dist/src → src}/utils/index.js +1 -74
  22. package/AGENTS.md +0 -102
  23. package/CHANGELOG.md +0 -161
  24. package/dist/AGENTS.md +0 -102
  25. package/dist/CHANGELOG.md +0 -161
  26. package/dist/LICENSE +0 -23
  27. package/dist/README.md +0 -158
  28. package/dist/package.json +0 -125
  29. package/dist/src/index.d.ts +0 -101
  30. package/dist/src/oxlint.d.ts +0 -20
  31. package/dist/src/oxlint.js +0 -21
  32. package/dist/src/rules/no-algorithm-confusion/index.d.ts +0 -24
  33. package/dist/src/rules/no-algorithm-none/index.d.ts +0 -24
  34. package/dist/src/rules/no-decode-without-verify/index.d.ts +0 -23
  35. package/dist/src/rules/no-hardcoded-secret/index.d.ts +0 -23
  36. package/dist/src/rules/no-sensitive-payload/index.d.ts +0 -20
  37. package/dist/src/rules/no-timestamp-manipulation/index.d.ts +0 -23
  38. package/dist/src/rules/no-weak-secret/index.d.ts +0 -23
  39. package/dist/src/rules/require-algorithm-whitelist/index.d.ts +0 -24
  40. package/dist/src/rules/require-audience-validation/index.d.ts +0 -20
  41. package/dist/src/rules/require-expiration/index.d.ts +0 -23
  42. package/dist/src/rules/require-issued-at/index.d.ts +0 -23
  43. package/dist/src/rules/require-issuer-validation/index.d.ts +0 -20
  44. package/dist/src/rules/require-max-age/index.d.ts +0 -24
  45. package/dist/src/types/index.js +0 -7
  46. package/dist/src/utils/index.d.ts +0 -98
  47. package/src/__compatibility__/jwt-interface.spec.ts +0 -192
  48. package/src/index.test.ts +0 -47
  49. package/src/index.ts +0 -209
  50. package/src/oxlint.test.ts +0 -61
  51. package/src/oxlint.ts +0 -23
  52. package/src/rules/no-algorithm-confusion/index.ts +0 -208
  53. package/src/rules/no-algorithm-confusion/no-algorithm-confusion.test.ts +0 -106
  54. package/src/rules/no-algorithm-none/index.ts +0 -270
  55. package/src/rules/no-algorithm-none/no-algorithm-none.test.ts +0 -146
  56. package/src/rules/no-decode-without-verify/index.ts +0 -162
  57. package/src/rules/no-decode-without-verify/no-decode-without-verify.test.ts +0 -76
  58. package/src/rules/no-hardcoded-secret/index.ts +0 -230
  59. package/src/rules/no-hardcoded-secret/no-hardcoded-secret.test.ts +0 -97
  60. package/src/rules/no-sensitive-payload/index.ts +0 -123
  61. package/src/rules/no-sensitive-payload/no-sensitive-payload.test.ts +0 -157
  62. package/src/rules/no-timestamp-manipulation/index.ts +0 -118
  63. package/src/rules/no-timestamp-manipulation/no-timestamp-manipulation.test.ts +0 -54
  64. package/src/rules/no-weak-secret/index.ts +0 -204
  65. package/src/rules/no-weak-secret/no-weak-secret.test.ts +0 -114
  66. package/src/rules/require-algorithm-whitelist/index.ts +0 -145
  67. package/src/rules/require-algorithm-whitelist/require-algorithm-whitelist.test.ts +0 -72
  68. package/src/rules/require-audience-validation/index.ts +0 -120
  69. package/src/rules/require-audience-validation/require-audience-validation.test.ts +0 -68
  70. package/src/rules/require-expiration/index.ts +0 -170
  71. package/src/rules/require-expiration/require-expiration.test.ts +0 -135
  72. package/src/rules/require-issued-at/index.ts +0 -143
  73. package/src/rules/require-issued-at/require-issued-at.test.ts +0 -64
  74. package/src/rules/require-issuer-validation/index.ts +0 -120
  75. package/src/rules/require-issuer-validation/require-issuer-validation.test.ts +0 -68
  76. package/src/rules/require-max-age/index.ts +0 -117
  77. package/src/rules/require-max-age/require-max-age.test.ts +0 -69
  78. package/src/tests/coverage-dual-layer.test.ts +0 -323
  79. package/src/types/index.ts +0 -107
  80. package/src/utils/index.ts +0 -285
  81. package/src/utils/utils.test.ts +0 -324
  82. /package/{dist/src → src}/types/index.d.ts +0 -0
@@ -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.noTimestampManipulation = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -80,7 +75,6 @@ exports.noTimestampManipulation = (0, eslint_devkit_1.createRule)({
80
75
  if (!optionsArg) {
81
76
  return;
82
77
  }
83
- // Check for noTimestamp: true
84
78
  if ((0, utils_1.hasOption)(optionsArg, 'noTimestamp')) {
85
79
  const value = (0, utils_1.getOptionValue)(optionsArg, 'noTimestamp');
86
80
  if (value?.type === 'Literal' && value.value === true) {
@@ -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.noWeakSecret = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
5
  const utils_1 = require("../../utils");
11
- // Common weak secret patterns
12
6
  const WEAK_SECRET_PATTERNS = [
13
7
  /^secret$/i,
14
8
  /^password$/i,
@@ -99,25 +93,16 @@ exports.noWeakSecret = (0, eslint_devkit_1.createRule)({
99
93
  create(context) {
100
94
  const options = context.options[0] ?? {};
101
95
  const { minSecretLength = 32 } = options;
102
- /**
103
- * Check if a value matches known weak patterns
104
- */
105
96
  const isKnownWeakPattern = (value) => {
106
97
  return WEAK_SECRET_PATTERNS.some((pattern) => pattern.test(value));
107
98
  };
108
- /**
109
- * Check the secret argument for weakness
110
- */
111
99
  const checkSecret = (secretNode) => {
112
- // Environment variables are considered safe (configuration)
113
100
  if ((0, utils_1.isEnvVariable)(secretNode)) {
114
101
  return;
115
102
  }
116
- // Check string literals
117
103
  if (secretNode.type === 'Literal' &&
118
104
  typeof secretNode.value === 'string') {
119
105
  const secretValue = secretNode.value;
120
- // Check for known weak patterns
121
106
  if (isKnownWeakPattern(secretValue)) {
122
107
  context.report({
123
108
  node: secretNode,
@@ -125,7 +110,6 @@ exports.noWeakSecret = (0, eslint_devkit_1.createRule)({
125
110
  });
126
111
  return;
127
112
  }
128
- // Check for short secrets
129
113
  if (secretValue.length < minSecretLength) {
130
114
  context.report({
131
115
  node: secretNode,
@@ -137,7 +121,6 @@ exports.noWeakSecret = (0, eslint_devkit_1.createRule)({
137
121
  });
138
122
  }
139
123
  }
140
- // Template literals with obvious weak values
141
124
  if (secretNode.type === 'TemplateLiteral' && secretNode.quasis.length === 1) {
142
125
  const rawValue = secretNode.quasis[0].value.raw;
143
126
  if (isKnownWeakPattern(rawValue) || rawValue.length < minSecretLength) {
@@ -150,11 +133,9 @@ exports.noWeakSecret = (0, eslint_devkit_1.createRule)({
150
133
  };
151
134
  return {
152
135
  CallExpression(node) {
153
- // Check both sign and verify operations
154
136
  if (!(0, utils_1.isSignOperation)(node) && !(0, utils_1.isVerifyOperation)(node)) {
155
137
  return;
156
138
  }
157
- // Secret is usually the second argument
158
139
  if (node.arguments.length >= 2) {
159
140
  checkSecret(node.arguments[1]);
160
141
  }
@@ -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.requireAlgorithmWhitelist = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -79,16 +74,13 @@ exports.requireAlgorithmWhitelist = (0, eslint_devkit_1.createRule)({
79
74
  create(context) {
80
75
  return {
81
76
  CallExpression(node) {
82
- // Only check verify operations
83
77
  if (!(0, utils_1.isVerifyOperation)(node)) {
84
78
  return;
85
79
  }
86
- // Need at least token and key arguments
87
80
  if (node.arguments.length < 2) {
88
81
  return;
89
82
  }
90
83
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
91
- // No options at all - definitely missing algorithms
92
84
  if (!optionsArg) {
93
85
  context.report({
94
86
  node,
@@ -96,7 +88,6 @@ exports.requireAlgorithmWhitelist = (0, eslint_devkit_1.createRule)({
96
88
  });
97
89
  return;
98
90
  }
99
- // Options exist but no algorithms property
100
91
  const hasAlgorithms = (0, utils_1.hasOption)(optionsArg, 'algorithms') ||
101
92
  (0, utils_1.hasOption)(optionsArg, 'algorithm') ||
102
93
  (0, utils_1.hasOption)(optionsArg, 'alg');
@@ -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.requireAudienceValidation = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -78,7 +73,6 @@ exports.requireAudienceValidation = (0, eslint_devkit_1.createRule)({
78
73
  return;
79
74
  }
80
75
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
81
- // No options at all
82
76
  if (!optionsArg) {
83
77
  context.report({
84
78
  node,
@@ -86,7 +80,6 @@ exports.requireAudienceValidation = (0, eslint_devkit_1.createRule)({
86
80
  });
87
81
  return;
88
82
  }
89
- // Check for audience option
90
83
  if (!(0, utils_1.hasOption)(optionsArg, 'audience') && !(0, utils_1.hasOption)(optionsArg, 'aud')) {
91
84
  context.report({
92
85
  node: 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.requireExpiration = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -75,10 +70,6 @@ exports.requireExpiration = (0, eslint_devkit_1.createRule)({
75
70
  },
76
71
  ],
77
72
  create(context) {
78
- /**
79
- * Check if payload contains exp claim
80
- */
81
- // oxlint-disable-next-line consistent-function-scoping
82
73
  const payloadHasExp = (payloadNode) => {
83
74
  if (payloadNode.type !== 'ObjectExpression') {
84
75
  return false;
@@ -89,25 +80,20 @@ exports.requireExpiration = (0, eslint_devkit_1.createRule)({
89
80
  };
90
81
  return {
91
82
  CallExpression(node) {
92
- // Only check sign operations
93
83
  if (!(0, utils_1.isSignOperation)(node)) {
94
84
  return;
95
85
  }
96
- // Need at least payload argument
97
86
  if (node.arguments.length < 1) {
98
87
  return;
99
88
  }
100
89
  const payloadArg = node.arguments[0];
101
90
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
102
- // Check if payload has exp claim
103
91
  if (payloadHasExp(payloadArg)) {
104
92
  return;
105
93
  }
106
- // Check if options has expiresIn
107
94
  if (optionsArg && (0, utils_1.hasOption)(optionsArg, 'expiresIn')) {
108
95
  return;
109
96
  }
110
- // Report missing expiration — suggest adding expiresIn to the options object
111
97
  const optionsArg3 = node.arguments[2];
112
98
  context.report({
113
99
  node,
@@ -118,11 +104,9 @@ exports.requireExpiration = (0, eslint_devkit_1.createRule)({
118
104
  fix(fixer) {
119
105
  const sourceCode = context.sourceCode;
120
106
  if (optionsArg3 && optionsArg3.type === eslint_devkit_1.AST_NODE_TYPES.ObjectExpression) {
121
- // Options object exists but lacks expiresIn — insert property
122
107
  const openBrace = sourceCode.getFirstToken(optionsArg3);
123
108
  return fixer.insertTextAfter(openBrace, ' expiresIn: \'1h\',');
124
109
  }
125
- // No options arg — insert { expiresIn: '1h' } as third argument
126
110
  const lastArg = node.arguments[node.arguments.length - 1];
127
111
  return fixer.insertTextAfter(lastArg, ', { expiresIn: \'1h\' }');
128
112
  },
@@ -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.requireIssuedAt = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -62,10 +57,6 @@ exports.requireIssuedAt = (0, eslint_devkit_1.createRule)({
62
57
  },
63
58
  ],
64
59
  create(context) {
65
- /**
66
- * Check if payload contains iat claim
67
- */
68
- // oxlint-disable-next-line consistent-function-scoping
69
60
  const payloadHasIat = (payloadNode) => {
70
61
  if (payloadNode.type !== 'ObjectExpression') {
71
62
  return false;
@@ -76,36 +67,24 @@ exports.requireIssuedAt = (0, eslint_devkit_1.createRule)({
76
67
  };
77
68
  return {
78
69
  CallExpression(node) {
79
- // Only check sign operations
80
70
  if (!(0, utils_1.isSignOperation)(node)) {
81
71
  return;
82
72
  }
83
- // Need at least payload argument
84
73
  if (node.arguments.length < 1) {
85
74
  return;
86
75
  }
87
76
  const payloadArg = node.arguments[0];
88
77
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
89
- // Check if payload has iat claim
90
78
  if (payloadHasIat(payloadArg)) {
91
79
  return;
92
80
  }
93
- // Check if options has noTimestamp: false (meaning iat IS added)
94
- // jsonwebtoken adds iat by default unless noTimestamp: true
95
81
  if (optionsArg && (0, utils_1.hasOption)(optionsArg, 'noTimestamp')) {
96
- // If noTimestamp is explicitly set, we flag it (they're disabling iat)
97
82
  context.report({
98
83
  node: optionsArg,
99
84
  messageId: 'missingIssuedAt',
100
85
  });
101
86
  return;
102
87
  }
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
88
  },
110
89
  };
111
90
  },
@@ -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.requireIssuerValidation = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -78,7 +73,6 @@ exports.requireIssuerValidation = (0, eslint_devkit_1.createRule)({
78
73
  return;
79
74
  }
80
75
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
81
- // No options at all
82
76
  if (!optionsArg) {
83
77
  context.report({
84
78
  node,
@@ -86,7 +80,6 @@ exports.requireIssuerValidation = (0, eslint_devkit_1.createRule)({
86
80
  });
87
81
  return;
88
82
  }
89
- // Check for issuer option
90
83
  if (!(0, utils_1.hasOption)(optionsArg, 'issuer') && !(0, utils_1.hasOption)(optionsArg, 'iss')) {
91
84
  context.report({
92
85
  node: 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.requireMaxAge = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -71,7 +66,6 @@ exports.requireMaxAge = (0, eslint_devkit_1.createRule)({
71
66
  return;
72
67
  }
73
68
  const optionsArg = (0, utils_1.getOptionsArgument)(node, 2);
74
- // No options at all
75
69
  if (!optionsArg) {
76
70
  context.report({
77
71
  node,
@@ -79,7 +73,6 @@ exports.requireMaxAge = (0, eslint_devkit_1.createRule)({
79
73
  });
80
74
  return;
81
75
  }
82
- // Check for maxAge option
83
76
  if (!(0, utils_1.hasOption)(optionsArg, 'maxAge') && !(0, utils_1.hasOption)(optionsArg, 'clockTolerance')) {
84
77
  context.report({
85
78
  node: optionsArg,
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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.JWT_METHODS = exports.SENSITIVE_PAYLOAD_FIELDS = exports.SECURE_ALGORITHMS = exports.SYMMETRIC_ALGORITHMS = exports.INSECURE_ALGORITHMS = exports.JWT_LIBRARIES = void 0;
9
4
  exports.isJwtLibraryCall = isJwtLibraryCall;
@@ -18,9 +13,6 @@ exports.isVerifyOperation = isVerifyOperation;
18
13
  exports.isDecodeOperation = isDecodeOperation;
19
14
  exports.getOptionsArgument = getOptionsArgument;
20
15
  exports.isTestFile = isTestFile;
21
- /**
22
- * Supported JWT libraries
23
- */
24
16
  exports.JWT_LIBRARIES = {
25
17
  JSONWEBTOKEN: 'jsonwebtoken',
26
18
  JOSE: 'jose',
@@ -29,24 +21,15 @@ exports.JWT_LIBRARIES = {
29
21
  JWKS_RSA: 'jwks-rsa',
30
22
  JWT_DECODE: 'jwt-decode',
31
23
  };
32
- /**
33
- * Insecure algorithms that should be flagged
34
- */
35
24
  exports.INSECURE_ALGORITHMS = new Set([
36
25
  'none',
37
26
  'None',
38
27
  'NONE',
39
- 'HS256', // Only insecure when used with public keys
28
+ 'HS256',
40
29
  'HS384',
41
30
  'HS512',
42
31
  ]);
43
- /**
44
- * Algorithms vulnerable to confusion attacks when used with asymmetric keys
45
- */
46
32
  exports.SYMMETRIC_ALGORITHMS = new Set(['HS256', 'HS384', 'HS512']);
47
- /**
48
- * Recommended secure algorithms
49
- */
50
33
  exports.SECURE_ALGORITHMS = new Set([
51
34
  'RS256',
52
35
  'RS384',
@@ -59,30 +42,20 @@ exports.SECURE_ALGORITHMS = new Set([
59
42
  'PS512',
60
43
  'EdDSA',
61
44
  ]);
62
- /**
63
- * Sensitive field names that should not be in JWT payload
64
- * All lowercase for case-insensitive matching
65
- */
66
45
  exports.SENSITIVE_PAYLOAD_FIELDS = new Set([
67
- // Passwords
68
46
  'password', 'passwd', 'pwd', 'pass', 'secret',
69
- // API Keys
70
47
  'apikey', 'api_key', 'api-key',
71
48
  'apisecret', 'api_secret', 'api-secret',
72
- // Tokens
73
49
  'accesstoken', 'access_token', 'access-token',
74
50
  'refreshtoken', 'refresh_token', 'refresh-token',
75
51
  'bearertoken', 'bearer_token', 'bearer-token',
76
- // Keys
77
52
  'privatekey', 'private_key', 'private-key',
78
53
  'secretkey', 'secret_key', 'secret-key',
79
- // PII - Personal Identifiable Information
80
54
  'email', 'emailaddress', 'email_address', 'email-address',
81
55
  'phone', 'phonenumber', 'phone_number', 'phone-number',
82
56
  'ssn', 'socialsecuritynumber', 'social_security_number',
83
57
  'dob', 'dateofbirth', 'date_of_birth', 'birthdate',
84
58
  'address', 'streetaddress', 'street_address',
85
- // Financial
86
59
  'creditcard', 'credit_card', 'credit-card',
87
60
  'cardnumber', 'card_number', 'card-number',
88
61
  'cvv', 'cvc', 'securitycode', 'security_code',
@@ -91,43 +64,29 @@ exports.SENSITIVE_PAYLOAD_FIELDS = new Set([
91
64
  'accountnumber', 'account_number', 'account-number',
92
65
  'routingnumber', 'routing_number', 'routing-number',
93
66
  ]);
94
- /**
95
- * JWT method patterns for different operations
96
- */
97
67
  exports.JWT_METHODS = {
98
68
  SIGN: new Set(['sign', 'signJWT', 'SignJWT']),
99
69
  VERIFY: new Set(['verify', 'verifyJWT', 'jwtVerify']),
100
70
  DECODE: new Set(['decode', 'jwtDecode', 'decodeJWT']),
101
71
  };
102
- /**
103
- * Check if a node represents a call to a JWT library method
104
- */
105
72
  function isJwtLibraryCall(node, targetMethods) {
106
- // Check member expression: jwt.verify(), jose.jwtVerify()
107
73
  if (node.callee.type === 'MemberExpression') {
108
74
  const property = node.callee.property;
109
75
  if (property.type === 'Identifier') {
110
76
  return targetMethods.has(property.name);
111
77
  }
112
78
  }
113
- // Check direct calls: jwtVerify(), jwtDecode()
114
79
  if (node.callee.type === 'Identifier') {
115
80
  return targetMethods.has(node.callee.name);
116
81
  }
117
82
  return false;
118
83
  }
119
- /**
120
- * Check if a node is a string literal with a specific value
121
- */
122
84
  function isStringLiteral(node, value) {
123
85
  if (node.type !== 'Literal' || typeof node.value !== 'string') {
124
86
  return false;
125
87
  }
126
88
  return value === undefined || node.value === value;
127
89
  }
128
- /**
129
- * Extract algorithm from options object
130
- */
131
90
  function extractAlgorithms(optionsNode) {
132
91
  const algorithms = [];
133
92
  for (const prop of optionsNode.properties) {
@@ -138,11 +97,9 @@ function extractAlgorithms(optionsNode) {
138
97
  if (keyName !== 'algorithms' && keyName !== 'algorithm' && keyName !== 'alg') {
139
98
  continue;
140
99
  }
141
- // Single algorithm: { algorithm: 'HS256' }
142
100
  if (prop.value.type === 'Literal' && typeof prop.value.value === 'string') {
143
101
  algorithms.push(prop.value.value);
144
102
  }
145
- // Array of algorithms: { algorithms: ['RS256', 'ES256'] }
146
103
  if (prop.value.type === 'ArrayExpression') {
147
104
  for (const elem of prop.value.elements) {
148
105
  if (elem && elem.type === 'Literal' && typeof elem.value === 'string') {
@@ -153,17 +110,11 @@ function extractAlgorithms(optionsNode) {
153
110
  }
154
111
  return algorithms;
155
112
  }
156
- /**
157
- * Check if options object has a specific property set
158
- */
159
113
  function hasOption(optionsNode, optionName) {
160
114
  return optionsNode.properties.some((prop) => prop.type === 'Property' &&
161
115
  prop.key.type === 'Identifier' &&
162
116
  prop.key.name === optionName);
163
117
  }
164
- /**
165
- * Get the value of a specific option from options object
166
- */
167
118
  function getOptionValue(optionsNode, optionName) {
168
119
  for (const prop of optionsNode.properties) {
169
120
  if (prop.type === 'Property' &&
@@ -174,20 +125,13 @@ function getOptionValue(optionsNode, optionName) {
174
125
  }
175
126
  return undefined;
176
127
  }
177
- /**
178
- * Check if a literal appears to be a weak secret (short string)
179
- */
180
128
  function isWeakSecret(node, minLength = 32) {
181
129
  if (node.type === 'Literal' && typeof node.value === 'string') {
182
130
  return node.value.length < minLength;
183
131
  }
184
132
  return false;
185
133
  }
186
- /**
187
- * Check if a node is an environment variable access (safe pattern)
188
- */
189
134
  function isEnvVariable(node) {
190
- // process.env.JWT_SECRET
191
135
  if (node.type === 'MemberExpression' &&
192
136
  node.object.type === 'MemberExpression' &&
193
137
  node.object.object.type === 'Identifier' &&
@@ -198,29 +142,15 @@ function isEnvVariable(node) {
198
142
  }
199
143
  return false;
200
144
  }
201
- /**
202
- * Check if this call looks like a JWT sign operation
203
- */
204
145
  function isSignOperation(node) {
205
146
  return isJwtLibraryCall(node, exports.JWT_METHODS.SIGN);
206
147
  }
207
- /**
208
- * Check if this call looks like a JWT verify operation
209
- */
210
148
  function isVerifyOperation(node) {
211
149
  return isJwtLibraryCall(node, exports.JWT_METHODS.VERIFY);
212
150
  }
213
- /**
214
- * Check if this call looks like a JWT decode operation (no verification)
215
- */
216
151
  function isDecodeOperation(node) {
217
152
  return isJwtLibraryCall(node, exports.JWT_METHODS.DECODE);
218
153
  }
219
- /**
220
- * Get the options argument from a JWT call
221
- * For jwt.verify(token, secret, options) -> returns options
222
- * For jwt.sign(payload, secret, options) -> returns options
223
- */
224
154
  function getOptionsArgument(node, optionsIndex = 2) {
225
155
  const arg = node.arguments[optionsIndex];
226
156
  if (arg && arg.type === 'ObjectExpression') {
@@ -228,9 +158,6 @@ function getOptionsArgument(node, optionsIndex = 2) {
228
158
  }
229
159
  return undefined;
230
160
  }
231
- /**
232
- * Check if file is a test file
233
- */
234
161
  function isTestFile(filename) {
235
162
  return /\.(test|spec)\.[jt]sx?$/.test(filename) ||
236
163
  /__(tests?|mocks?)__/.test(filename) ||
package/AGENTS.md DELETED
@@ -1,102 +0,0 @@
1
- # AGENTS.md
2
-
3
- > Context for AI coding agents working on eslint-plugin-jwt-security
4
-
5
- ## Setup Commands
6
-
7
- ```bash
8
- # Install dependencies (from monorepo root)
9
- npm install
10
-
11
- # Build this package
12
- nx build eslint-plugin-jwt-security
13
-
14
- # Run tests
15
- nx test eslint-plugin-jwt-security
16
-
17
- # Run tests with coverage
18
- nx test eslint-plugin-jwt-security --coverage
19
-
20
- # Lint this package
21
- nx lint eslint-plugin-jwt-security
22
- ```
23
-
24
- ## Code Style
25
-
26
- - TypeScript strict mode with `@interlace/eslint-devkit` types
27
- - Use `AST_NODE_TYPES` constants, never string literals for node types
28
- - Use `formatLLMMessage()` for all rule error messages
29
- - Include CWE, CVSS, OWASP in every security message
30
- - Use `c8 ignore` comments with documented reasons for untestable code
31
- - Single-pass AST traversal patterns (O(n) complexity)
32
-
33
- ## Testing Instructions
34
-
35
- - Tests use `@typescript-eslint/rule-tester` with Vitest
36
- - Each rule has `index.ts` (implementation) and `*.test.ts` (tests) in same directory
37
- - Run specific rule test: `nx test eslint-plugin-jwt-security --testPathPattern="no-algorithm-none"`
38
- - Coverage target: ≥90% lines, ≥95% functions
39
- - All tests must pass before committing
40
-
41
- ## Project Structure
42
-
43
- ```
44
- src/
45
- ├── index.ts # Plugin entry, exports rules and configs
46
- └── rules/ # 13 rule directories
47
- └── [rule-name]/
48
- ├── index.ts # Rule implementation
49
- └── *.test.ts # Rule tests
50
- docs/rules/ # Markdown documentation per rule
51
- ```
52
-
53
- ## Plugin Purpose
54
-
55
- Comprehensive JWT security ESLint plugin with **13 rules** covering algorithm attacks, replay prevention, and claim validation. Targets 6 major Node.js JWT libraries.
56
-
57
- ## Rule Categories
58
-
59
- | Category | Rules | CWEs |
60
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
61
- | Algorithm Attacks | `no-algorithm-none`, `no-algorithm-confusion` | 347 |
62
- | Verification & Secrets | `require-algorithm-whitelist`, `no-decode-without-verify`, `no-weak-secret`, `no-hardcoded-secret`, `no-timestamp-manipulation` | 326, 345, 757, 798 |
63
- | Claims | `require-expiration`, `require-issued-at`, `require-issuer-validation`, `require-audience-validation`, `require-max-age`, `no-sensitive-payload` | 287, 294, 359, 613 |
64
-
65
- ## Library Detection
66
-
67
- The plugin detects these method calls:
68
-
69
- - **Sign**: `jwt.sign()`, `signJWT()`, `SignJWT()`
70
- - **Verify**: `jwt.verify()`, `jwtVerify()`, `verifyJWT()`
71
- - **Decode**: `jwt.decode()`, `jwtDecode()`, `decodeJWT()`
72
-
73
- ## Common Fix Patterns
74
-
75
- ```typescript
76
- // Algorithm None Attack (CVE-2022-23540)
77
- // BAD: jwt.verify(token, secret, { algorithms: ['none'] })
78
- // GOOD: jwt.verify(token, secret, { algorithms: ['RS256'] })
79
-
80
- // Algorithm Confusion
81
- // BAD: jwt.verify(token, publicKey, { algorithms: ['HS256'] })
82
- // GOOD: jwt.verify(token, publicKey, { algorithms: ['RS256'] })
83
-
84
- // Hardcoded Secret
85
- // BAD: jwt.sign(payload, 'my-secret-key')
86
- // GOOD: jwt.sign(payload, process.env.JWT_SECRET)
87
-
88
- // Missing Expiration
89
- // BAD: jwt.sign({ sub: 'user' }, secret)
90
- // GOOD: jwt.sign({ sub: 'user' }, secret, { expiresIn: '1h' })
91
-
92
- // Replay Attack Prevention
93
- // BAD: jwt.sign(payload, secret, { noTimestamp: true })
94
- // GOOD: jwt.sign(payload, secret)
95
- // GOOD: jwt.verify(token, secret, { maxAge: '1h' })
96
- ```
97
-
98
- ## Security Considerations
99
-
100
- - CWE coverage: 287, 294, 326, 345, 347, 359, 613, 757, 798
101
- - Detects CVE-2022-23540 (Algorithm None attack)
102
- - Covers 6 major JWT libraries: jsonwebtoken, jose, jwt-decode, node-jose, jwt-simple, jsonwebtoken-esm