eslint-plugin-jwt-security 3.5.3 → 3.6.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/README.md CHANGED
@@ -48,10 +48,34 @@ That trade costs recall, and we measure it:
48
48
 
49
49
  - To check out the [guide](https://eslint.interlace.tools/docs/security/plugin-jwt-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security). 📚
50
50
 
51
+ <!-- AUTO-GENERATED:INSTALL:START - Do not edit manually -->
52
+
53
+ **oxlint / Vite+** — the same rules under the same `jwt-security/<rule>` IDs, without ESLint:
54
+
55
+ ```bash
56
+ npm install oxlint-plugin-jwt-security --save-dev
57
+ ```
58
+
59
+ ```jsonc
60
+ // .oxlintrc.json
61
+ { "jsPlugins": ["oxlint-plugin-jwt-security"] }
62
+ ```
63
+
64
+ ```ts
65
+ // vite.config.ts
66
+ import { defineConfig } from 'vite-plus';
67
+
68
+ export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-jwt-security'] } });
69
+ ```
70
+
71
+ **ESLint:**
72
+
51
73
  ```bash
52
74
  npm install eslint-plugin-jwt-security --save-dev
53
75
  ```
54
76
 
77
+ <!-- AUTO-GENERATED:INSTALL:END -->
78
+
55
79
  ## ⚙️ Configuration Presets
56
80
  | Preset | Description |
57
81
  | :------------ | :-------------------------------------------------------- |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jwt-security",
3
- "version": "3.5.3",
3
+ "version": "3.6.0",
4
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
6
  "main": "./src/index.js",
@@ -79,7 +79,7 @@
79
79
  "node": ">=18.0.0"
80
80
  },
81
81
  "dependencies": {
82
- "@interlace/eslint-devkit": "^1.20.2"
82
+ "@interlace/eslint-devkit": "^1.21.0"
83
83
  },
84
84
  "peerDependencies": {
85
85
  "eslint": "^8.40.0 || ^9.0.0 || ^10.0.0",
@@ -91,6 +91,9 @@
91
91
  "jwt-decode": "^3.0.0 || ^4.0.0"
92
92
  },
