eslint-plugin-jwt-security 3.3.0 → 3.4.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
@@ -155,6 +155,7 @@ Part of the **Interlace ESLint ecosystem** — AI-native rules with LLM-optimize
155
155
  | [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-secure-coding.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-secure-coding) | Injection prevention. |
156
156
  | [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-sequelize-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-sequelize-security) | Sequelize ORM security. |
157
157
  | [`eslint-plugin-sqlite-security`](https://www.npmjs.com/package/eslint-plugin-sqlite-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-sqlite-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-sqlite-security) | SQLite security. |
158
+ | [`eslint-plugin-supabase-security`](https://www.npmjs.com/package/eslint-plugin-supabase-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-supabase-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-supabase-security) | Supabase security. |
158
159
  | [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-typeorm-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-typeorm-security) | TypeORM security. |
159
160
  | [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-vercel-ai-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | AI SDK security. |
160
161
 
@@ -162,6 +163,7 @@ Part of the **Interlace ESLint ecosystem** — AI-native rules with LLM-optimize
162
163
 
163
164
  | Plugin | Downloads | Description |
164
165
  | :--- | :---: | :--- |
166
+ | [`eslint-plugin-cli-floor`](https://www.npmjs.com/package/eslint-plugin-cli-floor) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-cli-floor.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-cli-floor) | CLI floor for commander, yargs and burgee. |
165
167
  | [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-conventions.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-conventions) | Team-specific habits and styles. |
166
168
  | [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-import-next) | Fast cycle + import-graph analysis. |
167
169
  | [`eslint-plugin-maintainability`](https://www.npmjs.com/package/eslint-plugin-maintainability) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-maintainability.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-maintainability) | Cognitive load and clean-code patterns. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jwt-security",
3
- "version": "3.3.0",
3
+ "version": "3.4.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",
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.3.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"};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.4.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"};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");const PUBLIC_KEY_PATTERNS=[/public/i,/\.pub$/,/\.pem$/,/-----BEGIN PUBLIC KEY-----/,/-----BEGIN RSA PUBLIC KEY-----/,/-----BEGIN EC PUBLIC KEY-----/,/getPublicKey/i,/publicKey/i,/jwks/i];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 looksLikePublicKey=node=>{const text=sourceCode.getText(node);return PUBLIC_KEY_PATTERNS.some(pattern=>pattern.test(text))};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)){return}if(node.arguments.length<2){return}const keyArg=node.arguments[1];const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!looksLikePublicKey(keyArg)){return}if(optionsArg){const algorithms=(0,utils_1.extractAlgorithms)(optionsArg);const symmetricAlg=hasSymmetricAlgorithm(algorithms);if(symmetricAlg){for(const prop of optionsArg.properties){if(prop.type==="Property"&&["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");const PUBLIC_KEY_PATTERNS=[/public/i,/\.pub$/,/\.pem$/,/-----BEGIN PUBLIC KEY-----/,/-----BEGIN RSA PUBLIC KEY-----/,/-----BEGIN EC PUBLIC KEY-----/,/getPublicKey/i,/publicKey/i,/jwks/i];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 looksLikePublicKey=node=>{const text=sourceCode.getText(node);return PUBLIC_KEY_PATTERNS.some(pattern=>pattern.test(text))};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&&!looksLikePublicKey(node.arguments[1])){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 +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 checkOptionsForNone=optionsNode=>{for(const prop of optionsNode.properties){if(prop.type!=="Property"){continue}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"})}}}}};return{CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node)&&!(0,utils_1.isSignOperation)(node)){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(optionsArg){checkOptionsForNone(optionsArg)}}}}});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:{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 +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");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:["@decoded-header-only","@verified-separately"],strictMode:false}],create(context){const options=context.options[0]??{};const{trustedAnnotations=[]}=options;const isJwtDecodeLibrary=node=>{if(node.callee.type==="Identifier"&&(node.callee.name==="jwtDecode"||node.callee.name==="jwt_decode")){return true}return false};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 readsOnlyTimeClaims=node=>{const outer=skipTypeWrappers(node);const parent=outer.parent;if(parent.type==="MemberExpression"&&parent.object===outer){return(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(parent),TIME_CLAIMS)}if(parent.type!=="VariableDeclarator"||parent.id.type!=="Identifier"){return false}const[variable]=context.sourceCode.getDeclaredVariables(parent);let sawTimeClaim=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 isTimeClaimRead=useParent.type==="MemberExpression"&&useParent.object===use&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(useParent),TIME_CLAIMS);if(isTimeClaimRead){sawTimeClaim=true}return isTimeClaimRead});return allUsesAllowed&&sawTimeClaim};const GRANT_RESPONSE_MEMBERS=new Set(["access_token","id_token","refresh_token"]);const REQUEST_RECEIVER=/\b(?:req|reqs|request|body|query|params|headers|header|cookies|cookie|searchParams|payload|input)\b/i;const MAX_PROVENANCE_DEPTH=4;const lookupVariable=(startScope,name)=>{for(let scope=startScope;scope!=null;scope=scope.upper){const found=scope.set.get(name);if(found)return found}return null};const tracesToGrantResponse=(node,depth)=>{if(depth>MAX_PROVENANCE_DEPTH)return false;if(node.type==="TSAsExpression"||node.type==="TSNonNullExpression"){return tracesToGrantResponse(node.expression,depth+1)}if(node.type==="MemberExpression"){if(node.computed||node.property.type!=="Identifier")return false;if(!GRANT_RESPONSE_MEMBERS.has(node.property.name))return false;return!REQUEST_RECEIVER.test(context.sourceCode.getText(node.object))}if(node.type!=="Identifier")return false;const variable=lookupVariable(context.sourceCode.getScope(node),node.name);const[definition]=variable?.defs??[];if(definition===void 0)return false;if(definition.type==="Variable"){const{init}=definition.node;return init!=null&&tracesToGrantResponse(init,depth+1)}if(definition.type!=="Parameter")return false;return everyCallSitePassesGrantResponse(definition,depth)};const everyCallSitePassesGrantResponse=(definition,depth)=>{const fn=definition.node;const index=fn.params.indexOf(definition.name);if(index<0||fn.id==null)return false;const fnVariable=lookupVariable(context.sourceCode.getScope(fn),fn.id.name);const references=fnVariable.references;let sawCall=false;for(const reference of references){const parent=reference.identifier.parent;if(parent?.type!=="CallExpression"||parent.callee!==reference.identifier){continue}sawCall=true;const argument=parent.arguments[index];if(argument===void 0||!tracesToGrantResponse(argument,depth+1)){return false}}return sawCall};return{CallExpression(node){if((0,utils_1.isDecodeOperation)(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyTimeClaims(node)){return}const[tokenArgument]=node.arguments;if(tokenArgument!==void 0&&tracesToGrantResponse(tokenArgument,0)){return}context.report({node,messageId:"decodeWithoutVerify"});return}if(isJwtDecodeLibrary(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){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");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:["@decoded-header-only","@verified-separately"],strictMode:false}],create(context){const options=context.options[0]??{};const{trustedAnnotations=[],allowHeaderInspection=false}=options;const sourceCode=context.sourceCode;const isJwtDecodeLibrary=node=>{if(node.callee.type==="Identifier"&&(node.callee.name==="jwtDecode"||node.callee.name==="jwt_decode")){return true}return false};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=>{const name=(0,eslint_devkit_1.propertyName)(member);if((0,eslint_devkit_1.namesOneOf)(name,TIME_CLAIMS))return true;if(name!=="header")return false;if(allowHeaderInspection)return true;const next=member.parent;return next.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(next)==="kid"};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=>{const outer=skipTypeWrappers(node);const parent=outer.parent;if(parent.type==="MemberExpression"&&parent.object===outer){return isAllowedRead(parent)}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);if(isAllowed){sawAllowedRead=true}return isAllowed});return allUsesAllowed&&sawAllowedRead};const GRANT_RESPONSE_MEMBERS=new Set(["access_token","id_token","refresh_token"]);const REQUEST_RECEIVER=/\b(?:req|reqs|request|body|query|params|headers|header|cookies|cookie|searchParams|payload|input)\b/i;const MAX_PROVENANCE_DEPTH=4;const lookupVariable=(startScope,name)=>{for(let scope=startScope;scope!=null;scope=scope.upper){const found=scope.set.get(name);if(found)return found}return null};const tracesToGrantResponse=(node,depth)=>{if(depth>MAX_PROVENANCE_DEPTH)return false;if(node.type==="TSAsExpression"||node.type==="TSNonNullExpression"){return tracesToGrantResponse(node.expression,depth+1)}if(node.type==="MemberExpression"){if(node.computed||node.property.type!=="Identifier")return false;if(!GRANT_RESPONSE_MEMBERS.has(node.property.name))return false;return!REQUEST_RECEIVER.test(context.sourceCode.getText(node.object))}if(node.type!=="Identifier")return false;const variable=lookupVariable(context.sourceCode.getScope(node),node.name);const[definition]=variable?.defs??[];if(definition===void 0)return false;if(definition.type==="Variable"){const{init}=definition.node;return init!=null&&tracesToGrantResponse(init,depth+1)}if(definition.type!=="Parameter")return false;return everyCallSitePassesGrantResponse(definition,depth)};const everyCallSitePassesGrantResponse=(definition,depth)=>{const fn=definition.node;const index=fn.params.indexOf(definition.name);if(index<0||fn.id==null)return false;const fnVariable=lookupVariable(context.sourceCode.getScope(fn),fn.id.name);const references=fnVariable.references;let sawCall=false;for(const reference of references){const parent=reference.identifier.parent;if(parent?.type!=="CallExpression"||parent.callee!==reference.identifier){continue}sawCall=true;const argument=parent.arguments[index];if(argument===void 0||!tracesToGrantResponse(argument,depth+1)){return false}}return sawCall};return{CallExpression(node){if((0,utils_1.isDecodeOperation)(node,sourceCode)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}if(readsOnlyAllowedClaims(node)){return}const[tokenArgument]=node.arguments;if(tokenArgument!==void 0&&tracesToGrantResponse(tokenArgument,0)){return}context.report({node,messageId:"decodeWithoutVerify"});return}if(isJwtDecodeLibrary(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){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",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 isHardcodedString=node=>(0,eslint_devkit_1.staticString)(node)!==null;const resolveConstLiteralValue=node=>{if(node.type!=="Identifier")return null;const scope=context.sourceCode.getScope(node);const variable=scope.references.find(r=>r.identifier===node)?.resolved;const def=variable?.defs[0];if(!def||def.type!=="Variable")return null;const decl=def.parent;if(decl?.type!=="VariableDeclaration"||decl.kind!=="const")return null;return def.node.init??null};const isSafeKeySource=node=>{if((0,utils_1.isEnvVariable)(node)){return true}if(node.type==="CallExpression"){const inner=(0,utils_1.byteKeyLiteral)(node);return inner===null||!isHardcodedStringOrResolvedConst(inner.literal)}if(node.type==="AwaitExpression"){return true}if(node.type==="Identifier"){const init=resolveConstLiteralValue(node);if(init&&isHardcodedString(init)){return false}return true}if(node.type==="MemberExpression"&&!isHardcodedString(node)){return true}return false};const isHardcodedStringOrResolvedConst=node=>{if(isHardcodedString(node))return true;const init=resolveConstLiteralValue(node);return init?isHardcodedString(init):false};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)&&!(0,utils_1.isSignatureVerifyOperation)(node)){return}if(node.arguments.length<2){return}const secretArg=node.arguments[1];if(isSafeKeySource(secretArg)){return}const literal=(0,utils_1.byteKeyLiteral)(secretArg)?.literal??secretArg;if(isHardcodedStringOrResolvedConst(literal)){context.report({node:secretArg,messageId:"hardcodedSecret"})}}}}});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:{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 +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");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"})},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]);return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}if(node.arguments.length<1){return}const payloadArg=node.arguments[0];if(payloadArg.type==="ObjectExpression"){for(const prop of payloadArg.properties){const keyText=prop.type==="Property"?(0,eslint_devkit_1.objectKeyName)(prop):null;if(keyText!==null){if(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");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"})},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;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 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");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"})},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){return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){return}if((0,utils_1.hasOption)(optionsArg,"noTimestamp")){const value=(0,utils_1.getOptionValue)(optionsArg,"noTimestamp");if(value?.type==="Literal"&&value.value===true){context.report({node:value,messageId:"noTimestampTrue"})}}}}}});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");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"})},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 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")}}},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",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 isKnownWeakPattern=value=>{return WEAK_SECRET_PATTERNS.some(pattern=>pattern.test(value))};const checkSecret=(secretNode,encoding)=>{if((0,utils_1.isEnvVariable)(secretNode)){return}const bytes=(0,utils_1.byteKeyLiteral)(secretNode);if(bytes!==null){checkSecret(bytes.literal,bytes.encoding);return}if(secretNode.type==="Literal"&&typeof secretNode.value==="string"){const secretValue=secretNode.value;if(isKnownWeakPattern(secretValue)){context.report({node:secretNode,messageId:"weakSecret"});return}if(decodedByteLength(secretValue,encoding)<minSecretLength){context.report({node:secretNode,messageId:"shortSecret",data:{length:String(secretValue.length),minLength:String(minSecretLength)}})}}if(secretNode.type==="TemplateLiteral"&&secretNode.quasis.length===1){const rawValue=secretNode.quasis[0].value.raw;if(isKnownWeakPattern(rawValue)||rawValue.length<minSecretLength){context.report({node:secretNode,messageId:"weakSecret"})}}};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)&&!(0,utils_1.isSignatureVerifyOperation)(node)){return}if(node.arguments.length>=2){checkSecret(node.arguments[1])}}}}});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:{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 +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){return{CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node)){return}if(node.arguments.length<2){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){context.report({node,messageId:"missingAlgorithmWhitelist"});return}const hasAlgorithms=(0,utils_1.hasOption)(optionsArg,"algorithms");if(!hasAlgorithms){context.report({node:optionsArg,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:{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 +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){return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length<2){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){context.report({node,messageId:"missingAudienceValidation"});return}if(!(0,utils_1.hasOption)(optionsArg,"audience")&&!(0,utils_1.hasOption)(optionsArg,"aud")){context.report({node:optionsArg,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:{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 +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");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"}),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",default:86400,description:"Maximum allowed expiration time in seconds (24h default)"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{maxExpirationSeconds:86400,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const payloadHasExp=payloadNode=>{const resolved=payloadNode.type==="Identifier"?resolveObjectLiteral(payloadNode):payloadNode;if(resolved===null||resolved.type!=="ObjectExpression"){return false}return resolved.properties.some(prop=>prop.type==="Property"&&!prop.computed&&(prop.key.type==="Identifier"&&prop.key.name==="exp"||prop.key.type==="Literal"&&prop.key.value==="exp"))};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 JWS_ONLY_BUILDERS=new Set(["FlattenedSign","CompactSign","GeneralSign"]);const chainSetsExpiration=node=>{let current=node;while(current.type==="CallExpression"||current.type==="MemberExpression"){if(current.type==="CallExpression"&&current.callee.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(current.callee)==="setExpirationTime"){return true}current=current.type==="CallExpression"?current.callee:current.object}if(current.type!=="NewExpression"){return false}const ctor=current.callee.type==="Identifier"?current.callee.name:current.callee.type==="MemberExpression"&&current.callee.property.type==="Identifier"?current.callee.property.name:null;if(ctor!==null&&JWS_ONLY_BUILDERS.has(ctor)){return true}const claims=current.arguments[0];return claims!==void 0&&payloadHasExp(claims)};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}if(node.arguments.length<1){return}const payloadArg=node.arguments[0];const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(payloadHasExp(payloadArg)){return}if(chainSetsExpiration(node)){return}if(optionsArg&&(0,utils_1.hasOption)(optionsArg,"expiresIn")){return}const optionsArg3=node.arguments[2];context.report({node,messageId:"missingExpiration",suggest:[{messageId:"addExpiration",fix(fixer){const sourceCode=context.sourceCode;if(optionsArg3&&optionsArg3.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){const openBrace=sourceCode.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");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"}),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",default:86400,description:"Maximum allowed expiration time in seconds (24h default)"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{maxExpirationSeconds:86400,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const payloadHasExp=payloadNode=>{const resolved=payloadNode.type==="Identifier"?resolveObjectLiteral(payloadNode):payloadNode;if(resolved===null||resolved.type!=="ObjectExpression"){return false}return resolved.properties.some(prop=>prop.type==="Property"&&!prop.computed&&(prop.key.type==="Identifier"&&prop.key.name==="exp"||prop.key.type==="Literal"&&prop.key.value==="exp"))};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)){return}if(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){return}const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null&&((0,utils_1.hasOption)(options,"expiresIn")||options.opaque)){return}}const optionsArg3=node.arguments[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 payloadHasIat=payloadNode=>{if(payloadNode.type!=="ObjectExpression"){return false}return payloadNode.properties.some(prop=>prop.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(prop)==="iat")};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node)){return}if(node.arguments.length<1){return}const payloadArg=node.arguments[0];const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(payloadHasIat(payloadArg)){return}if(optionsArg&&(0,utils_1.hasOption)(optionsArg,"noTimestamp")){context.report({node:optionsArg,messageId:"missingIssuedAt"});return}}}}});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:{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,opaqueCounts)=>{const resolved=(0,utils_1.resolveObject)(claims,sourceCode);return resolved!==null&&(opaqueCounts&&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],true)){context.report({node,messageId:"missingIssuedAt"})}return}if(claimsHaveIat(node.arguments[0],false)){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"&&noTimestamp.value===false)){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){return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length<2){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){context.report({node,messageId:"missingIssuerValidation"});return}if(!(0,utils_1.hasOption)(optionsArg,"issuer")&&!(0,utils_1.hasOption)(optionsArg,"iss")){context.report({node:optionsArg,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:{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 +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){return{CallExpression(node){if(!(0,utils_1.isVerifyOperation)(node)){return}if(node.arguments.length<2){return}const optionsArg=(0,utils_1.getOptionsArgument)(node,2);if(!optionsArg){context.report({node,messageId:"missingMaxAge"});return}if(!(0,utils_1.hasOption)(optionsArg,"maxAge")&&!(0,utils_1.hasOption)(optionsArg,"clockTolerance")){context.report({node:optionsArg,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:{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 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.JWT_METHODS=exports.SENSITIVE_PAYLOAD_FIELDS=exports.SECURE_ALGORITHMS=exports.SYMMETRIC_ALGORITHMS=exports.INSECURE_ALGORITHMS=exports.JWT_LIBRARIES=void 0;exports.isJwtLibraryCall=isJwtLibraryCall;exports.isStringLiteral=isStringLiteral;exports.extractAlgorithms=extractAlgorithms;exports.hasOption=hasOption;exports.getOptionValue=getOptionValue;exports.isWeakSecret=isWeakSecret;exports.byteKeyLiteral=byteKeyLiteral;exports.isEnvVariable=isEnvVariable;exports.isSignOperation=isSignOperation;exports.isVerifyOperation=isVerifyOperation;exports.isSignatureVerifyOperation=isSignatureVerifyOperation;exports.isDecodeOperation=isDecodeOperation;exports.getOptionsArgument=getOptionsArgument;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.JWT_LIBRARIES={JSONWEBTOKEN:"jsonwebtoken",JOSE:"jose",EXPRESS_JWT:"express-jwt",NESTJS_JWT:"@nestjs/jwt",JWKS_RSA:"jwks-rsa",JWT_DECODE:"jwt-decode"};exports.INSECURE_ALGORITHMS=new Set(["none","None","NONE","HS256","HS384","HS512"]);exports.SYMMETRIC_ALGORITHMS=new Set(["HS256","HS384","HS512"]);exports.SECURE_ALGORITHMS=new Set(["RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","EdDSA"]);exports.SENSITIVE_PAYLOAD_FIELDS=new Set(["password","passwd","pwd","pass","secret","apikey","api_key","api-key","apisecret","api_secret","api-secret","accesstoken","access_token","access-token","refreshtoken","refresh_token","refresh-token","bearertoken","bearer_token","bearer-token","privatekey","private_key","private-key","secretkey","secret_key","secret-key","email","emailaddress","email_address","email-address","phone","phonenumber","phone_number","phone-number","ssn","socialsecuritynumber","social_security_number","dob","dateofbirth","date_of_birth","birthdate","address","streetaddress","street_address","creditcard","credit_card","credit-card","cardnumber","card_number","card-number","cvv","cvc","securitycode","security_code","pin","pincode","pin_code","bankaccount","bank_account","bank-account","accountnumber","account_number","account-number","routingnumber","routing_number","routing-number"]);exports.JWT_METHODS={SIGN:new Set(["sign","signJWT","SignJWT"]),VERIFY:new Set(["verify","verifyJWT","jwtVerify"]),DECODE:new Set(["decode","jwtDecode","decodeJWT","decodeJwt"]),JWS_VERIFY:new Set(["compactVerify","flattenedVerify","generalVerify"])};const SIGNATURE_VERIFY=new Set([...exports.JWT_METHODS.VERIFY,...exports.JWT_METHODS.JWS_VERIFY]);const JWT_LIBRARY_ROOTS=new Set(Object.values(exports.JWT_LIBRARIES));function importEqualsSpecifierOf(stmt){const ref=stmt.moduleReference;if(ref.type!==eslint_devkit_1.AST_NODE_TYPES.TSExternalModuleReference)return null;return String(ref.expression.value)}function requireSpecifierOf(node){if(node==null)return null;const call=node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?node.object:node;if(call.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||call.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||call.callee.name!=="require"){return null}const[arg]=call.arguments;return arg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof arg.value==="string"?arg.value:null}function packageRootOf(rawSource){const source=denormalizeDenoSpecifier(rawSource);const parts=source.split("/");return source.startsWith("@")?parts.slice(0,2).join("/"):parts[0]}function denormalizeDenoSpecifier(source){if(source.startsWith("npm:"))return source.slice(4);const deno=/^https?:\/\/deno\.land\/x\/([^@/]+)/.exec(source);return deno?deno[1]:source}const fileUsesJwtLibrary=(0,eslint_devkit_1.createModuleEvidence)({packages:Object.values(exports.JWT_LIBRARIES)});function fileImportsJwtLibrary(node){let root=node;while(root.parent)root=root.parent;if(root.type!=="Program")return false;return fileUsesJwtLibrary(root)}function receiverIsForeignImport(node){if(node.callee.type!=="MemberExpression")return false;let object=node.callee.object;while(object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)object=object.object;if(object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;let root=node;while(root.parent)root=root.parent;for(const stmt of root.body){const source=bindingSourceOf(stmt,object.name);if(source===null)continue;return!JWT_LIBRARY_ROOTS.has(packageRootOf(source))}return false}function bindingSourceOf(stmt,name){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration){const binds=(stmt.specifiers??[]).some(spec=>spec.local?.name===name);return binds&&typeof stmt.source.value==="string"?stmt.source.value:null}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.TSImportEqualsDeclaration){return stmt.id.name===name?importEqualsSpecifierOf(stmt):null}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of stmt.declarations){if(!patternBinds(declarator.id,name))continue;const source=requireSpecifierOf(declarator.init);if(source!==null)return source}}return null}function patternBinds(id,name){if(id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return id.name===name;if(id.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){return id.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&prop.value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&prop.value.name===name)}return false}const NON_JWT_CONSTRUCTORS=new Set(["TextDecoder","TextEncoder","URL","URLSearchParams","Buffer","Uint8Array","Response","Request","Headers"]);function receiverIsForeignConstruction(receiver){return receiver.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&receiver.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&NON_JWT_CONSTRUCTORS.has(receiver.callee.name)}function calleeIsForeign(node){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=node.callee.name;let root=node;while(root.parent)root=root.parent;for(const stmt of root.body){const declaration=stmt.type===eslint_devkit_1.AST_NODE_TYPES.ExportNamedDeclaration||stmt.type===eslint_devkit_1.AST_NODE_TYPES.ExportDefaultDeclaration?stmt.declaration??stmt:stmt;if(declaration.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration){if(declaration.id!==null&&declaration.id.name===name)return true;continue}const source=bindingSourceOf(declaration,name);if(source!==null)return!JWT_LIBRARY_ROOTS.has(packageRootOf(source));if(declaration.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration&&declaration.declarations.some(declarator=>patternBinds(declarator.id,name))){return true}}return false}function isJwtLibraryCall(node,targetMethods){if(!fileImportsJwtLibrary(node)){return false}if(receiverIsForeignImport(node)){return false}if(calleeIsForeign(node)){return false}if(node.callee.type==="MemberExpression"){if(receiverIsForeignConstruction(node.callee.object)){return false}const method=(0,eslint_devkit_1.propertyName)(node.callee);if(method!==null){return targetMethods.has(method)}}if(node.callee.type==="Identifier"){return targetMethods.has(node.callee.name)}return false}function isStringLiteral(node,value){if(node.type!=="Literal"||typeof node.value!=="string"){return false}return value===void 0||node.value===value}function extractAlgorithms(optionsNode){const algorithms=[];for(const prop of optionsNode.properties){if(prop.type!=="Property"){continue}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"){algorithms.push(prop.value.value)}if(prop.value.type==="ArrayExpression"){for(const elem of prop.value.elements){if(elem&&elem.type==="Literal"&&typeof elem.value==="string"){algorithms.push(elem.value)}}}}return algorithms}function hasOption(optionsNode,optionName){return optionsNode.properties.some(prop=>prop.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(prop)===optionName)}function getOptionValue(optionsNode,optionName){for(const prop of optionsNode.properties){if(prop.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(prop)===optionName){return prop.value}}return void 0}function isWeakSecret(node,minLength=32){if(node.type==="Literal"&&typeof node.value==="string"){return node.value.length<minLength}return false}function byteKeyLiteral(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return null;const arg=node.arguments[0];if(arg===void 0)return null;const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;const method=(0,eslint_devkit_1.propertyName)(callee);if(method==="encode"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&callee.object.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.callee.name==="TextEncoder"){return{literal:arg}}if(method==="from"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"){const encodingArg=node.arguments[1];return{literal:arg,encoding:encodingArg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof encodingArg.value==="string"?encodingArg.value.toLowerCase():void 0}}return null}function isEnvVariable(node){if(node.type==="MemberExpression"&&node.object.type==="MemberExpression"&&node.object.object.type==="Identifier"&&node.object.object.name==="process"&&(0,eslint_devkit_1.propertyName)(node.object)==="env"){return true}return false}function isSignOperation(node){return isJwtLibraryCall(node,exports.JWT_METHODS.SIGN)}function isVerifyOperation(node){return isJwtLibraryCall(node,exports.JWT_METHODS.VERIFY)}function isSignatureVerifyOperation(node){return isJwtLibraryCall(node,SIGNATURE_VERIFY)}function isDecodeOperation(node){return isJwtLibraryCall(node,exports.JWT_METHODS.DECODE)}function getOptionsArgument(node,optionsIndex=2){const arg=node.arguments[optionsIndex];if(arg&&arg.type==="ObjectExpression"){return arg}return void 0}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.JWT_METHODS=exports.SENSITIVE_PAYLOAD_FIELDS=exports.SECURE_ALGORITHMS=exports.SYMMETRIC_ALGORITHMS=exports.INSECURE_ALGORITHMS=exports.JWT_LIBRARIES=void 0;exports.isJwtLibraryCall=isJwtLibraryCall;exports.isStringLiteral=isStringLiteral;exports.extractAlgorithms=extractAlgorithms;exports.hasOption=hasOption;exports.getOptionValue=getOptionValue;exports.isWeakSecret=isWeakSecret;exports.byteKeyLiteral=byteKeyLiteral;exports.isEnvVariable=isEnvVariable;exports.isSignOperation=isSignOperation;exports.isVerifyOperation=isVerifyOperation;exports.isSignatureVerifyOperation=isSignatureVerifyOperation;exports.isDecodeOperation=isDecodeOperation;exports.resolveObject=resolveObject;exports.isNestJwtShape=isNestJwtShape;exports.resolveCallOptions=resolveCallOptions;exports.joseBuilderChain=joseBuilderChain;exports.keyNodesOf=keyNodesOf;exports.keyLiterals=keyLiterals;exports.isPublicKeyMaterial=isPublicKeyMaterial;exports.jwtConfigOf=jwtConfigOf;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.JWT_LIBRARIES={JSONWEBTOKEN:"jsonwebtoken",JOSE:"jose",EXPRESS_JWT:"express-jwt",NESTJS_JWT:"@nestjs/jwt",JWKS_RSA:"jwks-rsa",JWT_DECODE:"jwt-decode",PASSPORT_JWT:"passport-jwt",FAST_JWT:"fast-jwt"};exports.INSECURE_ALGORITHMS=new Set(["none","None","NONE","HS256","HS384","HS512"]);exports.SYMMETRIC_ALGORITHMS=new Set(["HS256","HS384","HS512"]);exports.SECURE_ALGORITHMS=new Set(["RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","EdDSA"]);exports.SENSITIVE_PAYLOAD_FIELDS=new Set(["password","passwd","pwd","pass","secret","apikey","api_key","api-key","apisecret","api_secret","api-secret","accesstoken","access_token","access-token","refreshtoken","refresh_token","refresh-token","bearertoken","bearer_token","bearer-token","privatekey","private_key","private-key","secretkey","secret_key","secret-key","email","emailaddress","email_address","email-address","phone","phonenumber","phone_number","phone-number","ssn","socialsecuritynumber","social_security_number","dob","dateofbirth","date_of_birth","birthdate","address","streetaddress","street_address","creditcard","credit_card","credit-card","cardnumber","card_number","card-number","cvv","cvc","securitycode","security_code","pin","pincode","pin_code","bankaccount","bank_account","bank-account","accountnumber","account_number","account-number","routingnumber","routing_number","routing-number","passwordhash","password_hash","hashedpassword","hashed_password","passworddigest","password_digest"]);exports.JWT_METHODS={SIGN:new Set(["sign","signJWT","SignJWT","signAsync"]),VERIFY:new Set(["verify","verifyJWT","jwtVerify","verifyAsync"]),DECODE:new Set(["decode","jwtDecode","decodeJWT","decodeJwt"]),JWS_VERIFY:new Set(["compactVerify","flattenedVerify","generalVerify"])};const SIGNATURE_VERIFY=new Set([...exports.JWT_METHODS.VERIFY,...exports.JWT_METHODS.JWS_VERIFY]);const JWT_LIBRARY_ROOTS=new Set(Object.values(exports.JWT_LIBRARIES));function importEqualsSpecifierOf(stmt){const ref=stmt.moduleReference;if(ref.type!==eslint_devkit_1.AST_NODE_TYPES.TSExternalModuleReference)return null;return String(ref.expression.value)}function requireSpecifierOf(node){if(node==null)return null;const call=node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?node.object:node;if(call.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||call.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||call.callee.name!=="require"){return null}const[arg]=call.arguments;return arg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof arg.value==="string"?arg.value:null}function packageRootOf(rawSource){const source=denormalizeDenoSpecifier(rawSource);const parts=source.split("/");return source.startsWith("@")?parts.slice(0,2).join("/"):parts[0]}function denormalizeDenoSpecifier(source){if(source.startsWith("npm:"))return source.slice(4);const deno=/^https?:\/\/deno\.land\/x\/([^@/]+)/.exec(source);return deno?deno[1]:source}const fileUsesJwtLibrary=(0,eslint_devkit_1.createModuleEvidence)({packages:Object.values(exports.JWT_LIBRARIES)});function fileImportsJwtLibrary(node){let root=node;while(root.parent)root=root.parent;if(root.type!=="Program")return false;return fileUsesJwtLibrary(root)}function receiverIsForeignImport(node){if(node.callee.type!=="MemberExpression")return false;let object=node.callee.object;while(object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)object=object.object;if(object.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;let root=node;while(root.parent)root=root.parent;for(const stmt of root.body){const source=bindingSourceOf(stmt,object.name);if(source===null)continue;return!JWT_LIBRARY_ROOTS.has(packageRootOf(source))}return false}function bindingSourceOf(stmt,name){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration){const binds=(stmt.specifiers??[]).some(spec=>spec.local?.name===name);return binds&&typeof stmt.source.value==="string"?stmt.source.value:null}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.TSImportEqualsDeclaration){return stmt.id.name===name?importEqualsSpecifierOf(stmt):null}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of stmt.declarations){if(!patternBinds(declarator.id,name))continue;const source=requireSpecifierOf(declarator.init);if(source!==null)return source}}return null}function patternBinds(id,name){if(id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return id.name===name;if(id.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){return id.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&prop.value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&prop.value.name===name)}return false}const NON_JWT_CONSTRUCTORS=new Set(["TextDecoder","TextEncoder","URL","URLSearchParams","Buffer","Uint8Array","Response","Request","Headers"]);function receiverIsForeignConstruction(receiver){return receiver.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&receiver.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&NON_JWT_CONSTRUCTORS.has(receiver.callee.name)}function calleeIsForeign(node){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const name=node.callee.name;let root=node;while(root.parent)root=root.parent;for(const stmt of root.body){const declaration=stmt.type===eslint_devkit_1.AST_NODE_TYPES.ExportNamedDeclaration||stmt.type===eslint_devkit_1.AST_NODE_TYPES.ExportDefaultDeclaration?stmt.declaration??stmt:stmt;if(declaration.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration){if(declaration.id!==null&&declaration.id.name===name)return true;continue}const source=bindingSourceOf(declaration,name);if(source!==null)return!JWT_LIBRARY_ROOTS.has(packageRootOf(source));if(declaration.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration&&declaration.declarations.some(declarator=>patternBinds(declarator.id,name))){return true}}return false}function isJwtLibraryCall(node,targetMethods,sourceCode){if(!fileImportsJwtLibrary(node)){return false}if(receiverIsForeignImport(node)){return false}if(calleeIsForeign(node)){return false}if(sourceCode!==void 0&&receiverIsForeignValue(node,sourceCode)){return false}if(node.callee.type==="MemberExpression"){if(receiverIsForeignConstruction(node.callee.object)){return false}const method=(0,eslint_devkit_1.propertyName)(node.callee);if(method!==null){return targetMethods.has(method)}}if(node.callee.type==="Identifier"){return targetMethods.has(node.callee.name)}return false}function isStringLiteral(node,value){if(node.type!=="Literal"||typeof node.value!=="string"){return false}return value===void 0||node.value===value}function extractAlgorithms(optionsNode){const algorithms=[];for(const prop of optionsNode.properties){if(prop.type!=="Property"){continue}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"){algorithms.push(prop.value.value)}if(prop.value.type==="ArrayExpression"){for(const elem of prop.value.elements){if(elem&&elem.type==="Literal"&&typeof elem.value==="string"){algorithms.push(elem.value)}}}}return algorithms}function hasOption(optionsNode,optionName){return optionsNode.properties.some(prop=>prop.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(prop)===optionName)}function getOptionValue(optionsNode,optionName){let found;for(const prop of optionsNode.properties){if(prop.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(prop)===optionName){found=prop.value}}return found}function isWeakSecret(node,minLength=32){if(node.type==="Literal"&&typeof node.value==="string"){return node.value.length<minLength}return false}function byteKeyLiteral(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return null;const arg=node.arguments[0];if(arg===void 0)return null;const callee=node.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;const method=(0,eslint_devkit_1.propertyName)(callee);if(method==="encode"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&callee.object.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.callee.name==="TextEncoder"){return{literal:arg}}if(method==="from"&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="Buffer"){const encodingArg=node.arguments[1];return{literal:arg,encoding:encodingArg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof encodingArg.value==="string"?encodingArg.value.toLowerCase():void 0}}return null}function isEnvVariable(node){if(node.type==="MemberExpression"&&node.object.type==="MemberExpression"&&node.object.object.type==="Identifier"&&node.object.object.name==="process"&&(0,eslint_devkit_1.propertyName)(node.object)==="env"){return true}return false}function isSignOperation(node,sourceCode){return isJwtLibraryCall(node,exports.JWT_METHODS.SIGN,sourceCode)}function isVerifyOperation(node,sourceCode){return isJwtLibraryCall(node,exports.JWT_METHODS.VERIFY,sourceCode)}function isSignatureVerifyOperation(node,sourceCode){return isJwtLibraryCall(node,SIGNATURE_VERIFY,sourceCode)}function isDecodeOperation(node,sourceCode){return isJwtLibraryCall(node,exports.JWT_METHODS.DECODE,sourceCode)}const MAX_RESOLUTION_DEPTH=4;function findVariable(sourceCode,node){for(let scope=sourceCode.getScope(node);scope!==null;scope=scope.upper){const variable=scope.set.get(node.name);if(variable!==void 0)return variable}return null}function bindingOf(sourceCode,node){const variable=findVariable(sourceCode,node);const def=variable?.defs[0];if(def===void 0)return{kind:"unbound"};if(def.type==="FunctionName")return{kind:"function"};if(def.type==="Variable"&&def.parent.kind==="const"&&def.node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&def.node.init!==null){return{kind:"const",init:def.node.init,variable}}return{kind:"other"}}function flattenObject(object,sourceCode,depth){const properties=[];let opaque=false;for(const element of object.properties){if(element.type===eslint_devkit_1.AST_NODE_TYPES.Property){properties.push(element);continue}const spread=resolveObject(element.argument,sourceCode,depth+1);if(spread===null){opaque=true;continue}properties.push(...spread.properties);opaque||=spread.opaque}return{properties,opaque}}function resolveObject(node,sourceCode,depth=0){if(node===void 0||depth>MAX_RESOLUTION_DEPTH)return null;const value=(0,eslint_devkit_1.unwrapTypeSyntax)(node);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return flattenObject(value,sourceCode,depth);case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const binding=bindingOf(sourceCode,value);if(binding.kind==="const"){return resolveObject(binding.init,sourceCode,depth+1)}if(binding.kind==="function")return null;return{properties:[],opaque:true}}case eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.Literal:case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return null;default:return{properties:[],opaque:true}}}function calledName(node){return node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?node.callee.name:(0,eslint_devkit_1.propertyName)(node.callee)}function chainRoot(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}return current}function thisMemberTypeSource(member){const name=(0,eslint_devkit_1.propertyName)(member);let classBody=member.parent;while(classBody&&classBody.type!==eslint_devkit_1.AST_NODE_TYPES.ClassBody){classBody=classBody.parent}if(!classBody)return null;let annotation;for(const element of classBody.body){if(element.type===eslint_devkit_1.AST_NODE_TYPES.PropertyDefinition&&(0,eslint_devkit_1.objectKeyName)(element)===name){annotation=element.typeAnnotation?.typeAnnotation}if(element.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition&&element.kind==="constructor"){for(const param of element.value.params){if(param.type===eslint_devkit_1.AST_NODE_TYPES.TSParameterProperty&&param.parameter.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&param.parameter.name===name){annotation=param.parameter.typeAnnotation?.typeAnnotation}}}}if(annotation?.type!==eslint_devkit_1.AST_NODE_TYPES.TSTypeReference||annotation.typeName.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}const typeName=annotation.typeName.name;for(const stmt of programOf(member).body){const source=bindingSourceOf(stmt,typeName);if(source!==null)return source}return null}function programOf(node){let root=node;while(root.parent)root=root.parent;return root}function isJwtSource(source){return JWT_LIBRARY_ROOTS.has(packageRootOf(source))}const NON_JWT_NAMESPACES=new Set(["subtle","base64url"]);function importedNameOf(sourceCode,node){const def=findVariable(sourceCode,node)?.defs[0];return def?.node.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier&&def.node.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?def.node.imported.name:null}function receiverIsForeignValue(node,sourceCode){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const receiver=node.callee.object;const namespace=receiver.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?(0,eslint_devkit_1.propertyName)(receiver):receiver.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?importedNameOf(sourceCode,receiver):null;if(namespace!==null&&NON_JWT_NAMESPACES.has(namespace))return true;const root=chainRoot(receiver);if(root.type===eslint_devkit_1.AST_NODE_TYPES.ThisExpression){if(receiver.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const source=thisMemberTypeSource(innermostMember(receiver));return source!==null&&!isJwtSource(source)}if(root.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const binding=bindingOf(sourceCode,root);if(binding.kind==="unbound")return root.name==="crypto";if(binding.kind!=="const")return false;return valueIsForeign(binding.init)}function innermostMember(member){let current=member;while(current.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}return current}function valueIsForeign(init){let value=(0,eslint_devkit_1.unwrapTypeSyntax)(init);if(value.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)value=value.argument;if(value.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&value.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression){return false}const required=requireSpecifierOf(value);if(required!==null)return!isJwtSource(required);const callee=chainRoot(value.callee);if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;if(value.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&NON_JWT_CONSTRUCTORS.has(callee.name)){return true}for(const stmt of programOf(value).body){const source=bindingSourceOf(stmt,callee.name);if(source!==null)return!isJwtSource(source)}return false}function isNestJwtShape(node,sourceCode){const name=calledName(node);if(name==="signAsync"||name==="verifyAsync")return true;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.object.type===eslint_devkit_1.AST_NODE_TYPES.ThisExpression&&thisMemberTypeSource(node.callee.object)===exports.JWT_LIBRARIES.NESTJS_JWT){return true}if(node.arguments.length!==2)return false;const second=resolveObject(node.arguments[1],sourceCode);return second!==null&&!second.opaque&&!["key","passphrase","kty"].some(key=>hasOption(second,key))}function resolveCallOptions(node,sourceCode){if(isNestJwtShape(node,sourceCode)){const own=resolveObject(node.arguments[1],sourceCode);return{properties:own===null?[]:own.properties,opaque:true}}return resolveObject(node.arguments[2],sourceCode)}const JOSE_SIGN_BUILDERS=new Set(["SignJWT","CompactSign","FlattenedSign","GeneralSign"]);function joseBuilderChain(node,sourceCode){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;const calls=new Set;let current=node.callee.object;let hops=0;while(current.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression){if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){if(current.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){calls.add((0,eslint_devkit_1.propertyName)(current.callee))}current=current.callee}else if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}else if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&hops++<MAX_RESOLUTION_DEPTH){const binding=bindingOf(sourceCode,current);if(binding.kind!=="const")return null;for(const reference of binding.variable.references){const use=reference.identifier.parent;if(reference.identifier.range[0]<node.range[0]&&use.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&use.parent.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){calls.add((0,eslint_devkit_1.propertyName)(use))}}current=binding.init}else{return null}}const kind=current.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?current.callee.name:current.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?(0,eslint_devkit_1.propertyName)(current.callee):null;return kind!==null&&JOSE_SIGN_BUILDERS.has(kind)?{builder:current,kind,calls}:null}const KEY_OPTION_NAMES=["secret","privateKey","publicKey"];function keyNodesOf(node,sourceCode){if(joseBuilderChain(node,sourceCode)!==null){return node.arguments.slice(0,1)}if(isNestJwtShape(node,sourceCode)){const options=resolveObject(node.arguments[1],sourceCode);return keyValuesOf(options,KEY_OPTION_NAMES)}return node.arguments.slice(1,2)}function keyValuesOf(options,names){if(options===null)return[];return options.properties.filter(prop=>names.includes((0,eslint_devkit_1.objectKeyName)(prop)??"")).map(prop=>prop.value)}function keyLiterals(node,sourceCode,depth=0){if(depth>MAX_RESOLUTION_DEPTH)return[];const value=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if((0,eslint_devkit_1.staticString)(value)!==null){return[{literal:value}]}if(value.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression){return[...keyLiterals(value.left,sourceCode,depth+1),...keyLiterals(value.right,sourceCode,depth+1)]}if(value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const binding=bindingOf(sourceCode,value);return binding.kind==="const"?keyLiterals(binding.init,sourceCode,depth+1):[]}const bytes=byteKeyLiteral(value);if(bytes===null)return[];return keyLiterals(bytes.literal,sourceCode,depth+1).map(found=>({literal:found.literal,encoding:bytes.encoding}))}const PUBLIC_PEM=/-----BEGIN (?:RSA |EC )?PUBLIC KEY-----|-----BEGIN CERTIFICATE-----/;function isPublicKeyMaterial(literal){return PUBLIC_PEM.test((0,eslint_devkit_1.staticString)(literal))}const CONFIG_KEY_OPTIONS=new Map([[exports.JWT_LIBRARIES.NESTJS_JWT,KEY_OPTION_NAMES],[exports.JWT_LIBRARIES.EXPRESS_JWT,["secret"]],[exports.JWT_LIBRARIES.PASSPORT_JWT,["secretOrKey"]],[exports.JWT_LIBRARIES.FAST_JWT,["key"]]]);function jwtConfigOf(node,sourceCode){const root=chainRoot(node.callee);if(root.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;let source=null;for(const stmt of programOf(node).body){source??=bindingSourceOf(stmt,root.name)}const keyNames=source===null?void 0:CONFIG_KEY_OPTIONS.get(packageRootOf(source));if(keyNames===void 0)return null;if(source===exports.JWT_LIBRARIES.NESTJS_JWT&&(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||(0,eslint_devkit_1.propertyName)(node.callee)!=="register")){return null}const options=resolveObject(node.arguments[0],sourceCode);if(options===null)return null;return{options,keys:keyValuesOf(options,keyNames)}}