eslint-plugin-jwt-security 3.4.0 → 3.5.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 +1 -1
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/rules/no-algorithm-confusion/index.js +1 -1
- package/src/rules/no-decode-without-verify/index.js +1 -1
- package/src/rules/no-sensitive-payload/index.js +1 -1
- package/src/rules/no-timestamp-manipulation/index.js +1 -1
- package/src/rules/require-expiration/index.js +1 -1
- package/src/rules/require-issued-at/index.js +1 -1
- package/src/types/index.d.ts +4 -0
- package/src/utils/cross-file.js +1 -0
- package/src/utils/index.js +1 -1
- package/src/utils/nest-modules.js +1 -0
- package/src/utils/provenance.js +1 -0
- package/src/utils/type-info.js +1 -0
- package/src/utils/value-flow.js +1 -0
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md)
|
|
|
123
123
|
| [require-algorithm-whitelist](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-algorithm-whitelist?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-757 | | | This rule enforces explicit algorithm specification in verify() calls | 🟢 | 💼 | | | | |
|
|
124
124
|
| [require-audience-validation](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-audience-validation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-287 | | | The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance: | 🟢 | | | | | |
|
|
125
125
|
| [require-expiration](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-expiration?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-613 | | | The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance: | 🟢 | 💼 | | | | |
|
|
126
|
-
| [require-issued-at](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-issued-at?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-294 | | | This rule ensures tokens have the iat claim for freshness validation | 🟢 | |
|
|
126
|
+
| [require-issued-at](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-issued-at?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-294 | | | This rule ensures tokens have the iat claim for freshness validation | 🟢 | | ⚠️ | | | |
|
|
127
127
|
| [require-issuer-validation](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-issuer-validation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-287 | | | The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance: | 🟢 | | | | | |
|
|
128
128
|
| [require-max-age](https://eslint.interlace.tools/docs/security/plugin-jwt-security/rules/require-max-age?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-jwt-security) | CWE-294 | | | This rule mandates maxAge in verify operations | 🟢 | | | | | |
|
|
129
129
|
<!-- AUTO-GENERATED:RULES_TABLE:END -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-jwt-security",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.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.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitivePayload=exports.requireMaxAge=exports.noTimestampManipulation=exports.requireAudienceValidation=exports.requireIssuerValidation=exports.requireIssuedAt=exports.noHardcodedSecret=exports.requireExpiration=exports.noWeakSecret=exports.noDecodeWithoutVerify=exports.requireAlgorithmWhitelist=exports.noAlgorithmConfusion=exports.noAlgorithmNone=exports.configs=exports.plugin=exports.rules=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const no_algorithm_none_1=require("./rules/no-algorithm-none");Object.defineProperty(exports,"noAlgorithmNone",{enumerable:true,get:function(){return no_algorithm_none_1.noAlgorithmNone}});const no_algorithm_confusion_1=require("./rules/no-algorithm-confusion");Object.defineProperty(exports,"noAlgorithmConfusion",{enumerable:true,get:function(){return no_algorithm_confusion_1.noAlgorithmConfusion}});const require_algorithm_whitelist_1=require("./rules/require-algorithm-whitelist");Object.defineProperty(exports,"requireAlgorithmWhitelist",{enumerable:true,get:function(){return require_algorithm_whitelist_1.requireAlgorithmWhitelist}});const no_decode_without_verify_1=require("./rules/no-decode-without-verify");Object.defineProperty(exports,"noDecodeWithoutVerify",{enumerable:true,get:function(){return no_decode_without_verify_1.noDecodeWithoutVerify}});const no_weak_secret_1=require("./rules/no-weak-secret");Object.defineProperty(exports,"noWeakSecret",{enumerable:true,get:function(){return no_weak_secret_1.noWeakSecret}});const require_expiration_1=require("./rules/require-expiration");Object.defineProperty(exports,"requireExpiration",{enumerable:true,get:function(){return require_expiration_1.requireExpiration}});const no_hardcoded_secret_1=require("./rules/no-hardcoded-secret");Object.defineProperty(exports,"noHardcodedSecret",{enumerable:true,get:function(){return no_hardcoded_secret_1.noHardcodedSecret}});const require_issued_at_1=require("./rules/require-issued-at");Object.defineProperty(exports,"requireIssuedAt",{enumerable:true,get:function(){return require_issued_at_1.requireIssuedAt}});const require_issuer_validation_1=require("./rules/require-issuer-validation");Object.defineProperty(exports,"requireIssuerValidation",{enumerable:true,get:function(){return require_issuer_validation_1.requireIssuerValidation}});const require_audience_validation_1=require("./rules/require-audience-validation");Object.defineProperty(exports,"requireAudienceValidation",{enumerable:true,get:function(){return require_audience_validation_1.requireAudienceValidation}});const no_timestamp_manipulation_1=require("./rules/no-timestamp-manipulation");Object.defineProperty(exports,"noTimestampManipulation",{enumerable:true,get:function(){return no_timestamp_manipulation_1.noTimestampManipulation}});const require_max_age_1=require("./rules/require-max-age");Object.defineProperty(exports,"requireMaxAge",{enumerable:true,get:function(){return require_max_age_1.requireMaxAge}});const no_sensitive_payload_1=require("./rules/no-sensitive-payload");Object.defineProperty(exports,"noSensitivePayload",{enumerable:true,get:function(){return no_sensitive_payload_1.noSensitivePayload}});exports.rules={get"no-algorithm-none"(){return require("./rules/no-algorithm-none").noAlgorithmNone},get"no-algorithm-confusion"(){return require("./rules/no-algorithm-confusion").noAlgorithmConfusion},get"require-algorithm-whitelist"(){return require("./rules/require-algorithm-whitelist").requireAlgorithmWhitelist},get"no-decode-without-verify"(){return require("./rules/no-decode-without-verify").noDecodeWithoutVerify},get"no-weak-secret"(){return require("./rules/no-weak-secret").noWeakSecret},get"require-expiration"(){return require("./rules/require-expiration").requireExpiration},get"no-hardcoded-secret"(){return require("./rules/no-hardcoded-secret").noHardcodedSecret},get"require-issued-at"(){return require("./rules/require-issued-at").requireIssuedAt},get"require-issuer-validation"(){return require("./rules/require-issuer-validation").requireIssuerValidation},get"require-audience-validation"(){return require("./rules/require-audience-validation").requireAudienceValidation},get"no-timestamp-manipulation"(){return require("./rules/no-timestamp-manipulation").noTimestampManipulation},get"require-max-age"(){return require("./rules/require-max-age").requireMaxAge},get"no-sensitive-payload"(){return require("./rules/no-sensitive-payload").noSensitivePayload}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-jwt-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-jwt-security",version:"3.5.0"},rules:exports.rules};const recommendedRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-expiration":"error","jwt-security/no-sensitive-payload":"warn","jwt-security/require-issued-at":"warn"};const strictRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/require-algorithm-whitelist":"error","jwt-security/no-decode-without-verify":"error","jwt-security/no-weak-secret":"error","jwt-security/no-hardcoded-secret":"error","jwt-security/require-expiration":"error","jwt-security/require-issued-at":"error","jwt-security/require-issuer-validation":"error","jwt-security/require-audience-validation":"error","jwt-security/no-timestamp-manipulation":"error","jwt-security/require-max-age":"error","jwt-security/no-sensitive-payload":"error"};const legacyRules={"jwt-security/no-algorithm-none":"error","jwt-security/no-algorithm-confusion":"error","jwt-security/no-hardcoded-secret":"warn"};exports.configs={flagship:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:{"jwt-security/no-algorithm-none":"error"}},recommended:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:recommendedRules},strict:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules},legacy:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:legacyRules},all:{plugins:{"jwt-security":exports.plugin,jwt:exports.plugin},rules:strictRules}};exports.default=exports.plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmConfusion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noAlgorithmConfusion=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.noAlgorithmConfusion=(0,eslint_devkit_1.createRule)({name:"no-algorithm-confusion",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-algorithm-confusion.md",description:"Prevent algorithm confusion attacks using symmetric algorithms with asymmetric keys",cwe:"CWE-347",cvss:9.5},fixable:void 0,hasSuggestions:false,messages:{algorithmConfusion:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"JWT Algorithm Confusion Attack",cwe:"CWE-347",description:"Symmetric algorithm (HS*) used with public key allows token forgery",severity:"CRITICAL",fix:"Use asymmetric algorithms (RS256, ES256) with public keys",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),symmetricWithPublicKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Symmetric Algorithm with Public Key",cwe:"CWE-347",description:"HS256/384/512 should only be used with shared secrets, not public keys",severity:"CRITICAL",fix:"Switch to RS256 or ES256 for asymmetric key verification",documentationLink:"https://portswigger.net/web-security/jwt/algorithm-confusion"}),useAsymmetricAlgorithm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Asymmetric Algorithm",description:"Replace HS* with RS* or ES* algorithm",severity:"LOW",fix:"Use RS256 or ES256 with public key verification",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{symmetricAlgorithms:{type:"array",items:{type:"string"},default:["HS256","HS384","HS512"],description:"Algorithms to flag when used with public keys"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{symmetricAlgorithms:["HS256","HS384","HS512"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const symmetricAlgSet=new Set(options.symmetricAlgorithms??["HS256","HS384","HS512"]);const sourceCode=context.sourceCode;const hasSymmetricAlgorithm=algorithms=>{for(const alg of algorithms){if(symmetricAlgSet.has(alg)){return alg}}return null};return{CallExpression(node){if(!(0,utils_1.isSignatureVerifyOperation)(node,sourceCode)){return}if(node.arguments.length<2){return}const options2=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options2===null){return}const algorithms=(0,utils_1.extractAlgorithms)(options2);const symmetricAlg=hasSymmetricAlgorithm(algorithms);if(!symmetricAlg){return}const mixesFamilies=algorithms.some(alg=>utils_1.SECURE_ALGORITHMS.has(alg));if(!mixesFamilies&&!(0,utils_1.isPublicKeySource)(node.arguments[1],sourceCode)){return}for(const prop of options2.properties){if(["algorithms","algorithm","alg"].includes((0,eslint_devkit_1.objectKeyName)(prop)??"")){context.report({node:prop.value,messageId:"algorithmConfusion",data:{algorithm:symmetricAlg}});return}}}}}});exports.default=exports.noAlgorithmConfusion;
|
|
@@ -1 +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=[],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};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDecodeWithoutVerify=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const provenance_1=require("../../utils/provenance");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};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&&(0,provenance_1.fromBackChannel)(tokenArgument,sourceCode,{filename:context.filename,parser:context.languageOptions.parser})){return}context.report({node,messageId:"decodeWithoutVerify"});return}if(isJwtDecodeLibrary(node)){if((0,eslint_devkit_1.hasSafeAnnotation)(node,context,trustedAnnotations)){return}context.report({node,messageId:"jwtDecodeLibrary"})}}}}});exports.default=exports.noDecodeWithoutVerify;
|
|
@@ -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]);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
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitivePayload=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const type_info_1=require("../../utils/type-info");const value_flow_1=require("../../utils/value-flow");exports.noSensitivePayload=(0,eslint_devkit_1.createRule)({name:"no-sensitive-payload",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-sensitive-payload.md",description:"Prevent storing sensitive data in JWT payload which is only base64-encoded",cwe:"CWE-359",cvss:5.3},fixable:void 0,hasSuggestions:false,messages:{sensitivePayloadField:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in JWT Payload",cwe:"CWE-359",description:'JWT payloads are not encrypted - sensitive data like "{{fieldName}}" can be read by anyone',severity:"MEDIUM",fix:"Store sensitive data server-side, reference by ID in token",documentationLink:"https://tools.ietf.org/html/rfc8725"}),wholeRecordSpread:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Whole Record Spread into JWT Payload",cwe:"CWE-200",cvss:5.3,description:"Spreading a whole object into the claims copies every field it has - including ones added later - into a token anyone can read",severity:"MEDIUM",fix:"Pick the claims explicitly: { sub: user.id, role: user.role }",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{additionalSensitiveFields:{type:"array",items:{type:"string"},default:[],description:"Additional field names to flag as sensitive"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{additionalSensitiveFields:[],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]??{};const additionalFields=options.additionalSensitiveFields??[];const allSensitiveFields=new Set([...utils_1.SENSITIVE_PAYLOAD_FIELDS,...additionalFields]);const sourceCode=context.sourceCode;const isSignedRequestObject=payload=>{const clientId=(0,utils_1.getOptionValue)(payload,"client_id");const issuer=(0,utils_1.getOptionValue)(payload,"iss");return clientId!==void 0&&issuer!==void 0&&(0,utils_1.hasOption)(payload,"aud")&&sourceCode.getText(issuer)===sourceCode.getText(clientId)};const isAuthorizationParams=(spread,payload)=>{if(!isSignedRequestObject(payload))return false;const source=(0,value_flow_1.resolveTerminal)(spread.argument,sourceCode);return source.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(0,value_flow_1.findVariable)(sourceCode,source)?.defs[0]?.type==="Parameter"};return{CallExpression(node){if(!(0,utils_1.isSignOperation)(node,sourceCode)){return}const chain=(0,utils_1.joseBuilderChain)(node,sourceCode);const payload=(0,utils_1.resolveObject)(chain===null?node.arguments[0]:chain.builder.arguments[0],sourceCode);if(payload===null){return}for(const spread of payload.opaqueSpreads){if(!isAuthorizationParams(spread,payload)&&(0,type_info_1.mayCarryField)(spread.argument,sourceCode,allSensitiveFields)){context.report({node:spread,messageId:"wholeRecordSpread"})}}for(const prop of payload.properties){const keyText=(0,eslint_devkit_1.objectKeyName)(prop);if(keyText!==null&&allSensitiveFields.has(keyText.toLowerCase())){context.report({node:prop,messageId:"sensitivePayloadField",data:{fieldName:keyText}})}}}}}});exports.default=exports.noSensitivePayload;
|
|
@@ -1 +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"}),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
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimestampManipulation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS=300;exports.noTimestampManipulation=(0,eslint_devkit_1.createRule)({name:"no-timestamp-manipulation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/no-timestamp-manipulation.md",description:"Prevent disabling automatic timestamp generation which enables replay attacks",cwe:"CWE-294",cvss:7.5},fixable:void 0,hasSuggestions:false,messages:{timestampDisabled:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timestamp Generation Disabled",cwe:"CWE-294",description:"noTimestamp:true disables iat claim, enabling replay attacks",severity:"HIGH",fix:"Remove noTimestamp option or set to false",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),noTimestampTrue:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Back to the Future Attack Vector",cwe:"CWE-294",description:"Disabling timestamps allows attackers to forge tokens valid in the future",severity:"HIGH",fix:"Always include iat claim for freshness validation",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"}),ignoreExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Expiration Check Disabled",cwe:"CWE-613",cvss:7.5,description:"ignoreExpiration:true accepts expired tokens, so a stolen token never stops working",severity:"HIGH",fix:"Remove ignoreExpiration; refresh the token instead of accepting an expired one",documentationLink:"https://tools.ietf.org/html/rfc8725"}),excessiveClockTolerance:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Excessive Clock Tolerance",cwe:"CWE-613",cvss:7.5,description:"A clockTolerance far beyond clock skew keeps expired tokens valid for that long",severity:"HIGH",fix:"Keep clockTolerance to seconds of skew (maxClockToleranceSeconds, default 300)",documentationLink:"https://tools.ietf.org/html/rfc8725"})},schema:[{type:"object",properties:{maxClockToleranceSeconds:{type:"number",minimum:0,default:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,description:"Largest numeric clockTolerance (seconds) a verify may allow"},trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{maxClockToleranceSeconds:DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS,trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;const{maxClockToleranceSeconds=DEFAULT_MAX_CLOCK_TOLERANCE_SECONDS}=context.options[0]??{};const reportLiteralTrue=(options,name,messageId)=>{const value=(0,utils_1.getOptionValue)(options,name);if(value?.type==="Literal"&&value.value===true){context.report({node:value,messageId})}};const checkConfig=node=>{const config=(0,utils_1.jwtConfigOf)(node,sourceCode);if(config!==null){reportLiteralTrue(config.options,"ignoreExpiration","ignoreExpiration")}};return{CallExpression(node){checkConfig(node);if((0,utils_1.isSignOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"noTimestamp","noTimestampTrue")}return}if((0,utils_1.isVerifyOperation)(node,sourceCode)){const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null){reportLiteralTrue(options,"ignoreExpiration","ignoreExpiration");const tolerance=(0,utils_1.getOptionValue)(options,"clockTolerance");const seconds=tolerance===void 0?null:(0,utils_1.staticNumber)(tolerance,sourceCode);if(seconds!==null&&seconds>maxClockToleranceSeconds){context.report({node:tolerance,messageId:"excessiveClockTolerance"})}}}},NewExpression:checkConfig}}});exports.default=exports.noTimestampManipulation;
|
|
@@ -1 +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 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}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireExpiration=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");const nest_modules_1=require("../../utils/nest-modules");exports.requireExpiration=(0,eslint_devkit_1.createRule)({name:"require-expiration",skipTestFiles:true,meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-expiration.md",description:"Require expiration claim (exp) or expiresIn option in JWT signing",cwe:"CWE-613",cvss:5.4},hasSuggestions:true,messages:{missingExpiration:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing JWT Expiration",cwe:"CWE-613",description:"JWT without expiration is valid forever, increasing exposure window",severity:"MEDIUM",fix:'Add expiresIn: "1h" or exp claim to payload',documentationLink:"https://tools.ietf.org/html/rfc8725"}),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}const nestShaped=(0,utils_1.isNestJwtShape)(node,sourceCode)||node.arguments.length===1&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.object.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression;if(nestShaped){const own=(0,utils_1.resolveObject)(node.arguments[1],sourceCode);if(own!==null&&((0,utils_1.hasOption)(own,"expiresIn")||own.opaque)||(0,nest_modules_1.nestModuleExpiry)(context.filename,context.languageOptions.parser)!=="missing"){return}}else{const options=(0,utils_1.resolveCallOptions)(node,sourceCode);if(options!==null&&((0,utils_1.hasOption)(options,"expiresIn")||options.opaque)){return}}}const optionsArg3=node.arguments[(0,utils_1.isNestJwtShape)(node,sourceCode)?1:2];context.report({node,messageId:"missingExpiration",suggest:[{messageId:"addExpiration",fix(fixer){const sourceCode2=context.sourceCode;if(optionsArg3&&optionsArg3.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){const openBrace=sourceCode2.getFirstToken(optionsArg3);return fixer.insertTextAfter(openBrace," expiresIn: '1h',")}const lastArg=node.arguments[node.arguments.length-1];return fixer.insertTextAfter(lastArg,", { expiresIn: '1h' }")}}]})}}}});exports.default=exports.requireExpiration;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireIssuedAt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const utils_1=require("../../utils");exports.requireIssuedAt=(0,eslint_devkit_1.createRule)({name:"require-issued-at",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-jwt-security/docs/rules/require-issued-at.md",description:"Require iat (issued at) claim for token freshness validation",cwe:"CWE-294",cvss:5},fixable:void 0,hasSuggestions:false,messages:{missingIssuedAt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Issued At Claim",cwe:"CWE-294",description:"JWT without iat claim cannot be validated for freshness, enabling replay attacks",severity:"MEDIUM",fix:"Add iat claim to payload or use library option that adds it automatically",documentationLink:"https://securitypattern.com/post/jwt-back-to-the-future"})},schema:[{type:"object",properties:{trustedSanitizers:{type:"array",items:{type:"string"},default:[]},trustedAnnotations:{type:"array",items:{type:"string"},default:[]},strictMode:{type:"boolean",default:false}},additionalProperties:false}]},defaultOptions:[{trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const sourceCode=context.sourceCode;const claimsHaveIat=(claims,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===
|
|
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"&&typeof noTimestamp.value==="boolean")){context.report({node,messageId:"missingIssuedAt"})}}}}});exports.default=exports.requireIssuedAt;
|
package/src/types/index.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface JwtRuleOptions extends SecurityRuleOptions {
|
|
|
13
13
|
/** JWT libraries to consider for detection. Default: all supported libraries */
|
|
14
14
|
targetLibraries?: Array<'jsonwebtoken' | 'jose' | 'express-jwt' | '@nestjs/jwt' | 'jwks-rsa' | 'jwt-decode'>;
|
|
15
15
|
}
|
|
16
|
+
export interface NoTimestampManipulationOptions extends JwtRuleOptions {
|
|
17
|
+
/** Largest numeric `clockTolerance` (seconds) a verify may allow. Default 300. */
|
|
18
|
+
maxClockToleranceSeconds?: number;
|
|
19
|
+
}
|
|
16
20
|
export interface NoAlgorithmNoneOptions extends JwtRuleOptions {
|
|
17
21
|
/** Allow 'none' algorithm in test files. Default: false */
|
|
18
22
|
allowInTests?: boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(o,m,k,k2){if(k2===void 0)k2=k;var desc=Object.getOwnPropertyDescriptor(m,k);if(!desc||("get"in desc?!m.__esModule:desc.writable||desc.configurable)){desc={enumerable:true,get:function(){return m[k]}}}Object.defineProperty(o,k2,desc)}):(function(o,m,k,k2){if(k2===void 0)k2=k;o[k2]=m[k]}));var __setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v})}):function(o,v){o["default"]=v});var __importStar=this&&this.__importStar||(function(){var ownKeys=function(o){ownKeys=Object.getOwnPropertyNames||function(o2){var ar=[];for(var k in o2)if(Object.prototype.hasOwnProperty.call(o2,k))ar[ar.length]=k;return ar};return ownKeys(o)};return function(mod){if(mod&&mod.__esModule)return mod;var result={};if(mod!=null){for(var k=ownKeys(mod),i=0;i<k.length;i++)if(k[i]!=="default")__createBinding(result,mod,k[i])}__setModuleDefault(result,mod);return result}})();Object.defineProperty(exports,"__esModule",{value:true});exports.parseSource=parseSource;exports.parseFileCached=parseFileCached;exports.relativeExportModule=relativeExportModule;exports.resolveSymbol=resolveSymbol;exports.bindsName=bindsName;const fs=__importStar(require("node:fs"));const path=__importStar(require("node:path"));const node_module_1=require("node:module");const eslint_devkit_1=require("@interlace/eslint-devkit");const value_flow_1=require("./value-flow");const MAX_FILE_HOPS=4;const RESOLUTION_SUFFIXES=["",".ts",".tsx",".js",".mjs",".cjs","/index.ts","/index.tsx","/index.js","/index.mjs","/index.cjs"];const parsed=new Map;function fallbackParser(){try{return(0,node_module_1.createRequire)(path.join(process.cwd(),"noop.js"))("@typescript-eslint/parser")}catch{return null}}function tryParse(candidate,code,filePath){const parser=candidate;if(!parser)return null;const options={ecmaVersion:"latest",sourceType:"module",range:true,loc:true,filePath};try{return parser.parseForESLint?parser.parseForESLint(code,options).ast:parser.parse(code,options)}catch{return null}}function parseSource(code,filePath,parser){return tryParse(parser,code,filePath)??tryParse(fallbackParser(),code,filePath)}function parseFileCached(filePath,parser){const mtimeMs=fs.statSync(filePath,{throwIfNoEntry:false})?.mtimeMs??-1;const cached=parsed.get(filePath);if(cached?.mtimeMs===mtimeMs)return cached.program;let program=null;try{program=parseSource(fs.readFileSync(filePath,"utf8"),filePath,parser)}catch{program=null}parsed.set(filePath,{mtimeMs,program});return program}function resolveRelative(fromFile,specifier){const base=path.resolve(path.dirname(path.resolve(fromFile)),specifier);const typescriptTwin=base.replace(/\.js$/,".ts");for(const candidate of[...RESOLUTION_SUFFIXES.map(suffix=>base+suffix),typescriptTwin]){if(fs.statSync(candidate,{throwIfNoEntry:false})?.isFile()){return candidate}}return null}function bindingModule(program,name){for(const stmt of program.body){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration){for(const spec of stmt.specifiers){if(spec.local.name!==name)continue;return{source:String(stmt.source.value),exportName:spec.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier?(0,eslint_devkit_1.objectKeyName)({key:spec.imported,computed:false}):null}}}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of stmt.declarations){const source=(0,value_flow_1.requireSpecifier)(declarator.init);if(source===null)continue;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&declarator.id.name===name){const init=declarator.init;return{source,exportName:init.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?(0,eslint_devkit_1.propertyName)(init):null}}if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){for(const prop of declarator.id.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&prop.value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&prop.value.name===name){return{source,exportName:(0,eslint_devkit_1.objectKeyName)(prop)}}}}}}}return null}function exportedValue(program,name){for(const stmt of program.body){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.ExpressionStatement&&stmt.expression.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&stmt.expression.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const{left,right}=stmt.expression;const target=(0,eslint_devkit_1.propertyName)(left);if(target===name)return{kind:"value",node:right};if(target==="exports"&&right.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){for(const prop of right.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)===name){return{kind:"value",node:prop.value}}}}}if(stmt.type!==eslint_devkit_1.AST_NODE_TYPES.ExportNamedDeclaration)continue;if(stmt.declaration?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of stmt.declaration.declarations){if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&declarator.id.name===name&&declarator.init!==null){return{kind:"value",node:declarator.init}}}}for(const spec of stmt.specifiers){const exported=(0,eslint_devkit_1.objectKeyName)({key:spec.exported,computed:false});if(exported!==name)continue;const local=(0,eslint_devkit_1.objectKeyName)({key:spec.local,computed:false});return stmt.source?{kind:"reexport",source:String(stmt.source.value),exportName:local}:{kind:"value",node:{type:eslint_devkit_1.AST_NODE_TYPES.Identifier,name:local}}}}return null}function relativeExportModule(fromFile,specifier,exportName,parser,hops=0){if(hops>=MAX_FILE_HOPS)return null;const file=resolveRelative(fromFile,specifier);if(file===null)return null;const program=parseFileCached(file,parser);if(program===null)return null;const value=exportedValue(program,exportName);if(value===null)return null;let binding;if(value.kind==="reexport"){binding=value}else{const node=value.node;const required=(0,value_flow_1.requireSpecifier)(node);binding=required!==null?{source:required,exportName:node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression?(0,eslint_devkit_1.propertyName)(node):null}:node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?bindingModule(program,node.name):null}if(binding===null)return null;if(!binding.source.startsWith("."))return binding.source;return binding.exportName===null?null:relativeExportModule(file,binding.source,binding.exportName,parser,hops+1)}function topLevelDeclaration(program,name){for(const stmt of program.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||declaration.type===eslint_devkit_1.AST_NODE_TYPES.ClassDeclaration||declaration.type===eslint_devkit_1.AST_NODE_TYPES.TSInterfaceDeclaration||declaration.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAliasDeclaration)&&declaration.id?.name===name){return declaration}if(declaration.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration)continue;for(const declarator of declaration.declarations){if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&declarator.id.name===name&&declarator.init!==null&&(0,value_flow_1.requireSpecifier)(declarator.init)===null){return declarator.init}}}return null}function loadRelative(module,specifier){const file=resolveRelative(module.filename,specifier);const program=file===null?null:parseFileCached(file,module.parser);return program===null?null:{program,filename:file,parser:module.parser}}function resolveExport(module,name,hops){if(hops>=MAX_FILE_HOPS)return null;const declared=topLevelDeclaration(module.program,name);if(declared!==null)return{kind:"local",node:declared,module};for(const stmt of module.program.body){if(stmt.type!==eslint_devkit_1.AST_NODE_TYPES.ExportNamedDeclaration)continue;for(const spec of stmt.specifiers){if((0,eslint_devkit_1.objectKeyName)({key:spec.exported,computed:false})!==name){continue}const local=(0,eslint_devkit_1.objectKeyName)({key:spec.local,computed:false});if(stmt.source===null)return resolveSymbol(module,local,hops+1);const source=String(stmt.source.value);if(!source.startsWith("."))return{kind:"external",source};const next=loadRelative(module,source);return next===null?null:resolveExport(next,local,hops+1)}}return null}function resolveSymbol(module,name,hops=0){const local=topLevelDeclaration(module.program,name);if(local!==null)return{kind:"local",node:local,module};const binding=bindingModule(module.program,name);if(binding===null)return null;if(!binding.source.startsWith(".")){return{kind:"external",source:binding.source}}if(binding.exportName===null)return null;const target=loadRelative(module,binding.source);return target===null?null:resolveExport(target,binding.exportName,hops+1)}function bindsName(module,name){return topLevelDeclaration(module.program,name)!==null||bindingModule(module.program,name)!==null}
|
package/src/utils/index.js
CHANGED
|
@@ -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.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&¶m.parameter.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&¶m.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)}}
|
|
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=exports.staticNumber=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.isPublicKeySource=isPublicKeySource;exports.jwtConfigOf=jwtConfigOf;const value_flow_1=require("./value-flow");var value_flow_2=require("./value-flow");Object.defineProperty(exports,"staticNumber",{enumerable:true,get:function(){return value_flow_2.staticNumber}});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 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?(0,value_flow_1.importEqualsSpecifier)(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=(0,value_flow_1.requireSpecifier)(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 bindingOf(sourceCode,node){const variable=(0,value_flow_1.findVariable)(sourceCode,node);const def=variable?.defs[0];if(def===void 0)return{kind:"unbound"};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=[];const opaqueSpreads=[];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){opaqueSpreads.push(element);continue}properties.push(...spread.properties);if(spread.opaque){opaqueSpreads.push(...spread.opaqueSpreads.length>0?spread.opaqueSpreads:[element])}}return{properties,opaque:opaqueSpreads.length>0,opaqueSpreads}}const OPAQUE={properties:[],opaque:true,opaqueSpreads:[]};function resolveObject(node,sourceCode,depth=0){if(node===void 0||depth>MAX_RESOLUTION_DEPTH)return null;const value=(0,value_flow_1.resolveTerminal)(node,sourceCode);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return flattenObject(value,sourceCode,depth);case eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionExpression:case eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration:case eslint_devkit_1.AST_NODE_TYPES.Literal:case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return null;default:return OPAQUE}}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 thisMemberJwtSource(member,sourceCode){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||name===null)return null;const evidence=[];const declare=(annotation,decorators,value)=>{const type=annotation?.typeAnnotation;if(type?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference){evidence.push(type.typeName)}if(type?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeQuery)evidence.push(type.exprName);for(const decorator of decorators){const call=decorator.expression;if(call.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&call.arguments[0]){evidence.push(call.arguments[0])}}if(value)evidence.push(value)};for(const element of classBody.body){if(element.type===eslint_devkit_1.AST_NODE_TYPES.PropertyDefinition&&(0,eslint_devkit_1.objectKeyName)(element)===name){declare(element.typeAnnotation,element.decorators,element.value)}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)continue;const target=param.parameter.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentPattern?param.parameter.left:param.parameter;if(target.name===name){declare(target.typeAnnotation,param.decorators,param.parameter.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentPattern?param.parameter.right:null)}}}}collectThisAssignments(classBody,name,evidence);for(const node of evidence){const source=(0,value_flow_1.originModule)(node,sourceCode);if(source!==null&&isJwtSource(source))return source}return null}function collectThisAssignments(node,name,out){if(node.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentExpression&&node.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.object.type===eslint_devkit_1.AST_NODE_TYPES.ThisExpression&&(0,eslint_devkit_1.propertyName)(node.left)===name){out.push(node.right)}for(const child of(0,value_flow_1.childNodes)(node)){collectThisAssignments(child,name,out)}}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=(0,value_flow_1.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;return thisMemberJwtSource(innermostMember(receiver),sourceCode)===null}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=(0,value_flow_1.requireSpecifier)(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&&packageRootOf(thisMemberJwtSource(node.callee.object,sourceCode))===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 own===null?OPAQUE:{...own,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)]}const resolved=(0,value_flow_1.resolveTerminal)(value,sourceCode);if(resolved!==value){return keyLiterals(resolved,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 PUBLIC_KEY_FILE=/\.(?:pub|crt|cer)$/;const FILE_READERS=new Set(["readFileSync","readFile"]);const PUBLIC_KEY_EXPORTS=new Map([["crypto",new Set(["createPublicKey","X509Certificate"])],[exports.JWT_LIBRARIES.JOSE,new Set(["importSPKI","importX509","createRemoteJWKSet","createLocalJWKSet"])]]);function isPublicKeySource(node,sourceCode){const value=(0,value_flow_1.resolveTerminal)(node,sourceCode);const text=(0,eslint_devkit_1.staticString)(value);if(text!==null)return PUBLIC_PEM.test(text);if(value.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&value.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression){return false}const module=(0,value_flow_1.originModule)(value.callee,sourceCode);if(module===null)return false;const root=packageRootOf(module.replace(/^node:/,""));if(root===exports.JWT_LIBRARIES.JWKS_RSA)return true;const exportName=String((0,value_flow_1.calleeExportName)(value.callee,sourceCode));if(root==="fs"&&FILE_READERS.has(exportName)){const[path]=value.arguments;const file=path===void 0?null:(0,eslint_devkit_1.staticString)((0,value_flow_1.resolveTerminal)(path,sourceCode));return file!==null&&PUBLIC_KEY_FILE.test(file)}return PUBLIC_KEY_EXPORTS.get(root)?.has(exportName)??false}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)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(o,m,k,k2){if(k2===void 0)k2=k;var desc=Object.getOwnPropertyDescriptor(m,k);if(!desc||("get"in desc?!m.__esModule:desc.writable||desc.configurable)){desc={enumerable:true,get:function(){return m[k]}}}Object.defineProperty(o,k2,desc)}):(function(o,m,k,k2){if(k2===void 0)k2=k;o[k2]=m[k]}));var __setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(o,v){Object.defineProperty(o,"default",{enumerable:true,value:v})}):function(o,v){o["default"]=v});var __importStar=this&&this.__importStar||(function(){var ownKeys=function(o){ownKeys=Object.getOwnPropertyNames||function(o2){var ar=[];for(var k in o2)if(Object.prototype.hasOwnProperty.call(o2,k))ar[ar.length]=k;return ar};return ownKeys(o)};return function(mod){if(mod&&mod.__esModule)return mod;var result={};if(mod!=null){for(var k=ownKeys(mod),i=0;i<k.length;i++)if(k[i]!=="default")__createBinding(result,mod,k[i])}__setModuleDefault(result,mod);return result}})();Object.defineProperty(exports,"__esModule",{value:true});exports.nestModuleExpiry=nestModuleExpiry;const fs=__importStar(require("node:fs"));const path=__importStar(require("node:path"));const eslint_devkit_1=require("@interlace/eslint-devkit");const value_flow_1=require("./value-flow");const cross_file_1=require("./cross-file");const SKIPPED_DIRECTORIES=new Set(["node_modules","dist","build","coverage",".git",".next","out"]);const MODULE_FILE=/\.module\.ts$/;const MAX_SCANNED_ENTRIES=5e3;const NESTJS_JWT="@nestjs/jwt";const verdictByRoot=new Map;function nestModuleExpiry(filename,parser){const root=packageRootDirectory(path.dirname(path.resolve(filename)));if(root===null)return"unknown";let verdict=verdictByRoot.get(root);if(verdict===void 0){const registrations=moduleFiles(root).flatMap(file=>registrationsIn(file,parser));verdict=combine(registrations);verdictByRoot.set(root,verdict)}return verdict}function combine(registrations){if(registrations.length===0||registrations.includes("unknown")){return"unknown"}return registrations.includes("missing")?"missing":"set"}function packageRootDirectory(start){for(let directory=start;;){if(fs.existsSync(path.join(directory,"package.json")))return directory;const parent=path.dirname(directory);if(parent===directory)return null;directory=parent}}function moduleFiles(root){const files=[];const pending=[root];let visited=0;while(pending.length>0&&visited<MAX_SCANNED_ENTRIES){const directory=pending.pop();let entries;try{entries=fs.readdirSync(directory,{withFileTypes:true})}catch{continue}for(const entry of entries){visited++;const fullPath=path.join(directory,entry.name);if(entry.isDirectory()){if(!SKIPPED_DIRECTORIES.has(entry.name))pending.push(fullPath)}else if(MODULE_FILE.test(fullPath)){files.push(fullPath)}}}return files}function parseModule(file,parser){let code;try{code=fs.readFileSync(file,"utf8")}catch{return null}if(!code.includes("JwtModule"))return null;return(0,cross_file_1.parseSource)(code,file,parser)}function jwtModuleLocals(program){const locals=new Set;for(const stmt of program.body){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration&&stmt.source.value===NESTJS_JWT){for(const spec of stmt.specifiers){if(spec.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier&&(spec.imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?spec.imported.name:spec.imported.value)==="JwtModule"){locals.add(spec.local.name)}}}if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of stmt.declarations){if((0,value_flow_1.requireSpecifier)(declarator.init)===NESTJS_JWT&&declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){for(const prop of declarator.id.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)==="JwtModule"){locals.add(prop.value.name)}}}}}}return locals}function registrationsIn(file,parser){const program=parseModule(file,parser);if(program===null)return[];const locals=jwtModuleLocals(program);const found=[];const visit=node=>{if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&locals.has(node.callee.object.name)){const method=(0,eslint_devkit_1.propertyName)(node.callee);if(method==="register"){found.push(expiryOf(node.arguments[0],program))}if(method==="registerAsync"){found.push(expiryOfFactory(node.arguments[0],program))}}(0,value_flow_1.childNodes)(node).forEach(visit)};visit(program);return found}function objectIn(node,program){if(node?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return node;if(node?.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;for(const stmt of program.body){if(stmt.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration)continue;for(const declarator of stmt.declarations){if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&declarator.id.name===node.name&&declarator.init?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return declarator.init}}}return null}function lookup(object,key){let spread=false;for(const element of object.properties){if(element.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement){spread=true}else if((0,eslint_devkit_1.objectKeyName)(element)===key){return element.value}}return spread?"unknown":"absent"}function expiryOf(node,program){const options=objectIn(node,program);if(options===null)return"unknown";const signOptions=lookup(options,"signOptions");if(signOptions==="absent")return"missing";if(signOptions==="unknown")return"unknown";const sign=objectIn(signOptions,program);if(sign===null)return"unknown";const expiresIn=lookup(sign,"expiresIn");if(expiresIn==="unknown")return"unknown";return expiresIn==="absent"?"missing":"set"}function expiryOfFactory(node,program){const options=objectIn(node,program);const factory=options===null?"unknown":lookup(options,"useFactory");if(typeof factory==="string")return"unknown";return expiryOf((0,value_flow_1.soleReturn)(factory),program)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.fromBackChannel=fromBackChannel;const eslint_devkit_1=require("@interlace/eslint-devkit");const value_flow_1=require("./value-flow");const cross_file_1=require("./cross-file");const HTTP_CLIENT_PACKAGES=new Set(["axios","got","ky","undici","node-fetch","cross-fetch","isomorphic-fetch"]);const OIDC_CLIENT_PACKAGE="openid-client";const FRONT_CHANNEL_EXPORTS=new Set(["callbackParams"]);const BODY_READERS=new Set(["json","text"]);function fromBackChannel(node,sourceCode,crossFile,depth=0){if(depth>value_flow_1.MAX_FLOW_DEPTH)return false;const value=(0,value_flow_1.resolveTerminal)(node,sourceCode);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return fromBackChannel(value.object,sourceCode,crossFile,depth+1)||unwrapsBackChannel(value,sourceCode,crossFile,depth);case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return isBackChannelCall(value,sourceCode,crossFile,depth)||unwrapsBackChannel(value,sourceCode,crossFile,depth);case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return isBackChannelCall(value,sourceCode,crossFile,depth);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return identifierFromBackChannel(value,sourceCode,crossFile,depth);default:return false}}function isBackChannelCall(call,sourceCode,crossFile,depth){const callee=call.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&BODY_READERS.has(String((0,eslint_devkit_1.propertyName)(callee)))){return fromBackChannel(callee.object,sourceCode,crossFile,depth+1)}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="JSON"&&(0,eslint_devkit_1.propertyName)(callee)==="parse"&&isUnbound(sourceCode,callee.object)){const[text]=call.arguments;return text!==void 0&&fromBackChannel(text,sourceCode,crossFile,depth+1)}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isModuleLevel(sourceCode,callee)){const symbol=(0,cross_file_1.resolveSymbol)(lintedModule(call,crossFile),callee.name);if(symbol?.kind==="local"&&returnsFetchResponse(symbol))return true}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.name==="fetch"&&isUnbound(sourceCode,callee)){return true}const origin=(0,value_flow_1.originBinding)(callee,sourceCode);if(origin===null)return false;const module=origin.source.startsWith(".")&&origin.exportName!==null?(0,cross_file_1.relativeExportModule)(crossFile.filename,origin.source,origin.exportName,crossFile.parser)??origin.source:origin.source;const root=module.split("/")[0];if(HTTP_CLIENT_PACKAGES.has(root))return true;return root===OIDC_CLIENT_PACKAGE&&!FRONT_CHANNEL_EXPORTS.has(String((0,value_flow_1.calleeExportName)(callee,sourceCode)))}function identifierFromBackChannel(node,sourceCode,crossFile,depth){const def=(0,value_flow_1.findVariable)(sourceCode,node)?.defs[0];if(def?.type==="Variable"&&def.node.id.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier&&def.node.init!==null){return fromBackChannel(def.node.init,sourceCode,crossFile,depth+1)}if(def?.type!=="Parameter")return false;return everyCallSitePasses(def,sourceCode,crossFile,depth)}function everyCallSitePasses(def,sourceCode,crossFile,depth){const fn=def.node;const index=fn.params.indexOf(def.name);if(index<0||fn.id===null)return false;const fnVariable=(0,value_flow_1.findVariable)(sourceCode,fn.id);let sawCall=false;for(const reference of fnVariable.references){const parent=reference.identifier.parent;if(parent.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||parent.callee!==reference.identifier){continue}sawCall=true;const argument=parent.arguments[index];if(argument===void 0||!fromBackChannel(argument,sourceCode,crossFile,depth+1)){return false}}return sawCall}function isUnbound(sourceCode,node){return((0,value_flow_1.findVariable)(sourceCode,node)?.defs??[]).length===0}function isModuleLevel(sourceCode,node){const scope=(0,value_flow_1.findVariable)(sourceCode,node)?.scope.type;return scope==="module"||scope==="global"}function lintedModule(node,crossFile){let root=node;while(root.parent)root=root.parent;return{program:root,filename:crossFile.filename,parser:crossFile.parser}}const FETCH_RESPONSE_PACKAGES=new Set(["node-fetch","undici","cross-fetch","isomorphic-fetch"]);function returnsFetchResponse(symbol){let type=symbol.node.returnType?.typeAnnotation;if(type?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference&&type.typeName.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&type.typeName.name==="Promise"){type=type.typeArguments?.params[0]}if(type?.type!==eslint_devkit_1.AST_NODE_TYPES.TSTypeReference||type.typeName.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return false}const resolved=(0,cross_file_1.resolveSymbol)(symbol.module,type.typeName.name);if(resolved===null){return type.typeName.name==="Response"&&!(0,cross_file_1.bindsName)(symbol.module,type.typeName.name)&&!typeParameterBinds(symbol.node,type.typeName.name)}return resolved.kind==="external"&&FETCH_RESPONSE_PACKAGES.has(resolved.source.split("/")[0])}function typeParameterBinds(node,name){for(let n=node;n;n=n.parent){const params=n.typeParameters?.params;if(params?.some(p=>p.name.name===name))return true}return false}function wrapperOf(expr,module){if(expr.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&expr.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression||expr.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}const arg=expr.arguments[0]??null;let construction=expr;let where=module;if(expr.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const factory=(0,cross_file_1.resolveSymbol)(module,expr.callee.name);if(factory?.kind!=="local")return null;const built=(0,value_flow_1.soleReturn)(factory.node);if(built?.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression||built.arguments[0]?.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier){return null}const[param]=factory.node.params;if(param?.name!==built.arguments[0].name){return null}construction=built;where=factory.module}const cls=(0,cross_file_1.resolveSymbol)(where,construction.callee.name);if(cls?.kind!=="local"||cls.node.type!==eslint_devkit_1.AST_NODE_TYPES.ClassDeclaration){return null}const ctor=cls.node.body.body.find(member=>member.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition&&member.kind==="constructor");const first=ctor?.value.params[0];if(first?.type!==eslint_devkit_1.AST_NODE_TYPES.TSParameterProperty)return null;return{cls:cls.node,field:first.parameter.name,module:cls.module,arg}}function isThisField(node,field){return node?.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.ThisExpression&&(0,eslint_devkit_1.propertyName)(node)===field}function applySteps(returned,steps,module){let wrapper=wrapperOf(returned,module);let value=wrapper?.arg??null;for(const[index,step]of steps.entries()){if(wrapper===null)return"unknown";const yields=index===steps.length-1&&value!==null;const member=wrapper.cls.body.body.find(element=>(element.type===eslint_devkit_1.AST_NODE_TYPES.MethodDefinition||element.type===eslint_devkit_1.AST_NODE_TYPES.PropertyDefinition)&&(0,eslint_devkit_1.objectKeyName)(element)===step.name);if(!step.call){if(step.name===wrapper.field)return yields?value:"unknown";return member===void 0?"nothing":"unknown"}if(member?.type!==eslint_devkit_1.AST_NODE_TYPES.MethodDefinition)return"unknown";const returns=(0,value_flow_1.returnsOf)(member.value.body);if(returns.length===0)return"nothing";if(returns.length>1)return"unknown";const result=returns[0].argument;if(isThisField(result,wrapper.field))return yields?value:"unknown";const next=result===null?null:wrapperOf(result,wrapper.module);value=isThisField(next?.arg??null,wrapper.field)?value:null;wrapper=next}return"unknown"}function unwrapsBackChannel(node,sourceCode,crossFile,depth){const steps=[];let base=node;for(;;){if(base.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){steps.unshift({call:false,name:String((0,eslint_devkit_1.propertyName)(base))});base=base.object}else if(base.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&base.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){steps.unshift({call:true,name:String((0,eslint_devkit_1.propertyName)(base.callee))});base=base.callee.object}else{break}}const producer=(0,value_flow_1.resolveTerminal)(base,sourceCode);if(steps.length===0||producer.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression&&producer.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression){return false}const fn=producer.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression?(0,value_flow_1.resolveTerminal)(producer.callee,sourceCode):producer;const returned=fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression?[producer]:(0,value_flow_1.returnsOf)(fn.body).map(statement=>statement.argument);const module=lintedModule(node,crossFile);let yielded=0;for(const value of returned){const out=value===null?"nothing":applySteps(value,steps,module);if(out==="nothing")continue;if(out==="unknown"||!fromBackChannel(out,sourceCode,crossFile,depth+1)){return false}yielded++}return yielded>0}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.mayCarryField=mayCarryField;const ANY_OR_UNKNOWN=1|2;function mayCarryField(node,sourceCode,fields){const services=sourceCode.parserServices;if(!services.program)return true;const type=services.program.getTypeChecker().getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(node));if((type.flags&ANY_OR_UNKNOWN)!==0)return true;return type.getProperties().some(property=>fields.has(property.getName().toLowerCase()))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.MAX_FLOW_DEPTH=void 0;exports.findVariable=findVariable;exports.soleReturn=soleReturn;exports.returnsOf=returnsOf;exports.childNodes=childNodes;exports.resolveTerminal=resolveTerminal;exports.importSourceOf=importSourceOf;exports.requireSpecifier=requireSpecifier;exports.importEqualsSpecifier=importEqualsSpecifier;exports.originBinding=originBinding;exports.originModule=originModule;exports.calleeExportName=calleeExportName;exports.staticNumber=staticNumber;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.MAX_FLOW_DEPTH=8;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 soleVariableDef(sourceCode,node){const variable=findVariable(sourceCode,node);const def=variable?.defs[0];if(def===void 0)return null;if(def.type==="FunctionName")return def;if(def.type!=="Variable"||def.node.init===null)return null;const reassigned=variable.references.some(reference=>reference.isWrite()&&reference.identifier!==def.name);return reassigned?null:def}function patternPath(pattern,target){if(pattern===target)return[];if(pattern.type===eslint_devkit_1.AST_NODE_TYPES.AssignmentPattern){return patternPath(pattern.left,target)}if(pattern.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)return null;for(const prop of pattern.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const key=(0,eslint_devkit_1.objectKeyName)(prop);const rest=patternPath(prop.value,target);if(rest!==null)return key===null?null:[key,...rest]}return null}function pickProperty(object,key,sourceCode,depth,seen){for(let i=object.properties.length-1;i>=0;i--){const element=object.properties[i];if(element.type===eslint_devkit_1.AST_NODE_TYPES.Property){if((0,eslint_devkit_1.objectKeyName)(element)===key)return element.value;continue}const spread=resolveTerminal(element.argument,sourceCode,depth+1,seen);if(spread.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return null;const found=pickProperty(spread,key,sourceCode,depth+1,seen);if(found!==null)return found}return null}function soleReturn(fn){if(fn.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration){return null}if(fn.body.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return fn.body;const returns=[];collectReturns(fn.body,returns);return returns.length===1?returns[0].argument:null}function collectReturns(node,out){if(node.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){out.push(node);return}for(const child of childNodes(node)){if(child.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration&&child.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression&&child.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){collectReturns(child,out)}}}function returnsOf(body){const out=[];collectReturns(body,out);return out}const NON_CHILD_KEYS=new Set(["parent","loc","range","tokens","comments"]);function childNodes(node){const children=[];for(const[key,value]of Object.entries(node)){if(NON_CHILD_KEYS.has(key))continue;for(const item of Array.isArray(value)?value:[value]){if(item!==null&&typeof item==="object"&&typeof item.type==="string"){children.push(item)}}}return children}function resolveTerminal(node,sourceCode,depth=0,seen=new Set){const value=(0,eslint_devkit_1.unwrapTypeSyntax)(node);if(depth>exports.MAX_FLOW_DEPTH||seen.has(value))return value;seen.add(value);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:return resolveTerminal(value.argument,sourceCode,depth+1,seen);case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const def=soleVariableDef(sourceCode,value);if(def===null)return value;if(def.type==="FunctionName")return def.node;const declarator=def.node;const init=declarator.init;if(declarator.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return resolveTerminal(init,sourceCode,depth+1,seen)}const path=patternPath(declarator.id,def.name);let current=resolveTerminal(init,sourceCode,depth+1,seen);for(const key of path??[null]){if(key===null||current?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return value}const picked=pickProperty(current,key,sourceCode,depth+1,seen);current=picked===null?null:resolveTerminal(picked,sourceCode,depth+1,seen)}return current??value}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:{const key=(0,eslint_devkit_1.propertyName)(value);const object=resolveTerminal(value.object,sourceCode,depth+1,seen);if(key===null||object.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return value}const picked=pickProperty(object,key,sourceCode,depth+1,seen);return picked===null?value:resolveTerminal(picked,sourceCode,depth+1,seen)}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const callee=resolveTerminal(value.callee,sourceCode,depth+1,seen);const returned=soleReturn(callee);return returned===null?value:resolveTerminal(returned,sourceCode,depth+1,seen)}default:return value}}function importSourceOf(sourceCode,node){const def=findVariable(sourceCode,node)?.defs[0];if(def===void 0)return null;if(def.type==="ImportBinding"){const declaration=def.parent;return declaration.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration?String(declaration.source.value):importEqualsSpecifier(declaration)}if(def.type!=="Variable"||def.node.init===null)return null;return requireSpecifier(def.node.init)}function requireSpecifier(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}return(0,eslint_devkit_1.staticString)(call.arguments[0])}function importEqualsSpecifier(stmt){const ref=stmt.moduleReference;if(ref.type!==eslint_devkit_1.AST_NODE_TYPES.TSExternalModuleReference)return null;return String(ref.expression.value)}function exportNameOf(sourceCode,node){const def=findVariable(sourceCode,node).defs[0];if(def.node.type===eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier){const{imported}=def.node;return imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?imported.name:String(imported.value)}if(def.node.type!==eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator)return null;const path=patternPath(def.node.id,def.name);return path?.length===1?path[0]:null}function originBinding(node,sourceCode,depth=0){if(depth>exports.MAX_FLOW_DEPTH)return null;const value=resolveTerminal(node,sourceCode);switch(value.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const source=importSourceOf(sourceCode,value);return source===null?null:{source,exportName:exportNameOf(sourceCode,value)}}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:{const required=requireSpecifier(value);return required!==null?{source:required,exportName:null}:originBinding(value.callee,sourceCode,depth+1)}case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return originBinding(value.object,sourceCode,depth+1);default:return null}}function originModule(node,sourceCode){return originBinding(node,sourceCode)?.source??null}function calleeExportName(callee,sourceCode){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return(0,eslint_devkit_1.propertyName)(callee)}if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const def=findVariable(sourceCode,callee).defs[0];if(def.node.type!==eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier)return callee.name;const{imported}=def.node;return imported.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?imported.name:String(imported.value)}function staticNumber(node,sourceCode,depth=0){if(depth>exports.MAX_FLOW_DEPTH)return null;const value=resolveTerminal(node,sourceCode);if(value.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return typeof value.value==="number"?value.value:null}if(value.type!==eslint_devkit_1.AST_NODE_TYPES.BinaryExpression)return null;const left=staticNumber(value.left,sourceCode,depth+1);const right=staticNumber(value.right,sourceCode,depth+1);if(left===null||right===null)return null;switch(value.operator){case"+":return left+right;case"-":return left-right;case"*":return left*right;case"/":return left/right;default:return null}}
|