93
93
  "peerDependenciesMeta": {
94
+ "eslint": {
95
+ "optional": true
96
+ },
94
97
  "jsonwebtoken": {
95
98
  "optional": true
96
99
  },
package/src/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});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;const eslint_devkit_1=require("@interlace/eslint-devkit");const no_algorithm_none_1=require("./rules/no-algorithm-none");Object.defineProperty(exports,"noAlgorithmNone",{enumerable:true,get:function(){return no_algorithm_none_1.noAlgorithmNone}});const no_algorithm_confusion_1=require("./rules/no-algorithm-confusion");Object.defineProperty(exports,"noAlgorithmConfusion",{enumerable:true,get:function(){return no_algorithm_confusion_1.noAlgorithmConfusion}});const require_algorithm_whitelist_1=require("./rules/require-algorithm-whitelist");Object.defineProperty(exports,"requireAlgorithmWhitelist",{enumerable:true,get:function(){return require_algorithm_whitelist_1.requireAlgorithmWhitelist}});const no_decode_without_verify_1=require("./rules/no-decode-without-verify");Object.defineProperty(exports,"noDecodeWithoutVerify",{enumerable:true,get:function(){return no_decode_without_verify_1.noDecodeWithoutVerify}});const no_weak_secret_1=require("./rules/no-weak-secret");Object.defineProperty(exports,"noWeakSecret",{enumerable:true,get:function(){return no_weak_secret_1.noWeakSecret}});const require_expiration_1=require("./rules/require-expiration");Object.defineProperty(exports,"requireExpiration",{enumerable:true,get:function(){return require_expiration_1.requireExpiration}});const no_hardcoded_secret_1=require("./rules/no-hardcoded-secret");Object.defineProperty(exports,"noHardcodedSecret",{enumerable:true,get:function(){return no_hardcoded_secret_1.noHardcodedSecret}});const require_issued_at_1=require("./rules/require-issued-at");Object.defineProperty(exports,"requireIssuedAt",{enumerable:true,get:function(){return require_issued_at_1.requireIssuedAt}});const require_issuer_validation_1=require("./rules/require-issuer-validation");Object.defineProperty(exports,"requireIssuerValidation",{enumerable:true,get:function(){return require_issuer_validation_1.requireIssuerValidation}});const require_audience_validation_1=require("./rules/require-audience-validation");Object.defineProperty(exports,"requireAudienceValidation",{enumerable:true,get:function(){return require_audience_validation_1.requireAudienceValidation}});const no_timestamp_manipulation_1=require("./rules/no-timestamp-manipulation");Object.defineProperty(exports,"noTimestampManipulation",{enumerable:true,get:function(){return no_timestamp_manipulation_1.noTimestampManipulation}});const require_max_age_1=require("./rules/require-max-age");Object.defineProperty(exports,"requireMaxAge",{enumerable:true,get:function(){return require_max_age_1.requireMaxAge}});const no_sensitive_payload_1=require("./rules/no-sensitive-payload");Object.defineProperty(exports,"noSensitivePayload",{enumerable:true,get:function(){return no_sensitive_payload_1.noSensitivePayload}});exports.rules={get"no-algorithm-none"(){return require("./rules/no-algorithm-none").noAlgorithmNone},get"no-algorithm-confusion"(){return require("./rules/no-algorithm-confusion").noAlgorithmConfusion},get"require-algorithm-whitelist"(){return require("./rules/require-algorithm-whitelist").requireAlgorithmWhitelist},get"no-decode-without-verify"(){return require("./rules/no-decode-without-verify").noDecodeWithoutVerify},get"no-weak-secret"(){return require("./rules/no-weak-secret").noWeakSecret},get"require-expiration"(){return require("./rules/require-expiration").requireExpiration},get"no-hardcoded-secret"(){return require("./rules/no-hardcoded-secret").noHardcodedSecret},get"require-issued-at"(){return require("./rules/require-issued-at").requireIssuedAt},get"require-issuer-validation"(){return require("./rules/require-issuer-validation").requireIssuerValidation},get"require-audience-validation"(){return require("./rules/require-audience-validation").requireAudienceValidation},get"no-timestamp-manipulation"(){return require("./rules/no-timestamp-manipulation").noTimestampManipulation},get"require-max-age"(){return require("./rules/require-max-age").requireMaxAge},get"no-sensitive-payload"(){return require("./rules/no-sensitive-payload").noSensitivePayload}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-jwt-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-jwt-security",version:"3.5.3"},rules:exports.rules};const recommendedRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-expiration":"error","jwt-security/no-sensitive-payload":"warn","jwt-security/require-issued-at":"warn"};const strictRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/require-expiration":"error","jwt-security/require-issued-at":"error","jwt-security/require-issuer-validation":"error","jwt-security/require-audience-validation":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-max-age":"error","jwt-security/no-sensitive-payload":"error"};const legacyRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/no-hardcoded-secret":"warn"};exports.configs={flagship:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:{"jwt-security/no-algorithm-none":"error"}},recommended:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:recommendedRules},strict:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules},legacy:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:legacyRules},all:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules}};exports.default=exports.plugin;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});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;const eslint_devkit_1=require("@interlace/eslint-devkit");const no_algorithm_none_1=require("./rules/no-algorithm-none");Object.defineProperty(exports,"noAlgorithmNone",{enumerable:true,get:function(){return no_algorithm_none_1.noAlgorithmNone}});const no_algorithm_confusion_1=require("./rules/no-algorithm-confusion");Object.defineProperty(exports,"noAlgorithmConfusion",{enumerable:true,get:function(){return no_algorithm_confusion_1.noAlgorithmConfusion}});const require_algorithm_whitelist_1=require("./rules/require-algorithm-whitelist");Object.defineProperty(exports,"requireAlgorithmWhitelist",{enumerable:true,get:function(){return require_algorithm_whitelist_1.requireAlgorithmWhitelist}});const no_decode_without_verify_1=require("./rules/no-decode-without-verify");Object.defineProperty(exports,"noDecodeWithoutVerify",{enumerable:true,get:function(){return no_decode_without_verify_1.noDecodeWithoutVerify}});const no_weak_secret_1=require("./rules/no-weak-secret");Object.defineProperty(exports,"noWeakSecret",{enumerable:true,get:function(){return no_weak_secret_1.noWeakSecret}});const require_expiration_1=require("./rules/require-expiration");Object.defineProperty(exports,"requireExpiration",{enumerable:true,get:function(){return require_expiration_1.requireExpiration}});const no_hardcoded_secret_1=require("./rules/no-hardcoded-secret");Object.defineProperty(exports,"noHardcodedSecret",{enumerable:true,get:function(){return no_hardcoded_secret_1.noHardcodedSecret}});const require_issued_at_1=require("./rules/require-issued-at");Object.defineProperty(exports,"requireIssuedAt",{enumerable:true,get:function(){return require_issued_at_1.requireIssuedAt}});const require_issuer_validation_1=require("./rules/require-issuer-validation");Object.defineProperty(exports,"requireIssuerValidation",{enumerable:true,get:function(){return require_issuer_validation_1.requireIssuerValidation}});const require_audience_validation_1=require("./rules/require-audience-validation");Object.defineProperty(exports,"requireAudienceValidation",{enumerable:true,get:function(){return require_audience_validation_1.requireAudienceValidation}});const no_timestamp_manipulation_1=require("./rules/no-timestamp-manipulation");Object.defineProperty(exports,"noTimestampManipulation",{enumerable:true,get:function(){return no_timestamp_manipulation_1.noTimestampManipulation}});const require_max_age_1=require("./rules/require-max-age");Object.defineProperty(exports,"requireMaxAge",{enumerable:true,get:function(){return require_max_age_1.requireMaxAge}});const no_sensitive_payload_1=require("./rules/no-sensitive-payload");Object.defineProperty(exports,"noSensitivePayload",{enumerable:true,get:function(){return no_sensitive_payload_1.noSensitivePayload}});exports.rules={get"no-algorithm-none"(){return require("./rules/no-algorithm-none").noAlgorithmNone},get"no-algorithm-confusion"(){return require("./rules/no-algorithm-confusion").noAlgorithmConfusion},get"require-algorithm-whitelist"(){return require("./rules/require-algorithm-whitelist").requireAlgorithmWhitelist},get"no-decode-without-verify"(){return require("./rules/no-decode-without-verify").noDecodeWithoutVerify},get"no-weak-secret"(){return require("./rules/no-weak-secret").noWeakSecret},get"require-expiration"(){return require("./rules/require-expiration").requireExpiration},get"no-hardcoded-secret"(){return require("./rules/no-hardcoded-secret").noHardcodedSecret},get"require-issued-at"(){return require("./rules/require-issued-at").requireIssuedAt},get"require-issuer-validation"(){return require("./rules/require-issuer-validation").requireIssuerValidation},get"require-audience-validation"(){return require("./rules/require-audience-validation").requireAudienceValidation},get"no-timestamp-manipulation"(){return require("./rules/no-timestamp-manipulation").noTimestampManipulation},get"require-max-age"(){return require("./rules/require-max-age").requireMaxAge},get"no-sensitive-payload"(){return require("./rules/no-sensitive-payload").noSensitivePayload}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-jwt-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-jwt-security",version:"3.6.0"},rules:exports.rules};const recommendedRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-expiration":"error","jwt-security/no-sensitive-payload":"warn","jwt-security/require-issued-at":"warn"};const strictRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/require-expiration":"error","jwt-security/require-issued-at":"error","jwt-security/require-issuer-validation":"error","jwt-security/require-audience-validation":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-max-age":"error","jwt-security/no-sensitive-payload":"error"};const legacyRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/no-hardcoded-secret":"warn"};exports.configs={flagship:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:{"jwt-security/no-algorithm-none":"error"}},recommended:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:recommendedRules},strict:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules},legacy:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:legacyRules},all:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules}};exports.default=exports.plugin;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmConfusion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noAlgorithmConfusion=(0,eslint_devkit_1.createRule)({name:"no-algorithm-confusion",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-algorithm-confusion.md",description:"Prevent algorithm confusion attacks using symmetric algorithms with asymmetric keys",cwe:"CWE-347",cvss:9.5},fixable:void 0,hasSuggestions:false,messages:{algorithmConfusion:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm Confusion Attack",cwe:"CWE-347",description:"Symmetric algorithm (HS*) used with public key allows token forgery",severity:"CRITICAL",fix:"Use asymmetric algorithms (RS256, ES256) with public keys",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),symmetricWithPublicKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Symmetric Algorithm with Public Key",cwe:"CWE-347",description:"HS256/384/512 should only be used with shared secrets, not public keys",severity:"CRITICAL",fix:"Switch to RS256 or ES256 for asymmetric key verification",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),useAsymmetricAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Asymmetric Algorithm",description:"Replace HS* with RS* or ES* algorithm",severity:"LOW",fix:"Use RS256 or ES256 with public key verification",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{symmetricAlgorithms:{type:"array",items:{type:"string"},default:["HS256","HS384","HS512"],description:"Algorithms to flag when used with public keys"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{symmetricAlgorithms:["HS256","HS384","HS512"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const symmetricAlgSet=new Set(options.symmetricAlgorithms??["HS256","HS384","HS512"]);const sourceCode=context.sourceCode;const hasSymmetricAlgorithm=algorithms=>{for(const alg of algorithms){if(symmetricAlgSet.has(alg)){return alg}}return null};return{CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options2=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options2===null){return}const algorithms=(0,utils_1.extractAlgorithms)(options2);const symmetricAlg=hasSymmetricAlgorithm(algorithms);if(!symmetricAlg){return}const mixesFamilies=algorithms.some(alg=>utils_1.SECURE_ALGORITHMS.has(alg));if(!mixesFamilies&&!(0,utils_1.isPublicKeySource)(node.arguments[1],sourceCode)){return}for(const prop of options2.properties){if(["algorithms","algorithm","alg"].includes((0,eslint_devkit_1.objectKeyName)(prop)??"")){context.report({node:prop.value,messageId:"algorithmConfusion",data:{algorithm:symmetricAlg}});return}}}}}});exports.default=exports.noAlgorithmConfusion;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmConfusion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noAlgorithmConfusion=(0,eslint_devkit_1.createRule)({name:"no-algorithm-confusion",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-algorithm-confusion.md",description:"Prevent algorithm confusion attacks using symmetric algorithms with asymmetric keys",cwe:"CWE-347",cvss:9.5},fixable:void 0,hasSuggestions:false,messages:{algorithmConfusion:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm Confusion Attack",cwe:"CWE-347",description:"Symmetric algorithm (HS*) used with public key allows token forgery",severity:"CRITICAL",fix:"Use asymmetric algorithms (RS256, ES256) with public keys",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),symmetricWithPublicKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Symmetric Algorithm with Public Key",cwe:"CWE-347",description:"HS256/384/512 should only be used with shared secrets, not public keys",severity:"CRITICAL",fix:"Switch to RS256 or ES256 for asymmetric key verification",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),useAsymmetricAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Asymmetric Algorithm",description:"Replace HS* with RS* or ES* algorithm",severity:"LOW",fix:"Use RS256 or ES256 with public key verification",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{symmetricAlgorithms:{type:"array",items:{type:"string"},default:["HS256","HS384","HS512"],description:"Algorithms to flag when used with public keys"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{symmetricAlgorithms:["HS256","HS384","HS512"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let symmetricAlgSet;let sourceCode;const hasSymmetricAlgorithm=algorithms=>{for(const alg of algorithms){if(symmetricAlgSet.has(alg)){return alg}}return null};return{before(){const options=context.options[0]??{};symmetricAlgSet=new Set(options.symmetricAlgorithms??["HS256","HS384","HS512"]);sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){return}const algorithms=(0,utils_1.extractAlgorithms)(options);const symmetricAlg=hasSymmetricAlgorithm(algorithms);if(!symmetricAlg){return}const mixesFamilies=algorithms.some(alg=>utils_1.SECURE_ALGORITHMS.has(alg));if(!mixesFamilies&&!(0,utils_1.isPublicKeySource)(node.arguments[1],sourceCode)){return}for(const prop of options.properties){if(["algorithms","algorithm","alg"].includes((0,eslint_devkit_1.objectKeyName)(prop)??"")){context.report({node:prop.value,messageId:"algorithmConfusion",data:{algorithm:symmetricAlg}});return}}}}}});exports.default=exports.noAlgorithmConfusion;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmNone=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noAlgorithmNone=(0,eslint_devkit_1.createRule)({name:"no-algorithm-none",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-algorithm-none.md",description:'Disallow JWT "none" algorithm which bypasses signature verification (CVE-2022-23540)',cwe:"CWE-347",cvss:9.5,confidence:"high"},fixable:void 0,hasSuggestions:false,messages:{algorithmNone:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm None Attack",cwe:"CWE-347",description:'Using alg:"none" bypasses signature verification, allowing token forgery',severity:"CRITICAL",fix:'Remove "none" and use RS256, ES256, or other secure algorithms',documentationLink:"https://nvd.nist.gov/vuln/detail/CVE-2022-23540"}),algorithmNoneInArray:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm None in Whitelist",cwe:"CWE-347",description:'Including "none" in algorithms array allows unsigned tokens',severity:"CRITICAL",fix:'Remove "none" from the algorithms array',documentationLink:"https://nvd.nist.gov/vuln/detail/CVE-2022-23540"}),emptyAlgorithms:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Empty Algorithms Array",cwe:"CWE-347",description:"Empty algorithms array may default to accepting any algorithm including none",severity:"HIGH",fix:'Specify explicit algorithms: ["RS256"] or ["ES256"]',documentationLink:"https://tools.ietf.org/html/rfc8725"}),useSecureAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Secure Algorithm",description:"Replace with a secure algorithm",severity:"LOW",fix:'Use algorithms: ["RS256"] or algorithms: ["ES256"]',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:'Allow "none" algorithm in test files'},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{allowInTests:false,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const{allowInTests=false}=options;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const isNoneAlgorithm=value=>{return value.toLowerCase()==="none"};const sourceCode=context.sourceCode;const checkOptionsForNone=optionsNode=>{for(const prop of optionsNode.properties){const keyName=(0,eslint_devkit_1.objectKeyName)(prop);if(keyName!=="algorithms"&&keyName!=="algorithm"&&keyName!=="alg"){continue}if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){if(isNoneAlgorithm(prop.value.value)){context.report({node:prop.value,messageId:"algorithmNone"})}}if(prop.value.type==="ArrayExpression"){if(prop.value.elements.length===0){context.report({node:prop.value,messageId:"emptyAlgorithms"});continue}for(const elem of prop.value.elements){if(elem&&elem.type==="Literal"&&typeof elem.value==="string"&&isNoneAlgorithm(elem.value)){context.report({node:elem,messageId:"algorithmNoneInArray"})}}}}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config!==null){checkOptionsForNone(config.options)}};return{CallExpression(node){checkConfig(node);if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)&&!(0,utils_1.isSignOperation)(node,sourceCode)){return}const options2=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options2!==null){checkOptionsForNone(options2)}},NewExpression:checkConfig}}});exports.default=exports.noAlgorithmNone;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmNone=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noAlgorithmNone=(0,eslint_devkit_1.createRule)({name:"no-algorithm-none",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-algorithm-none.md",description:'Disallow JWT "none" algorithm which bypasses signature verification (CVE-2022-23540)',cwe:"CWE-347",cvss:9.5,confidence:"high"},fixable:void 0,hasSuggestions:false,messages:{algorithmNone:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm None Attack",cwe:"CWE-347",description:'Using alg:"none" bypasses signature verification, allowing token forgery',severity:"CRITICAL",fix:'Remove "none" and use RS256, ES256, or other secure algorithms',documentationLink:"https://nvd.nist.gov/vuln/detail/CVE-2022-23540"}),algorithmNoneInArray:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm None in Whitelist",cwe:"CWE-347",description:'Including "none" in algorithms array allows unsigned tokens',severity:"CRITICAL",fix:'Remove "none" from the algorithms array',documentationLink:"https://nvd.nist.gov/vuln/detail/CVE-2022-23540"}),emptyAlgorithms:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Empty Algorithms Array",cwe:"CWE-347",description:"Empty algorithms array may default to accepting any algorithm including none",severity:"HIGH",fix:'Specify explicit algorithms: ["RS256"] or ["ES256"]',documentationLink:"https://tools.ietf.org/html/rfc8725"}),useSecureAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Secure Algorithm",description:"Replace with a secure algorithm",severity:"LOW",fix:'Use algorithms: ["RS256"] or algorithms: ["ES256"]',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:'Allow "none" algorithm in test files'},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{allowInTests:false,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;const isNoneAlgorithm=value=>{return value.toLowerCase()==="none"};const checkOptionsForNone=optionsNode=>{for(const prop of optionsNode.properties){const keyName=(0,eslint_devkit_1.objectKeyName)(prop);if(keyName!=="algorithms"&&keyName!=="algorithm"&&keyName!=="alg"){continue}if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){if(isNoneAlgorithm(prop.value.value)){context.report({node:prop.value,messageId:"algorithmNone"})}}if(prop.value.type==="ArrayExpression"){if(prop.value.elements.length===0){context.report({node:prop.value,messageId:"emptyAlgorithms"});continue}for(const elem of prop.value.elements){if(elem&&elem.type==="Literal"&&typeof elem.value==="string"&&isNoneAlgorithm(elem.value)){context.report({node:elem,messageId:"algorithmNoneInArray"})}}}}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config!==null){checkOptionsForNone(config.options)}};return{before(){const options=context.options[0]??{};const{allowInTests=false}=options;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return false}sourceCode=context.sourceCode;return true},CallExpression(node){checkConfig(node);if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)&&!(0,utils_1.isSignOperation)(node,sourceCode)){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){checkOptionsForNone(options)}},NewExpression:checkConfig}}});exports.default=exports.noAlgorithmNone;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDecodeWithoutVerify=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const provenance_1=require("../../utils/provenance");const DEFAULT_TRUSTED_ANNOTATIONS=["@decoded-header-only","@verified-separately"];const JWT_DECODE_LIBRARY_NAMES=new Set(["jwtDecode","jwt_decode"]);exports.noDecodeWithoutVerify=(0,eslint_devkit_1.createRule)({name:"no-decode-without-verify",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-decode-without-verify.md",description:"Disallow trusting decoded JWT payload without signature verification",cwe:"CWE-345",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{decodeWithoutVerify:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Decoded JWT Without Verification",cwe:"CWE-345",description:"jwt.decode() returns payload without verifying signature - data can be forged",severity:"HIGH",fix:"Use jwt.verify(token, secret) instead of jwt.decode(token)",documentationLink:"https://owasp.org/API-Security/0xa7-security-misconfiguration/"}),jwtDecodeLibrary:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"jwt-decode Library Usage",cwe:"CWE-345",description:"jwt-decode library only decodes tokens, never verifies signatures",severity:"HIGH",fix:"Use jsonwebtoken.verify() or jose.jwtVerify() for verification",documentationLink:"https://www.npmjs.com/package/jwt-decode"}),useVerifyInstead:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Verify Instead",description:"Replace decode with verify to ensure authenticity",severity:"LOW",fix:'jwt.verify(token, secret, { algorithms: ["RS256"] })',documentationLink:"https://www.npmjs.com/package/jsonwebtoken"})},schema:[{type:"object",properties:{allowHeaderInspection:{type:"boolean",default:false,description:"Allow decode() for reading header before verification"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:["@decoded-header-only","@verified-separately"]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{allowHeaderInspection:false,trustedSanitizers:[],trustedAnnotations:DEFAULT_TRUSTED_ANNOTATIONS,strictMode:false}],create(context){const options=context.options[0]??{};const{trustedAnnotations=DEFAULT_TRUSTED_ANNOTATIONS,allowHeaderInspection=false}=options;const sourceCode=context.sourceCode;const isJwtDecodeLibrary=node=>node.callee.type==="Identifier"&&(0,utils_1.isJwtLibraryCall)(node,JWT_DECODE_LIBRARY_NAMES,sourceCode);const TIME_CLAIMS=new Set(["exp","iat","nbf"]);const skipTypeWrappers=node=>{let current=node;while(current.parent!=null&&(current.parent.type==="TSAsExpression"||current.parent.type==="TSNonNullExpression")){current=current.parent}return current};const isAllowedRead=(member,headerReads)=>{const name=(0,eslint_devkit_1.propertyName)(member);if((0,eslint_devkit_1.namesOneOf)(name,TIME_CLAIMS))return true;if(!headerReads||name!=="header")return false;if(allowHeaderInspection)return true;const next=member.parent;return next.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(next)==="kid"};const returnsHeader=node=>{const options2=node.arguments[1];if(options2?.type!=="ObjectExpression")return false;const complete=(0,utils_1.getOptionValue)(options2,"complete");return complete?.type==="Literal"&&complete.value===true};const destructuresOnlyTimeClaims=pattern=>pattern.properties.every(prop=>prop.type==="Property"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.objectKeyName)(prop),TIME_CLAIMS));const readsOnlyAllowedClaims=(node,headerReads)=>{const outer=skipTypeWrappers(node);const parent=outer.parent;if(parent.type==="MemberExpression"&&parent.object===outer){return isAllowedRead(parent,headerReads)}if(parent.type!=="VariableDeclarator"){return false}if(parent.id.type==="ObjectPattern"){return destructuresOnlyTimeClaims(parent.id)}if(parent.id.type!=="Identifier"){return false}const[variable]=context.sourceCode.getDeclaredVariables(parent);let sawAllowedRead=false;const allUsesAllowed=variable.references.every(reference=>{const use=skipTypeWrappers(reference.identifier);const useParent=use.parent;if(useParent.type==="VariableDeclarator"){return true}if(useParent.type==="UnaryExpression"||useParent.type==="IfStatement"||useParent.type==="LogicalExpression"||useParent.type==="ConditionalExpression"){return true}const isAllowed=useParent.type==="MemberExpression"&&useParent.object===use&&isAllowedRead(useParent,headerReads);if(isAllowed){sawAllowedRead=true}return isAllowed});return allUsesAllowed&&sawAllowedRead};return{CallExpression(node){if((0,utils_1.isDecodeOperation)(node,sourceCode)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyAllowedClaims(node,returnsHeader(node))){return}const[tokenArgument]=node.arguments;if(tokenArgument!==void 0&&(0,provenance_1.fromBackChannel)(tokenArgument,sourceCode,{filename:context.filename,parser:context.languageOptions.parser})){return}context.report({node,messageId:"decodeWithoutVerify"});return}if(isJwtDecodeLibrary(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyAllowedClaims(node,false)){return}context.report({node,messageId:"jwtDecodeLibrary"})}}}}});exports.default=exports.noDecodeWithoutVerify;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDecodeWithoutVerify=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const provenance_1=require("../../utils/provenance");const DEFAULT_TRUSTED_ANNOTATIONS=["@decoded-header-only","@verified-separately"];const JWT_DECODE_LIBRARY_NAMES=new Set(["jwtDecode","jwt_decode"]);exports.noDecodeWithoutVerify=(0,eslint_devkit_1.createRule)({name:"no-decode-without-verify",skipTestFiles:true,meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-decode-without-verify.md",description:"Disallow trusting decoded JWT payload without signature verification",cwe:"CWE-345",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{decodeWithoutVerify:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Decoded JWT Without Verification",cwe:"CWE-345",description:"jwt.decode() returns payload without verifying signature - data can be forged",severity:"HIGH",fix:"Use jwt.verify(token, secret) instead of jwt.decode(token)",documentationLink:"https://owasp.org/API-Security/0xa7-security-misconfiguration/"}),jwtDecodeLibrary:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"jwt-decode Library Usage",cwe:"CWE-345",description:"jwt-decode library only decodes tokens, never verifies signatures",severity:"HIGH",fix:"Use jsonwebtoken.verify() or jose.jwtVerify() for verification",documentationLink:"https://www.npmjs.com/package/jwt-decode"}),useVerifyInstead:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Verify Instead",description:"Replace decode with verify to ensure authenticity",severity:"LOW",fix:'jwt.verify(token, secret, { algorithms: ["RS256"] })',documentationLink:"https://www.npmjs.com/package/jsonwebtoken"})},schema:[{type:"object",properties:{allowHeaderInspection:{type:"boolean",default:false,description:"Allow decode() for reading header before verification"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:["@decoded-header-only","@verified-separately"]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{allowHeaderInspection:false,trustedSanitizers:[],trustedAnnotations:DEFAULT_TRUSTED_ANNOTATIONS,strictMode:false}],createOnce(context){let trustedAnnotations;let allowHeaderInspection;let sourceCode;let TIME_CLAIMS;const isJwtDecodeLibrary=node=>node.callee.type==="Identifier"&&(0,utils_1.isJwtLibraryCall)(node,JWT_DECODE_LIBRARY_NAMES,sourceCode);const skipTypeWrappers=node=>{let current=node;while(current.parent!=null&&(current.parent.type==="TSAsExpression"||current.parent.type==="TSNonNullExpression")){current=current.parent}return current};const isAllowedRead=(member,headerReads)=>{const name=(0,eslint_devkit_1.propertyName)(member);if((0,eslint_devkit_1.namesOneOf)(name,TIME_CLAIMS))return true;if(!headerReads||name!=="header")return false;if(allowHeaderInspection)return true;const next=member.parent;return next.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(next)==="kid"};const returnsHeader=node=>{const options=node.arguments[1];if(options?.type!=="ObjectExpression")return false;const complete=(0,utils_1.getOptionValue)(options,"complete");return complete?.type==="Literal"&&complete.value===true};const destructuresOnlyTimeClaims=pattern=>pattern.properties.every(prop=>prop.type==="Property"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.objectKeyName)(prop),TIME_CLAIMS));const readsOnlyAllowedClaims=(node,headerReads)=>{const outer=skipTypeWrappers(node);const parent=outer.parent;if(parent.type==="MemberExpression"&&parent.object===outer){return isAllowedRead(parent,headerReads)}if(parent.type!=="VariableDeclarator"){return false}if(parent.id.type==="ObjectPattern"){return destructuresOnlyTimeClaims(parent.id)}if(parent.id.type!=="Identifier"){return false}const[variable]=context.sourceCode.getDeclaredVariables(parent);let sawAllowedRead=false;const allUsesAllowed=variable.references.every(reference=>{const use=skipTypeWrappers(reference.identifier);const useParent=use.parent;if(useParent.type==="VariableDeclarator"){return true}if(useParent.type==="UnaryExpression"||useParent.type==="IfStatement"||useParent.type==="LogicalExpression"||useParent.type==="ConditionalExpression"){return true}const isAllowed=useParent.type==="MemberExpression"&&useParent.object===use&&isAllowedRead(useParent,headerReads);if(isAllowed){sawAllowedRead=true}return isAllowed});return allUsesAllowed&&sawAllowedRead};return{before(){const options=context.options[0]??{};({trustedAnnotations=DEFAULT_TRUSTED_ANNOTATIONS,allowHeaderInspection=false}=options);sourceCode=context.sourceCode;TIME_CLAIMS=new Set(["exp","iat","nbf"])},CallExpression(node){if((0,utils_1.isDecodeOperation)(node,sourceCode)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyAllowedClaims(node,returnsHeader(node))){return}const[tokenArgument]=node.arguments;if(tokenArgument!==void 0&&(0,provenance_1.fromBackChannel)(tokenArgument,sourceCode,{filename:context.filename,parser:context.languageOptions.parser})){return}context.report({node,messageId:"decodeWithoutVerify"});return}if(isJwtDecodeLibrary(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyAllowedClaims(node,false)){return}context.report({node,messageId:"jwtDecodeLibrary"})}}}}});exports.default=exports.noDecodeWithoutVerify;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noHardcodedSecret=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-secret",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-hardcoded-secret.md",description:"Disallow hardcoded secrets in JWT sign/verify operations",cwe:"CWE-798",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{hardcodedSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded JWT Secret",cwe:"CWE-798",description:"Secret key hardcoded in source code can be extracted from repositories",severity:"HIGH",fix:"Use process.env.JWT_SECRET or secure secret management",documentationLink:"https://cwe.mitre.org/data/definitions/798.html"}),useEnvVariable:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Environment Variable",description:"Replace hardcoded secret with environment variable",severity:"LOW",fix:"process.env.JWT_SECRET",documentationLink:"https://12factor.net/config"})},schema:[{type:"object",properties:{envPatterns:{type:"array",items:{type:"string"},default:[],description:"Patterns that indicate safe environment variable usage"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{envPatterns:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;const checkKeys=keys=>{for(const key of keys){const hardcoded=(0,utils_1.keyLiterals)(key,sourceCode).some(({literal})=>!(0,utils_1.isPublicKeyMaterial)(literal));if(hardcoded){context.report({node:key,messageId:"hardcodedSecret"})}}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config===null)return false;checkKeys(config.keys);return true};return{CallExpression(node){if(checkConfig(node)){return}if(!(0,utils_1.isSignOperation)(node,sourceCode)&&!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}checkKeys((0,utils_1.keyNodesOf)(node,sourceCode))},NewExpression:checkConfig}}});exports.default=exports.noHardcodedSecret;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noHardcodedSecret=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-secret",skipTestFiles:true,meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-hardcoded-secret.md",description:"Disallow hardcoded secrets in JWT sign/verify operations",cwe:"CWE-798",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{hardcodedSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded JWT Secret",cwe:"CWE-798",description:"Secret key hardcoded in source code can be extracted from repositories",severity:"HIGH",fix:"Use process.env.JWT_SECRET or secure secret management",documentationLink:"https://cwe.mitre.org/data/definitions/798.html"}),useEnvVariable:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Environment Variable",description:"Replace hardcoded secret with environment variable",severity:"LOW",fix:"process.env.JWT_SECRET",documentationLink:"https://12factor.net/config"})},schema:[{type:"object",properties:{envPatterns:{type:"array",items:{type:"string"},default:[],description:"Patterns that indicate safe environment variable usage"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{envPatterns:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;const checkKeys=keys=>{for(const key of keys){const hardcoded=(0,utils_1.keyLiterals)(key,sourceCode).some(({literal})=>!(0,utils_1.isPublicKeyMaterial)(literal));if(hardcoded){context.report({node:key,messageId:"hardcodedSecret"})}}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config===null)return false;checkKeys(config.keys);return true};return{before(){sourceCode=context.sourceCode},CallExpression(node){if(checkConfig(node)){return}if(!(0,utils_1.isSignOperation)(node,sourceCode)&&!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}checkKeys((0,utils_1.keyNodesOf)(node,sourceCode))},NewExpression:checkConfig}}});exports.default=exports.noHardcodedSecret;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitivePayload=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const type_info_1=require("../../utils/type-info");const value_flow_1=require("../../utils/value-flow");exports.noSensitivePayload=(0,eslint_devkit_1.createRule)({name:"no-sensitive-payload",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-sensitive-payload.md",description:"Prevent storing sensitive data in JWT payload which is only base64-encoded",cwe:"CWE-359",cvss:5.3},fixable:void 0,hasSuggestions:false,messages:{sensitivePayloadField:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in JWT Payload",cwe:"CWE-359",description:'JWT payloads are not encrypted - sensitive data like "{{fieldName}}" can be read by anyone',severity:"MEDIUM",fix:"Store sensitive data server-side, reference by ID in token",documentationLink:"https://tools.ietf.org/html/rfc8725"}),wholeRecordSpread:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Whole Record Spread into JWT Payload",cwe:"CWE-200",cvss:5.3,description:"Spreading a whole object into the claims copies every field it has - including ones added later - into a token anyone can read",severity:"MEDIUM",fix:"Pick the claims explicitly: { sub: user.id, role: user.role }",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{additionalSensitiveFields:{type:"array",items:{type:"string"},default:[],description:"Additional field names to flag as sensitive"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveFields:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const additionalFields=options.additionalSensitiveFields??[];const allSensitiveFields=new Set([...utils_1.SENSITIVE_PAYLOAD_FIELDS,...additionalFields]);const sourceCode=context.sourceCode;const isSignedRequestObject=payload=>{const clientId=(0,utils_1.getOptionValue)(payload,"client_id");const issuer=(0,utils_1.getOptionValue)(payload,"iss");return clientId!==void 0&&issuer!==void 0&&(0,utils_1.hasOption)(payload,"aud")&&sourceCode.getText(issuer)===sourceCode.getText(clientId)};const isAuthorizationParams=(spread,payload)=>{if(!isSignedRequestObject(payload))return false;const source=(0,value_flow_1.resolveTerminal)(spread.argument,sourceCode);return source.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(0,value_flow_1.findVariable)(sourceCode,source)?.defs[0]?.type==="Parameter"};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);const payload=(0,utils_1.resolveObject)(chain===null?node.arguments[0]:chain.builder.arguments[0],sourceCode);if(payload===null){return}for(const spread of payload.opaqueSpreads){if(!isAuthorizationParams(spread,payload)&&(0,type_info_1.mayCarryField)(spread.argument,sourceCode,allSensitiveFields)){context.report({node:spread,messageId:"wholeRecordSpread"})}}for(const prop of payload.properties){const keyText=(0,eslint_devkit_1.objectKeyName)(prop);if(keyText!==null&&allSensitiveFields.has(keyText.toLowerCase())){context.report({node:prop,messageId:"sensitivePayloadField",data:{fieldName:keyText}})}}}}}});exports.default=exports.noSensitivePayload;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitivePayload=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const type_info_1=require("../../utils/type-info");const value_flow_1=require("../../utils/value-flow");exports.noSensitivePayload=(0,eslint_devkit_1.createRule)({name:"no-sensitive-payload",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-sensitive-payload.md",description:"Prevent storing sensitive data in JWT payload which is only base64-encoded",cwe:"CWE-359",cvss:5.3},fixable:void 0,hasSuggestions:false,messages:{sensitivePayloadField:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in JWT Payload",cwe:"CWE-359",description:'JWT payloads are not encrypted - sensitive data like "{{fieldName}}" can be read by anyone',severity:"MEDIUM",fix:"Store sensitive data server-side, reference by ID in token",documentationLink:"https://tools.ietf.org/html/rfc8725"}),wholeRecordSpread:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Whole Record Spread into JWT Payload",cwe:"CWE-200",cvss:5.3,description:"Spreading a whole object into the claims copies every field it has - including ones added later - into a token anyone can read",severity:"MEDIUM",fix:"Pick the claims explicitly: { sub: user.id, role: user.role }",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{additionalSensitiveFields:{type:"array",items:{type:"string"},default:[],description:"Additional field names to flag as sensitive"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveFields:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let allSensitiveFields;let sourceCode;const isSignedRequestObject=payload=>{const clientId=(0,utils_1.getOptionValue)(payload,"client_id");const issuer=(0,utils_1.getOptionValue)(payload,"iss");return clientId!==void 0&&issuer!==void 0&&(0,utils_1.hasOption)(payload,"aud")&&sourceCode.getText(issuer)===sourceCode.getText(clientId)};const isAuthorizationParams=(spread,payload)=>{if(!isSignedRequestObject(payload))return false;const source=(0,value_flow_1.resolveTerminal)(spread.argument,sourceCode);return source.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(0,value_flow_1.findVariable)(sourceCode,source)?.defs[0]?.type==="Parameter"};return{before(){const options=context.options[0]??{};const additionalFields=options.additionalSensitiveFields??[];allSensitiveFields=new Set([...utils_1.SENSITIVE_PAYLOAD_FIELDS,...additionalFields]);sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);const payload=(0,utils_1.resolveObject)(chain===null?node.arguments[0]:chain.builder.arguments[0],sourceCode);if(payload===null){return}for(const spread of payload.opaqueSpreads){if(!isAuthorizationParams(spread,payload)&&(0,type_info_1.mayCarryField)(spread.argument,sourceCode,allSensitiveFields)){context.report({node:spread,messageId:"wholeRecordSpread"})}}for(const prop of payload.properties){const keyText=(0,eslint_devkit_1.objectKeyName)(prop);if(keyText!==null&&allSensitiveFields.has(keyText.toLowerCase())){context.report({node:prop,messageId:"sensitivePayloadField",data:{fieldName:keyText}})}}}}}});exports.default=exports.noSensitivePayload;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimestampManipulation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS=300;exports.noTimestampManipulation=(0,eslint_devkit_1.createRule)({name:"no-timestamp-manipulation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-timestamp-manipulation.md",description:"Prevent disabling automatic timestamp generation which enables replay attacks",cwe:"CWE-294",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{timestampDisabled:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timestamp Generation Disabled",cwe:"CWE-294",description:"noTimestamp:true disables iat claim, enabling replay attacks",severity:"HIGH",fix:"Remove noTimestamp option or set to false",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),noTimestampTrue:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Back to the Future Attack Vector",cwe:"CWE-294",description:"Disabling timestamps allows attackers to forge tokens valid in the future",severity:"HIGH",fix:"Always include iat claim for freshness validation",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),ignoreExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Expiration Check Disabled",cwe:"CWE-613",cvss:7.5,description:"ignoreExpiration:true accepts expired tokens, so a stolen token never stops working",severity:"HIGH",fix:"Remove ignoreExpiration; refresh the token instead of accepting an expired one",documentationLink:"https://tools.ietf.org/html/rfc8725"}),excessiveClockTolerance:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Excessive Clock Tolerance",cwe:"CWE-613",cvss:7.5,description:"A clockTolerance far beyond clock skew keeps expired tokens valid for that long",severity:"HIGH",fix:"Keep clockTolerance to seconds of skew (maxClockToleranceSeconds, default 300)",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{maxClockToleranceSeconds:{type:"number",minimum:0,default:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,description:"Largest numeric clockTolerance (seconds) a verify may allow"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{maxClockToleranceSeconds:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;const{maxClockToleranceSeconds=DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS}=context.options[0]??{};const reportLiteralTrue=(options,name,messageId)=>{const value=(0,utils_1.getOptionValue)(options,name);if(value?.type==="Literal"&&value.value===true){context.report({node:value,messageId})}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config!==null){reportLiteralTrue(config.options,"ignoreExpiration","ignoreExpiration")}};return{CallExpression(node){checkConfig(node);if((0,utils_1.isSignOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"noTimestamp","noTimestampTrue")}return}if((0,utils_1.isVerifyOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"ignoreExpiration","ignoreExpiration");const tolerance=(0,utils_1.getOptionValue)(options,"clockTolerance");const seconds=tolerance===void 0?null:(0,utils_1.staticNumber)(tolerance,sourceCode);if(seconds!==null&&seconds>maxClockToleranceSeconds){context.report({node:tolerance,messageId:"excessiveClockTolerance"})}}}},NewExpression:checkConfig}}});exports.default=exports.noTimestampManipulation;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimestampManipulation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS=300;exports.noTimestampManipulation=(0,eslint_devkit_1.createRule)({name:"no-timestamp-manipulation",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-timestamp-manipulation.md",description:"Prevent disabling automatic timestamp generation which enables replay attacks",cwe:"CWE-294",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{timestampDisabled:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timestamp Generation Disabled",cwe:"CWE-294",description:"noTimestamp:true disables iat claim, enabling replay attacks",severity:"HIGH",fix:"Remove noTimestamp option or set to false",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),noTimestampTrue:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Back to the Future Attack Vector",cwe:"CWE-294",description:"Disabling timestamps allows attackers to forge tokens valid in the future",severity:"HIGH",fix:"Always include iat claim for freshness validation",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),ignoreExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Expiration Check Disabled",cwe:"CWE-613",cvss:7.5,description:"ignoreExpiration:true accepts expired tokens, so a stolen token never stops working",severity:"HIGH",fix:"Remove ignoreExpiration; refresh the token instead of accepting an expired one",documentationLink:"https://tools.ietf.org/html/rfc8725"}),excessiveClockTolerance:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Excessive Clock Tolerance",cwe:"CWE-613",cvss:7.5,description:"A clockTolerance far beyond clock skew keeps expired tokens valid for that long",severity:"HIGH",fix:"Keep clockTolerance to seconds of skew (maxClockToleranceSeconds, default 300)",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{maxClockToleranceSeconds:{type:"number",minimum:0,default:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,description:"Largest numeric clockTolerance (seconds) a verify may allow"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{maxClockToleranceSeconds:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;let maxClockToleranceSeconds;const reportLiteralTrue=(options,name,messageId)=>{const value=(0,utils_1.getOptionValue)(options,name);if(value?.type==="Literal"&&value.value===true){context.report({node:value,messageId})}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config!==null){reportLiteralTrue(config.options,"ignoreExpiration","ignoreExpiration")}};return{before(){sourceCode=context.sourceCode;({maxClockToleranceSeconds=DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS}=context.options[0]??{})},CallExpression(node){checkConfig(node);if((0,utils_1.isSignOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"noTimestamp","noTimestampTrue")}return}if((0,utils_1.isVerifyOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"ignoreExpiration","ignoreExpiration");const tolerance=(0,utils_1.getOptionValue)(options,"clockTolerance");const seconds=tolerance===void 0?null:(0,utils_1.staticNumber)(tolerance,sourceCode);if(seconds!==null&&seconds>maxClockToleranceSeconds){context.report({node:tolerance,messageId:"excessiveClockTolerance"})}}}},NewExpression:checkConfig}}});exports.default=exports.noTimestampManipulation;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const WEAK_SECRET_PATTERNS=[/^secret$/i,/^password$/i,/^123456/,/^test/i,/^demo/i,/^example/i,/^changeme/i,/^default/i];function decodedByteLength(value,encoding){if(encoding==="hex")return Math.floor(value.length/2);if(encoding==="base64"||encoding==="base64url"){return Math.floor(value.replace(/=+$/u,"").length*3/4)}return value.length}exports.noWeakSecret=(0,eslint_devkit_1.createRule)({name:"no-weak-secret",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-weak-secret.md",description:"Require strong secrets (256+ bits) for HMAC-based JWT signing",cwe:"CWE-326",cvss:5.9},fixable:void 0,hasSuggestions:false,messages:{weakSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak JWT Secret",cwe:"CWE-326",description:"JWT secret is too weak and can be brute-forced",severity:"HIGH",fix:'Use crypto.randomBytes(32).toString("hex") for secrets',documentationLink:"https://tools.ietf.org/html/rfc8725"}),shortSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Short JWT Secret",cwe:"CWE-326",description:"JWT secret must be at least 32 characters (256 bits) for HS256",severity:"HIGH",fix:'Generate a secret with: crypto.randomBytes(32).toString("hex")',documentationLink:"https://tools.ietf.org/html/rfc8725"}),useStrongSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Strong Secret",description:"Replace with cryptographically strong secret",severity:"LOW",fix:"Use process.env.JWT_SECRET with a 256-bit random value",documentationLink:"https://nodejs.org/api/crypto.html"})},schema:[{type:"object",properties:{minSecretLength:{type:"integer",default:32,minimum:16,description:"Minimum secret length in characters"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{minSecretLength:32,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const{minSecretLength=32}=options;const sourceCode=context.sourceCode;const isKnownWeakPattern=value=>{return WEAK_SECRET_PATTERNS.some(pattern=>pattern.test(value))};const checkLiteral=({literal,encoding})=>{if(literal.type==="Literal"){const secretValue=literal.value;if(isKnownWeakPattern(secretValue)){context.report({node:literal,messageId:"weakSecret"});return}if(decodedByteLength(secretValue,encoding)<minSecretLength){context.report({node:literal,messageId:"shortSecret",data:{length:String(secretValue.length),minLength:String(minSecretLength)}})}return}const rawValue=literal.quasis[0].value.raw;if(isKnownWeakPattern(rawValue)||rawValue.length<minSecretLength){context.report({node:literal,messageId:"weakSecret"})}};const checkKeys=keys=>{for(const key of keys){(0,utils_1.keyLiterals)(key,sourceCode).forEach(checkLiteral)}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config===null)return false;checkKeys(config.keys);return true};return{CallExpression(node){if(checkConfig(node)){return}if(!(0,utils_1.isSignOperation)(node,sourceCode)&&!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}checkKeys((0,utils_1.keyNodesOf)(node,sourceCode))},NewExpression:checkConfig}}});exports.default=exports.noWeakSecret;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noWeakSecret=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const WEAK_SECRET_PATTERNS=[/^secret$/i,/^password$/i,/^123456/,/^test/i,/^demo/i,/^example/i,/^changeme/i,/^default/i];function decodedByteLength(value,encoding){if(encoding==="hex")return Math.floor(value.length/2);if(encoding==="base64"||encoding==="base64url"){return Math.floor(value.replace(/=+$/u,"").length*3/4)}return value.length}exports.noWeakSecret=(0,eslint_devkit_1.createRule)({name:"no-weak-secret",skipTestFiles:true,meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-weak-secret.md",description:"Require strong secrets (256+ bits) for HMAC-based JWT signing",cwe:"CWE-326",cvss:5.9},fixable:void 0,hasSuggestions:false,messages:{weakSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Weak JWT Secret",cwe:"CWE-326",description:"JWT secret is too weak and can be brute-forced",severity:"HIGH",fix:'Use crypto.randomBytes(32).toString("hex") for secrets',documentationLink:"https://tools.ietf.org/html/rfc8725"}),shortSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Short JWT Secret",cwe:"CWE-326",description:"JWT secret must be at least 32 characters (256 bits) for HS256",severity:"HIGH",fix:'Generate a secret with: crypto.randomBytes(32).toString("hex")',documentationLink:"https://tools.ietf.org/html/rfc8725"}),useStrongSecret:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Strong Secret",description:"Replace with cryptographically strong secret",severity:"LOW",fix:"Use process.env.JWT_SECRET with a 256-bit random value",documentationLink:"https://nodejs.org/api/crypto.html"})},schema:[{type:"object",properties:{minSecretLength:{type:"integer",default:32,minimum:16,description:"Minimum secret length in characters"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{minSecretLength:32,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let minSecretLength;let sourceCode;const isKnownWeakPattern=value=>{return WEAK_SECRET_PATTERNS.some(pattern=>pattern.test(value))};const checkLiteral=({literal,encoding})=>{if(literal.type==="Literal"){const secretValue=literal.value;if(isKnownWeakPattern(secretValue)){context.report({node:literal,messageId:"weakSecret"});return}if(decodedByteLength(secretValue,encoding)<minSecretLength){context.report({node:literal,messageId:"shortSecret",data:{length:String(secretValue.length),minLength:String(minSecretLength)}})}return}const rawValue=literal.quasis[0].value.raw;if(isKnownWeakPattern(rawValue)||rawValue.length<minSecretLength){context.report({node:literal,messageId:"weakSecret"})}};const checkKeys=keys=>{for(const key of keys){(0,utils_1.keyLiterals)(key,sourceCode).forEach(checkLiteral)}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config===null)return false;checkKeys(config.keys);return true};return{before(){const options=context.options[0]??{};({minSecretLength=32}=options);sourceCode=context.sourceCode},CallExpression(node){if(checkConfig(node)){return}if(!(0,utils_1.isSignOperation)(node,sourceCode)&&!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}checkKeys((0,utils_1.keyNodesOf)(node,sourceCode))},NewExpression:checkConfig}}});exports.default=exports.noWeakSecret;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAlgorithmWhitelist=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireAlgorithmWhitelist=(0,eslint_devkit_1.createRule)({name:"require-algorithm-whitelist",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-algorithm-whitelist.md",description:"Require explicit algorithm specification in JWT verify operations",cwe:"CWE-757",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{missingAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Algorithm Whitelist",cwe:"CWE-757",description:"JWT verification without explicit algorithms trusts the token header",severity:"HIGH",fix:'Add algorithms: ["RS256"] or algorithms: ["ES256"] to options',documentationLink:"https://tools.ietf.org/html/rfc8725"}),addAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Algorithm Whitelist",description:"Specify allowed algorithms explicitly",severity:"LOW",fix:'jwt.verify(token, key, { algorithms: ["RS256"] })',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{recommendedAlgorithms:{type:"array",items:{type:"string"},default:["RS256","ES256"],description:"Algorithms to suggest in auto-fix"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{recommendedAlgorithms:["RS256","ES256"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;return{CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingAlgorithmWhitelist"});return}if(!(0,utils_1.hasOption)(options,"algorithms")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingAlgorithmWhitelist"})}}}}});exports.default=exports.requireAlgorithmWhitelist;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAlgorithmWhitelist=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireAlgorithmWhitelist=(0,eslint_devkit_1.createRule)({name:"require-algorithm-whitelist",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-algorithm-whitelist.md",description:"Require explicit algorithm specification in JWT verify operations",cwe:"CWE-757",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{missingAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Algorithm Whitelist",cwe:"CWE-757",description:"JWT verification without explicit algorithms trusts the token header",severity:"HIGH",fix:'Add algorithms: ["RS256"] or algorithms: ["ES256"] to options',documentationLink:"https://tools.ietf.org/html/rfc8725"}),addAlgorithmWhitelist:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Algorithm Whitelist",description:"Specify allowed algorithms explicitly",severity:"LOW",fix:'jwt.verify(token, key, { algorithms: ["RS256"] })',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{recommendedAlgorithms:{type:"array",items:{type:"string"},default:["RS256","ES256"],description:"Algorithms to suggest in auto-fix"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{recommendedAlgorithms:["RS256","ES256"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;return{before(){sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingAlgorithmWhitelist"});return}if(!(0,utils_1.hasOption)(options,"algorithms")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingAlgorithmWhitelist"})}}}}});exports.default=exports.requireAlgorithmWhitelist;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAudienceValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireAudienceValidation=(0,eslint_devkit_1.createRule)({name:"require-audience-validation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-audience-validation.md",description:"Require audience (aud) claim validation in JWT verify operations",cwe:"CWE-287",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{missingAudienceValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Audience Validation",cwe:"CWE-287",description:"JWT verification without audience validation accepts tokens intended for other services",severity:"MEDIUM",fix:'Add audience option: { audience: "https://api.example.com" }',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{knownAudiences:{type:"array",items:{type:"string"},default:[],description:"Known valid audiences to suggest"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{knownAudiences:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingAudienceValidation"});return}if(!(0,utils_1.hasOption)(options,"audience")&&!(0,utils_1.hasOption)(options,"aud")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingAudienceValidation"})}}}}});exports.default=exports.requireAudienceValidation;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAudienceValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireAudienceValidation=(0,eslint_devkit_1.createRule)({name:"require-audience-validation",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-audience-validation.md",description:"Require audience (aud) claim validation in JWT verify operations",cwe:"CWE-287",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{missingAudienceValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Audience Validation",cwe:"CWE-287",description:"JWT verification without audience validation accepts tokens intended for other services",severity:"MEDIUM",fix:'Add audience option: { audience: "https://api.example.com" }',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{knownAudiences:{type:"array",items:{type:"string"},default:[],description:"Known valid audiences to suggest"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{knownAudiences:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;return{before(){sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingAudienceValidation"});return}if(!(0,utils_1.hasOption)(options,"audience")&&!(0,utils_1.hasOption)(options,"aud")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingAudienceValidation"})}}}}});exports.default=exports.requireAudienceValidation;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireExpiration=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const lifetime_1=require("../../utils/lifetime");const nest_modules_1=require("../../utils/nest-modules");exports.requireExpiration=(0,eslint_devkit_1.createRule)({name:"require-expiration",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-expiration.md",description:"Require expiration claim (exp) or expiresIn option in JWT signing",cwe:"CWE-613",cvss:5.4},hasSuggestions:true,messages:{missingExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing JWT Expiration",cwe:"CWE-613",description:"JWT without expiration is valid forever, increasing exposure window",severity:"MEDIUM",fix:'Add expiresIn: "1h" or exp claim to payload',documentationLink:"https://tools.ietf.org/html/rfc8725"}),excessiveExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Excessive JWT Expiration",cwe:"CWE-613",description:"JWT lives {{seconds}}s, longer than the configured maxExpirationSeconds ({{max}}s)",severity:"MEDIUM",fix:"Shorten expiresIn / exp to at most {{max}} seconds, and use a refresh token for longer sessions",documentationLink:"https://tools.ietf.org/html/rfc8725"}),addExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Expiration",description:"Add expiration to limit token lifetime",severity:"LOW",fix:'jwt.sign(payload, secret, { expiresIn: "1h" })',documentationLink:"https://www.npmjs.com/package/jsonwebtoken"})},schema:[{type:"object",properties:{maxExpirationSeconds:{type:"integer",description:"Maximum allowed token lifetime in seconds. Unset (the default) enforces no limit."},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const maxExpirationSeconds=context.options[0]?.maxExpirationSeconds;const checkLifetime=(node,seconds)=>{if(maxExpirationSeconds===void 0||seconds===null||seconds<=maxExpirationSeconds){return}context.report({node,messageId:"excessiveExpiration",data:{seconds:String(seconds),max:String(maxExpirationSeconds)}})};const optionsLifetime=options=>(0,lifetime_1.expiresInSeconds)((0,utils_1.getOptionValue)(options,"expiresIn"),sourceCode);const payloadHasExp=payloadNode=>{const resolved=resolvePayload(payloadNode);if(resolved===null||resolved.type!=="ObjectExpression"){return false}return(0,utils_1.hasOption)(resolved,"exp")};const resolvePayload=payloadNode=>payloadNode.type==="Identifier"?resolveObjectLiteral(payloadNode):payloadNode;const resolveObjectLiteral=node=>{let variable=null;let scope=context.sourceCode.getScope(node);while(scope!==null&&variable===null){variable=scope.variables.find(candidate=>candidate.name===node.name)??null;scope=scope.upper}if(variable===null||variable.defs.length!==1){return null}const definition=variable.defs[0];return definition.type==="Variable"?definition.node.init??null:null};const sourceCode=context.sourceCode;const JWS_ONLY_BUILDERS=new Set(["FlattenedSign","CompactSign","GeneralSign"]);return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}if(node.arguments.length<1){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);if(chain!==null){if(JWS_ONLY_BUILDERS.has(chain.kind)||chain.calls.has("setExpirationTime")){return}const claims=chain.builder.arguments[0];if(claims!==void 0&&payloadHasExp(claims)){return}}else{const payloadArg=node.arguments[0];if(payloadHasExp(payloadArg)){const exp=(0,utils_1.getOptionValue)(resolvePayload(payloadArg),"exp");checkLifetime(node,(0,lifetime_1.expClaimSeconds)(exp,sourceCode));return}const nestShaped=(0,utils_1.isNestJwtShape)(node,sourceCode)||node.arguments.length===1&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression;if(nestShaped){const own=(0,utils_1.resolveObject)(node.arguments[1],sourceCode);if(own!==null&&(0,utils_1.hasOption)(own,"expiresIn")){checkLifetime(node,optionsLifetime(own));return}if(own?.opaque===true||(0,nest_modules_1.nestModuleExpiry)(context.filename,context.languageOptions.parser)!=="missing"){return}}else{const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null&&(0,utils_1.hasOption)(options,"expiresIn")){checkLifetime(node,optionsLifetime(options));return}if(options?.opaque===true){return}}}const optionsArg3=node.arguments[(0,utils_1.isNestJwtShape)(node,sourceCode)?1:2];context.report({node,messageId:"missingExpiration",suggest:[{messageId:"addExpiration",fix(fixer){const sourceCode2=context.sourceCode;if(optionsArg3&&optionsArg3.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){const openBrace=sourceCode2.getFirstToken(optionsArg3);return fixer.insertTextAfter(openBrace," expiresIn: '1h',")}const lastArg=node.arguments[node.arguments.length-1];return fixer.insertTextAfter(lastArg,", { expiresIn: '1h' }")}}]})}}}});exports.default=exports.requireExpiration;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireExpiration=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const lifetime_1=require("../../utils/lifetime");const nest_modules_1=require("../../utils/nest-modules");exports.requireExpiration=(0,eslint_devkit_1.createRule)({name:"require-expiration",skipTestFiles:true,meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-expiration.md",description:"Require expiration claim (exp) or expiresIn option in JWT signing",cwe:"CWE-613",cvss:5.4},hasSuggestions:true,messages:{missingExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing JWT Expiration",cwe:"CWE-613",description:"JWT without expiration is valid forever, increasing exposure window",severity:"MEDIUM",fix:'Add expiresIn: "1h" or exp claim to payload',documentationLink:"https://tools.ietf.org/html/rfc8725"}),excessiveExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Excessive JWT Expiration",cwe:"CWE-613",description:"JWT lives {{seconds}}s, longer than the configured maxExpirationSeconds ({{max}}s)",severity:"MEDIUM",fix:"Shorten expiresIn / exp to at most {{max}} seconds, and use a refresh token for longer sessions",documentationLink:"https://tools.ietf.org/html/rfc8725"}),addExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Add Expiration",description:"Add expiration to limit token lifetime",severity:"LOW",fix:'jwt.sign(payload, secret, { expiresIn: "1h" })',documentationLink:"https://www.npmjs.com/package/jsonwebtoken"})},schema:[{type:"object",properties:{maxExpirationSeconds:{type:"integer",description:"Maximum allowed token lifetime in seconds. Unset (the default) enforces no limit."},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let maxExpirationSeconds;let sourceCode;let JWS_ONLY_BUILDERS;const checkLifetime=(node,seconds)=>{if(maxExpirationSeconds===void 0||seconds===null||seconds<=maxExpirationSeconds){return}context.report({node,messageId:"excessiveExpiration",data:{seconds:String(seconds),max:String(maxExpirationSeconds)}})};const optionsLifetime=options=>(0,lifetime_1.expiresInSeconds)((0,utils_1.getOptionValue)(options,"expiresIn"),sourceCode);const payloadHasExp=payloadNode=>{const resolved=resolvePayload(payloadNode);if(resolved===null||resolved.type!=="ObjectExpression"){return false}return(0,utils_1.hasOption)(resolved,"exp")};const resolvePayload=payloadNode=>payloadNode.type==="Identifier"?resolveObjectLiteral(payloadNode):payloadNode;const resolveObjectLiteral=node=>{let variable=null;let scope=context.sourceCode.getScope(node);while(scope!==null&&variable===null){variable=scope.variables.find(candidate=>candidate.name===node.name)??null;scope=scope.upper}if(variable===null||variable.defs.length!==1){return null}const definition=variable.defs[0];return definition.type==="Variable"?definition.node.init??null:null};return{before(){maxExpirationSeconds=context.options[0]?.maxExpirationSeconds;sourceCode=context.sourceCode;JWS_ONLY_BUILDERS=new Set(["FlattenedSign","CompactSign","GeneralSign"])},CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}if(node.arguments.length<1){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);if(chain!==null){if(JWS_ONLY_BUILDERS.has(chain.kind)||chain.calls.has("setExpirationTime")){return}const claims=chain.builder.arguments[0];if(claims!==void 0&&payloadHasExp(claims)){return}}else{const payloadArg=node.arguments[0];if(payloadHasExp(payloadArg)){const exp=(0,utils_1.getOptionValue)(resolvePayload(payloadArg),"exp");checkLifetime(node,(0,lifetime_1.expClaimSeconds)(exp,sourceCode));return}const nestShaped=(0,utils_1.isNestJwtShape)(node,sourceCode)||node.arguments.length===1&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression;if(nestShaped){const own=(0,utils_1.resolveObject)(node.arguments[1],sourceCode);if(own!==null&&(0,utils_1.hasOption)(own,"expiresIn")){checkLifetime(node,optionsLifetime(own));return}if(own?.opaque===true||(0,nest_modules_1.nestModuleExpiry)(context.filename,context.languageOptions.parser)!=="missing"){return}}else{const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null&&(0,utils_1.hasOption)(options,"expiresIn")){checkLifetime(node,optionsLifetime(options));return}if(options?.opaque===true){return}}}const optionsArg3=node.arguments[(0,utils_1.isNestJwtShape)(node,sourceCode)?1:2];context.report({node,messageId:"missingExpiration",suggest:[{messageId:"addExpiration",fix(fixer){const sourceCode2=context.sourceCode;if(optionsArg3&&optionsArg3.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){const openBrace=sourceCode2.getFirstToken(optionsArg3);return fixer.insertTextAfter(openBrace," expiresIn: '1h',")}const lastArg=node.arguments[node.arguments.length-1];return fixer.insertTextAfter(lastArg,", { expiresIn: '1h' }")}}]})}}}});exports.default=exports.requireExpiration;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireIssuedAt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireIssuedAt=(0,eslint_devkit_1.createRule)({name:"require-issued-at",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issued-at.md",description:"Require iat (issued at) claim for token freshness validation",cwe:"CWE-294",cvss:5},fixable:void 0,hasSuggestions:false,messages:{missingIssuedAt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Issued At Claim",cwe:"CWE-294",description:"JWT without iat claim cannot be validated for freshness, enabling replay attacks",severity:"MEDIUM",fix:"Add iat claim to payload or use library option that adds it automatically",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;const claimsHaveIat=claims=>{const resolved=(0,utils_1.resolveObject)(claims,sourceCode);return resolved!==null&&(resolved.opaque||(0,utils_1.hasOption)(resolved,"iat"))};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);if(chain!==null){if(chain.kind==="SignJWT"&&!chain.calls.has("setIssuedAt")&&!claimsHaveIat(chain.builder.arguments[0])){context.report({node,messageId:"missingIssuedAt"})}return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){return}const noTimestamp=(0,utils_1.getOptionValue)(options,"noTimestamp");if(noTimestamp!==void 0&&!(noTimestamp.type==="Literal"&&typeof noTimestamp.value==="boolean")){context.report({node,messageId:"missingIssuedAt"})}}}}});exports.default=exports.requireIssuedAt;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireIssuedAt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireIssuedAt=(0,eslint_devkit_1.createRule)({name:"require-issued-at",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issued-at.md",description:"Require iat (issued at) claim for token freshness validation",cwe:"CWE-294",cvss:5},fixable:void 0,hasSuggestions:false,messages:{missingIssuedAt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Issued At Claim",cwe:"CWE-294",description:"JWT without iat claim cannot be validated for freshness, enabling replay attacks",severity:"MEDIUM",fix:"Add iat claim to payload or use library option that adds it automatically",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;const claimsHaveIat=claims=>{const resolved=(0,utils_1.resolveObject)(claims,sourceCode);return resolved!==null&&(resolved.opaque||(0,utils_1.hasOption)(resolved,"iat"))};return{before(){sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);if(chain!==null){if(chain.kind==="SignJWT"&&!chain.calls.has("setIssuedAt")&&!claimsHaveIat(chain.builder.arguments[0])){context.report({node,messageId:"missingIssuedAt"})}return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){return}const noTimestamp=(0,utils_1.getOptionValue)(options,"noTimestamp");if(noTimestamp!==void 0&&!(noTimestamp.type==="Literal"&&typeof noTimestamp.value==="boolean")){context.report({node,messageId:"missingIssuedAt"})}}}}});exports.default=exports.requireIssuedAt;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireIssuerValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireIssuerValidation=(0,eslint_devkit_1.createRule)({name:"require-issuer-validation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issuer-validation.md",description:"Require issuer (iss) claim validation in JWT verify operations",cwe:"CWE-287",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{missingIssuerValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Issuer Validation",cwe:"CWE-287",description:"JWT verification without issuer validation accepts tokens from any source",severity:"MEDIUM",fix:'Add issuer option: { issuer: "https://auth.example.com" }',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{knownIssuers:{type:"array",items:{type:"string"},default:[],description:"Known valid issuers to suggest"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{knownIssuers:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingIssuerValidation"});return}if(!(0,utils_1.hasOption)(options,"issuer")&&!(0,utils_1.hasOption)(options,"iss")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingIssuerValidation"})}}}}});exports.default=exports.requireIssuerValidation;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireIssuerValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireIssuerValidation=(0,eslint_devkit_1.createRule)({name:"require-issuer-validation",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issuer-validation.md",description:"Require issuer (iss) claim validation in JWT verify operations",cwe:"CWE-287",cvss:9.8},fixable:void 0,hasSuggestions:false,messages:{missingIssuerValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Issuer Validation",cwe:"CWE-287",description:"JWT verification without issuer validation accepts tokens from any source",severity:"MEDIUM",fix:'Add issuer option: { issuer: "https://auth.example.com" }',documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{knownIssuers:{type:"array",items:{type:"string"},default:[],description:"Known valid issuers to suggest"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{knownIssuers:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;return{before(){sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingIssuerValidation"});return}if(!(0,utils_1.hasOption)(options,"issuer")&&!(0,utils_1.hasOption)(options,"iss")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingIssuerValidation"})}}}}});exports.default=exports.requireIssuerValidation;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxAge=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireMaxAge=(0,eslint_devkit_1.createRule)({name:"require-max-age",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-max-age.md",description:"Require maxAge option in verify operations to enforce token freshness",cwe:"CWE-294",cvss:5},fixable:void 0,hasSuggestions:false,messages:{missingMaxAge:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Token Age Validation",cwe:"CWE-294",description:"Without maxAge, tokens can be replayed long after issuance (Back to the Future attack)",severity:"MEDIUM",fix:'Add maxAge option: { maxAge: "1h" } to limit token lifetime',documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingMaxAge"});return}if(!(0,utils_1.hasOption)(options,"maxAge")&&!(0,utils_1.hasOption)(options,"maxTokenAge")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingMaxAge"})}}}}});exports.default=exports.requireMaxAge;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxAge=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireMaxAge=(0,eslint_devkit_1.createRule)({name:"require-max-age",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-max-age.md",description:"Require maxAge option in verify operations to enforce token freshness",cwe:"CWE-294",cvss:5},fixable:void 0,hasSuggestions:false,messages:{missingMaxAge:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Token Age Validation",cwe:"CWE-294",description:"Without maxAge, tokens can be replayed long after issuance (Back to the Future attack)",severity:"MEDIUM",fix:'Add maxAge option: { maxAge: "1h" } to limit token lifetime',documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],createOnce(context){let sourceCode;return{before(){sourceCode=context.sourceCode},CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options===null){context.report({node,messageId:"missingMaxAge"});return}if(!(0,utils_1.hasOption)(options,"maxAge")&&!(0,utils_1.hasOption)(options,"maxTokenAge")&&!options.opaque){context.report({node:node.arguments[2],messageId:"missingMaxAge"})}}}}});exports.default=exports.requireMaxAge;