eslint-plugin-vercel-ai-security 2.2.0 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-vercel-ai-security",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "ESLint plugin for Vercel AI SDK security — detects prompt injection, system-prompt leaks, hardcoded API keys, and unvalidated model output in generateText and streamText.",
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.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-vercel-ai-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.2.0"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/require-max-steps":"off","vercel-ai-security/require-abort-signal":"off","vercel-ai-security/require-request-timeout":"off","vercel-ai-security/no-training-data-exposure":"off","vercel-ai-security/require-output-filtering":"off","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-vercel-ai-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.3.0"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/require-max-steps":"off","vercel-ai-security/require-abort-signal":"off","vercel-ai-security/require-request-timeout":"off","vercel-ai-security/no-training-data-exposure":"off","vercel-ai-security/require-output-filtering":"off","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicSystemPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noDynamicSystemPrompt=(0,eslint_devkit_1.createRule)({name:"no-dynamic-system-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md",description:"Prevent dynamic content in system prompts to avoid agent confusion attacks",cwe:"CWE-74",cvss:8},messages:{dynamicSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt contains dynamic content. This can lead to agent confusion attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use a static system prompt defined as a constant. Avoid template literals or concatenation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),userControlledSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User-Controlled System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt is read straight from the request body. The caller can replace the agent's instructions.",severity:"HIGH",compliance:["SOC2"],fix:"Keep the system prompt server-side; let the request select from a fixed set of prompts by id",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{allowStaticTemplates}=options;const sourceCode=context.sourceCode;function isDynamicContent(node){const scope=sourceCode.getScope(node);if(node.type==="TemplateLiteral"){if(node.expressions.length===0)return!allowStaticTemplates;return!(0,sdk_1.isStaticText)(node,scope)}if(node.type==="BinaryExpression"&&node.operator==="+"){return!(0,sdk_1.isStaticText)(node,scope)}if(node.type==="CallExpression")return!(0,sdk_1.isDateValue)(node);return node.type==="AwaitExpression"}return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;if(isDynamicContent(prop.value)){context.report({node:prop.value,messageId:"dynamicSystemPrompt"})}else if((0,sdk_1.isRequestDerived)(prop.value,sourceCode.getScope(prop.value))){context.report({node:prop.value,messageId:"userControlledSystemPrompt"})}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicSystemPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noDynamicSystemPrompt=(0,eslint_devkit_1.createRule)({name:"no-dynamic-system-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md",description:"Prevent dynamic content in system prompts to avoid agent confusion attacks",cwe:"CWE-74",cvss:8},messages:{dynamicSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt contains dynamic content. This can lead to agent confusion attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use a static system prompt defined as a constant. Avoid template literals or concatenation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),userControlledSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User-Controlled System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt is read straight from the request body. The caller can replace the agent's instructions.",severity:"HIGH",compliance:["SOC2"],fix:"Keep the system prompt server-side; let the request select from a fixed set of prompts by id",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{allowStaticTemplates}=options;const sourceCode=context.sourceCode;function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length===0)return!allowStaticTemplates;return!(0,sdk_1.isStaticText)(node,sourceCode)}if(node.type==="BinaryExpression"&&node.operator==="+"){return!(0,sdk_1.isStaticText)(node,sourceCode)}if(node.type==="CallExpression")return!(0,sdk_1.isStaticText)(node,sourceCode);return node.type==="AwaitExpression"}return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;if(isDynamicContent(prop.value)){context.report({node:prop.value,messageId:"dynamicSystemPrompt"})}else if((0,sdk_1.isRequestDerived)(prop.value,sourceCode)){context.report({node:prop.value,messageId:"userControlledSystemPrompt"})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitiveInPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noSensitiveInPrompt=(0,eslint_devkit_1.createRule)({name:"no-sensitive-in-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md",description:"Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts",cwe:"CWE-200",cvss:8},messages:{sensitiveInPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in LLM Prompt",cwe:"CWE-200",owasp:"A01:2021",cvss:8,description:'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',severity:"CRITICAL",compliance:["SOC2","GDPR","HIPAA","PCI-DSS"],fix:"Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{sensitivePatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest sensitive data"}},additionalProperties:false}]},defaultOptions:[{sensitivePatterns:["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{sensitivePatterns}=options;const sourceCode=context.sourceCode;function isSensitiveIdentifier(name){return sensitivePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSensitiveData(node){if(node===null)return null;switch(node.type){case"Identifier":return isSensitiveIdentifier(node.name)?node.name:null;case"MemberExpression":{const name=(0,eslint_devkit_1.propertyName)(node);return name!==null&&isSensitiveIdentifier(name)?sourceCode.getText(node):null}case"TemplateLiteral":return firstSensitive(node.expressions);case"BinaryExpression":return findSensitiveData(node.left)??findSensitiveData(node.right);case"ArrayExpression":return firstSensitive(node.elements);case"SpreadElement":return findSensitiveData(node.argument);case"ObjectExpression":return firstSensitive(node.properties.map(prop=>prop.type==="Property"?prop.value:prop));default:return null}}function firstSensitive(nodes){for(const node of nodes){const found=findSensitiveData(node);if(found)return found}return null}const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!keyName||!propsToCheck.has(keyName))continue;const sensitiveVar=findSensitiveData(prop.value);if(sensitiveVar){context.report({node:prop.value,messageId:"sensitiveInPrompt",data:{variable:sensitiveVar}})}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitiveInPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");const records_1=require("../../utils/records");const type_info_1=require("../../utils/type-info");exports.noSensitiveInPrompt=(0,eslint_devkit_1.createRule)({name:"no-sensitive-in-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md",description:"Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts",cwe:"CWE-200",cvss:8},messages:{sensitiveInPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in LLM Prompt",cwe:"CWE-200",owasp:"A01:2021",cvss:8,description:'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',severity:"CRITICAL",compliance:["SOC2","GDPR","HIPAA","PCI-DSS"],fix:"Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{sensitivePatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest sensitive data"}},additionalProperties:false}]},defaultOptions:[{sensitivePatterns:["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{sensitivePatterns}=options;const sourceCode=context.sourceCode;function isSensitiveIdentifier(name){return sensitivePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSensitiveData(node){if(node===null)return null;switch(node.type){case"Identifier":return isSensitiveIdentifier(node.name)?node.name:null;case"MemberExpression":{const name=(0,eslint_devkit_1.propertyName)(node);return name!==null&&isSensitiveIdentifier(name)?sourceCode.getText(node):null}case"TemplateLiteral":for(const expr of node.expressions){const found=findSensitiveData(expr)??wholeRecord(expr);if(found)return found}return null;case"CallExpression":{const path=(0,eslint_devkit_1.memberPath)(node.callee)?.join(".");const[first]=node.arguments;if(path!=="JSON.stringify"&&path!=="String"||first===void 0)return null;return findSensitiveData(first)??wholeRecord(first)}case"BinaryExpression":return findSensitiveData(node.left)??findSensitiveData(node.right);case"ArrayExpression":return firstSensitive(node.elements);case"SpreadElement":return findSensitiveData(node.argument);case"ObjectExpression":return firstSensitive(node.properties.map(prop=>prop.type==="Property"?prop.value:prop));default:return null}}function wholeRecord(node){const label=sourceCode.getText(node);const names=(0,type_info_1.typePropertyNames)(node,sourceCode);if(names!==null)return names.some(isSensitiveIdentifier)?label:null;if(node.type==="MemberExpression")return null;return(0,flow_1.derivesFrom)(node,recordFlow)?label:null}const recordFlow={sourceCode,isSource:candidate=>candidate.type==="ObjectExpression"&&candidate.properties.some(prop=>prop.type==="Property"&&isSensitiveIdentifier((0,eslint_devkit_1.objectKeyName)(prop)??""))||(0,records_1.isDatabaseRowRead)(candidate,sourceCode)};function firstSensitive(nodes){for(const node of nodes){const found=findSensitiveData(node);if(found)return found}return null}const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!keyName||!propsToCheck.has(keyName))continue;const sensitiveVar=findSensitiveData(prop.value);if(sensitiveVar){context.report({node:prop.value,messageId:"sensitiveInPrompt",data:{variable:sensitiveVar}})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeOutputHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noUnsafeOutputHandling=(0,eslint_devkit_1.createRule)({name:"no-unsafe-output-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-unsafe-output-handling.md",description:"Prevent using AI output directly in dangerous operations (eval, SQL, HTML)",cwe:"CWE-94",cvss:9.8,confidence:"medium"},messages:{unsafeOutputExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output Used in Code Execution",cwe:"CWE-94",owasp:"A03:2021",cvss:9.8,description:'AI-generated content "{{variable}}" passed to {{function}}. This can lead to Remote Code Execution.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:"Never execute AI-generated code directly. Use sandboxed execution with validation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeOutputInSQL:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in SQL Query",cwe:"CWE-89",owasp:"A03:2021",cvss:9,description:"AI-generated content used in SQL query. Use parameterized queries instead.",severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:'Use parameterized queries: db.query("SELECT * FROM users WHERE id = ?", [aiOutput])',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"}),unsafeOutputInHTML:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in innerHTML",cwe:"CWE-79",owasp:"A03:2021",cvss:7.5,description:"AI-generated content assigned to innerHTML. This can lead to XSS attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use textContent or sanitize HTML: element.textContent = aiOutput",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"}),unsafeOutputInRequest:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI-Chosen Request URL",cwe:"CWE-918",owasp:"A10:2021",cvss:8.6,description:"Model-controlled value chooses the URL passed to fetch(). This can lead to Server-Side Request Forgery.",severity:"HIGH",compliance:["SOC2"],fix:"Fix the host and pass model input only as a path or query value, or check the URL against an allow-list",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{aiOutputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest AI output"}},additionalProperties:false}]},defaultOptions:[{aiOutputPatterns:["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput","llmResponse","modelOutput",".text"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{aiOutputPatterns}=options;const sourceCode=context.sourceCode;const aiBoundVariables=new Set;function isAIBound(node){if(node.type!=="Identifier")return false;for(const variable of aiBoundVariables){if(variable.references.some(r=>r.identifier===node))return true}return false}const AI_SDK_CALLS=new Set(["generateText","streamText","generateObject","streamObject"]);function isAISDKCall(node){let target=node;if(target.type==="AwaitExpression")target=target.argument;if(target.type!=="CallExpression")return false;const callee=target.callee;if(callee.type==="Identifier"&&AI_SDK_CALLS.has(callee.name))return true;if(callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),AI_SDK_CALLS))return true;return false}function isBoundOutput(node){if(isAIBound(node))return true;if(node.type==="MemberExpression")return isBoundOutput(node.object);return node.type==="CallExpression"&&node.callee.type==="MemberExpression"&&isBoundOutput(node.callee.object)}function isLikelyAIOutput(node){if(isBoundOutput(node))return true;if(node.type!=="Identifier"&&node.type!=="MemberExpression")return false;const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}function containsAIOutput(node){if(node.type==="TemplateLiteral"){return node.expressions.some(containsAIOutput)}if(node.type==="BinaryExpression"){return node.operator==="+"&&(containsAIOutput(node.left)||containsAIOutput(node.right))}return isLikelyAIOutput(node)}function isGlobal(node,name){return node.type==="Identifier"&&node.name===name&&!(0,sdk_1.lookupVariable)(name,sourceCode.getScope(node))?.defs.length}function isCodeExecutionCallee(callee){if(isGlobal(callee,"eval")||isGlobal(callee,"Function"))return true;const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,sourceCode.getScope(callee));if(binding){const fn=binding.path[binding.path.length-1];return binding.module==="child_process"&&CHILD_PROCESS_SINKS.has(fn)||binding.module==="vm"&&VM_SINKS.has(fn)}return callee.type==="Identifier"&&CHILD_PROCESS_SINKS.has(callee.name)&&!(0,sdk_1.lookupVariable)(callee.name,sourceCode.getScope(callee))?.defs.length}function isToolExecute(node){if((0,eslint_devkit_1.objectKeyName)(node)!=="execute")return false;if(node.value.type!=="ArrowFunctionExpression"&&node.value.type!=="FunctionExpression"){return false}const definition=node.parent;const owner=definition.parent;if(owner.type==="CallExpression"&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(owner.callee))){return true}return definition.properties.some(prop=>prop.type==="Property"&&SCHEMA_KEYS.has((0,eslint_devkit_1.objectKeyName)(prop)))}function choosesRequestTarget(node){if(node.type==="TemplateLiteral"){return node.quasis[0].value.raw===""&&isBoundOutput(node.expressions[0])}return isBoundOutput(node)}function reportExecution(node,callee,args){for(const arg of args){if(containsAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:sourceCode.getText(callee)}})}}}return{VariableDeclarator(node){if(!node.init)return;const init=(0,sdk_1.unwrap)(node.init);const bound=isAISDKCall(node.init)||init.type==="MemberExpression"&&(isBoundOutput(init.object)||isAISDKCall(init.object));if(!bound)return;for(const variable of sourceCode.getDeclaredVariables(node)){aiBoundVariables.add(variable)}},Property(node){if(!isToolExecute(node))return;for(const variable of sourceCode.getDeclaredVariables(node.value)){aiBoundVariables.add(variable)}},NewExpression(node){const isFunction=isGlobal(node.callee,"Function");const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));if(isFunction||binding?.module==="vm"&&binding.path[0]==="Script"){reportExecution(node,node.callee,node.arguments)}},CallExpression(node){if(isCodeExecutionCallee(node.callee)){reportExecution(node,node.callee,node.arguments);return}const name=(0,sdk_1.calleeName)(node.callee);if(isGlobal(node.callee,"fetch")){const[target]=node.arguments;if(target&&choosesRequestTarget(target)){context.report({node:target,messageId:"unsafeOutputInRequest"})}return}if(!SQL_SINKS.has(name))return;const[query]=node.arguments;if(query&&containsAIOutput(query)){context.report({node:query,messageId:"unsafeOutputInSQL"})}},AssignmentExpression(node){if(node.left.type==="MemberExpression"){const prop=node.left.property;if(prop.type==="Identifier"&&prop.name==="innerHTML"){if(isLikelyAIOutput(node.right)){context.report({node:node.right,messageId:"unsafeOutputInHTML"})}}}},JSXAttribute(node){if(node.name.name!=="dangerouslySetInnerHTML")return;const container=node.value;if(container?.type!==eslint_devkit_1.AST_NODE_TYPES.JSXExpressionContainer||container.expression.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return}for(const prop of container.expression.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)==="__html"&&isLikelyAIOutput(prop.value)){context.report({node:prop.value,messageId:"unsafeOutputInHTML"})}}}}}});const CHILD_PROCESS_SINKS=new Set(["exec","execSync","execFile","execFileSync","spawn","spawnSync","fork"]);const VM_SINKS=new Set(["runInNewContext","runInThisContext","runInContext","compileFunction"]);const SQL_SINKS=new Set(["query","execute","raw","run","unsafe","$queryRawUnsafe","$executeRawUnsafe"]);const TOOL_FACTORIES=new Set(["tool","dynamicTool"]);const SCHEMA_KEYS=new Set(["inputSchema","parameters"]);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeOutputHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.noUnsafeOutputHandling=(0,eslint_devkit_1.createRule)({name:"no-unsafe-output-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-unsafe-output-handling.md",description:"Prevent using AI output directly in dangerous operations (eval, SQL, HTML)",cwe:"CWE-94",cvss:9.8,confidence:"medium"},messages:{unsafeOutputExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output Used in Code Execution",cwe:"CWE-94",owasp:"A03:2021",cvss:9.8,description:'AI-generated content "{{variable}}" passed to {{function}}. This can lead to Remote Code Execution.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:"Never execute AI-generated code directly. Use sandboxed execution with validation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeOutputInSQL:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in SQL Query",cwe:"CWE-89",owasp:"A03:2021",cvss:9,description:"AI-generated content used in SQL query. Use parameterized queries instead.",severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:'Use parameterized queries: db.query("SELECT * FROM users WHERE id = ?", [aiOutput])',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"}),unsafeOutputInHTML:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in innerHTML",cwe:"CWE-79",owasp:"A03:2021",cvss:7.5,description:"AI-generated content assigned to innerHTML. This can lead to XSS attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use textContent or sanitize HTML: element.textContent = aiOutput",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"}),unsafeOutputInRequest:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI-Chosen Request URL",cwe:"CWE-918",owasp:"A10:2021",cvss:8.6,description:"Model-controlled value chooses the URL passed to fetch(). This can lead to Server-Side Request Forgery.",severity:"HIGH",compliance:["SOC2"],fix:"Fix the host and pass model input only as a path or query value, or check the URL against an allow-list",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{aiOutputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest AI output"}},additionalProperties:false}]},defaultOptions:[{aiOutputPatterns:["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput","llmResponse","modelOutput",".text"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{aiOutputPatterns}=options;const sourceCode=context.sourceCode;const AI_SDK_CALLS=new Set(["generateText","streamText","generateObject","streamObject"]);function isAISDKCall(call){const callee=call.callee;if(callee.type==="Identifier")return AI_SDK_CALLS.has(callee.name);return callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),AI_SDK_CALLS)}const outputFlow={sourceCode,isSource:node=>node.type==="CallExpression"&&isAISDKCall(node)||(0,sdk_1.isSdkHookCall)(node,sourceCode),isSourceParameter:(variable,fn)=>fn.parent.type==="Property"&&isToolExecute(fn.parent)||(0,sdk_1.isSdkTypedParameter)(variable,sourceCode)};function isBoundOutput(node){return(0,flow_1.derivesFrom)(node,outputFlow)}function isLikelyAIOutput(node){if(isBoundOutput(node))return true;if(node.type!=="Identifier"&&node.type!=="MemberExpression")return false;const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}function containsAIOutput(node){if(node.type==="TemplateLiteral"){return node.expressions.some(containsAIOutput)}if(node.type==="BinaryExpression"){return node.operator==="+"&&(containsAIOutput(node.left)||containsAIOutput(node.right))}return isLikelyAIOutput(node)}function isGlobal(node,name){return node.type==="Identifier"&&node.name===name&&!(0,sdk_1.lookupVariable)(name,sourceCode.getScope(node))?.defs.length}function isCodeExecutionCallee(callee){if(isGlobal(callee,"eval")||isGlobal(callee,"Function"))return true;const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,sourceCode.getScope(callee));if(binding){const fn=binding.path[binding.path.length-1];return binding.module==="child_process"&&CHILD_PROCESS_SINKS.has(fn)||binding.module==="vm"&&VM_SINKS.has(fn)}return callee.type==="Identifier"&&CHILD_PROCESS_SINKS.has(callee.name)&&!(0,sdk_1.lookupVariable)(callee.name,sourceCode.getScope(callee))?.defs.length}function isToolExecute(node){if((0,eslint_devkit_1.objectKeyName)(node)!=="execute")return false;const definition=node.parent;const owner=definition.parent;if(owner.type==="CallExpression"&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(owner.callee))){return true}return definition.properties.some(prop=>prop.type==="Property"&&SCHEMA_KEYS.has((0,eslint_devkit_1.objectKeyName)(prop)))}function choosesRequestTarget(node){if(node.type==="TemplateLiteral"){return node.quasis[0].value.raw===""&&isBoundOutput(node.expressions[0])}return isBoundOutput(node)}function reportExecution(node,callee,args){for(const arg of args){if(containsAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:sourceCode.getText(callee)}})}}}return{NewExpression(node){const isFunction=isGlobal(node.callee,"Function");const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));if(isFunction||binding?.module==="vm"&&binding.path[0]==="Script"){reportExecution(node,node.callee,node.arguments)}},CallExpression(node){if(isCodeExecutionCallee(node.callee)){reportExecution(node,node.callee,node.arguments);return}const name=(0,sdk_1.calleeName)(node.callee);if(isGlobal(node.callee,"fetch")){const[target]=node.arguments;if(target&&choosesRequestTarget(target)){context.report({node:target,messageId:"unsafeOutputInRequest"})}return}if(!SQL_SINKS.has(name))return;const[query]=node.arguments;if(query&&containsAIOutput(query)){context.report({node:query,messageId:"unsafeOutputInSQL"})}},AssignmentExpression(node){if(node.left.type==="MemberExpression"){const prop=node.left.property;if(prop.type==="Identifier"&&prop.name==="innerHTML"){if(isLikelyAIOutput(node.right)){context.report({node:node.right,messageId:"unsafeOutputInHTML"})}}}},JSXAttribute(node){if(node.name.name!=="dangerouslySetInnerHTML")return;const container=node.value;if(container?.type!==eslint_devkit_1.AST_NODE_TYPES.JSXExpressionContainer||container.expression.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return}for(const prop of container.expression.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)==="__html"&&isLikelyAIOutput(prop.value)){context.report({node:prop.value,messageId:"unsafeOutputInHTML"})}}}}}});const CHILD_PROCESS_SINKS=new Set(["exec","execSync","execFile","execFileSync","spawn","spawnSync","fork"]);const VM_SINKS=new Set(["runInNewContext","runInThisContext","runInContext","compileFunction"]);const SQL_SINKS=new Set(["query","execute","raw","run","unsafe","$queryRawUnsafe","$executeRawUnsafe"]);const TOOL_FACTORIES=new Set(["tool","dynamicTool"]);const SCHEMA_KEYS=new Set(["inputSchema","parameters"]);
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireMaxSteps=(0,eslint_devkit_1.createRule)({name:"require-max-steps",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-steps.md",description:"Disallow unbounded multi-step tool loops (hasToolCall-only stop conditions, request-controlled step limits)",cwe:"CWE-834",cvss:6.5},messages:{unboundedSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unbounded Multi-Step Tool Loop",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools sets a step limit that does not bound the loop ({{reason}}).",severity:"MEDIUM",compliance:["SOC2"],fix:"Bound the loop with a server-side constant: stopWhen: [hasToolCall(...), stepCountIs(20)] (v5+) or maxSteps: 20 (v4)",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen"})},schema:[{type:"object",properties:{suggestedMaxSteps:{type:"number",description:"Default max steps limit to suggest",default:5}},additionalProperties:false}]},defaultOptions:[{suggestedMaxSteps:5}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const sourceCode=context.sourceCode;function unboundedReason(key,value){const scope=sourceCode.getScope(value);if((0,sdk_1.isRequestDerived)(value,scope))return`${key} comes from the request`;if(value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&value.name==="Infinity"){return`${key} is Infinity`}if(key!=="stopWhen")return null;const conditions=value.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression?value.elements:[value];const named=conditions.map(c=>c?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression?(0,sdk_1.calleeName)(c.callee):null);if(named.every(name=>name==="hasToolCall")){return"hasToolCall only stops if the model calls that tool"}for(const condition of conditions){if(condition?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&STEP_COUNTERS.has((0,sdk_1.calleeName)(condition.callee))&&condition.arguments.some(arg=>(0,sdk_1.isRequestDerived)(arg,scope))){return"the step count comes from the request"}}return null}return{CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,FUNCTIONS_WITH_TOOLS);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(optionsArg?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;if(!(0,sdk_1.optionValue)(optionsArg,"tools"))return;for(const key of STEP_KEYS){const value=(0,sdk_1.optionValue)(optionsArg,key);const reason=value&&unboundedReason(key,value);if(reason){context.report({node:value,messageId:"unboundedSteps",data:{function:matchedFunction,reason}})}}}}}});const FUNCTIONS_WITH_TOOLS=["generateText","streamText"];const STEP_KEYS=["maxSteps","max_steps","stopWhen"];const STEP_COUNTERS=new Set(["stepCountIs","isStepCount"]);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireMaxSteps=(0,eslint_devkit_1.createRule)({name:"require-max-steps",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-steps.md",description:"Disallow unbounded multi-step tool loops (hasToolCall-only stop conditions, request-controlled step limits)",cwe:"CWE-834",cvss:6.5},messages:{unboundedSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unbounded Multi-Step Tool Loop",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools sets a step limit that does not bound the loop ({{reason}}).",severity:"MEDIUM",compliance:["SOC2"],fix:"Bound the loop with a server-side constant: stopWhen: [hasToolCall(...), stepCountIs(20)] (v5+) or maxSteps: 20 (v4)",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen"})},schema:[{type:"object",properties:{suggestedMaxSteps:{type:"number",description:"Default max steps limit to suggest",default:5}},additionalProperties:false}]},defaultOptions:[{suggestedMaxSteps:5}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const sourceCode=context.sourceCode;function unboundedReason(key,value){if((0,sdk_1.isRequestDerived)(value,sourceCode))return`${key} comes from the request`;if(value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&value.name==="Infinity"){return`${key} is Infinity`}if(key!=="stopWhen")return null;const conditions=value.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression?value.elements:[value];const named=conditions.map(c=>c?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression?(0,sdk_1.calleeName)(c.callee):null);if(named.every(name=>name==="hasToolCall")){return"hasToolCall only stops if the model calls that tool"}for(const condition of conditions){if(condition?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&STEP_COUNTERS.has((0,sdk_1.calleeName)(condition.callee))&&condition.arguments.some(arg=>(0,sdk_1.isRequestDerived)(arg,sourceCode))){return"the step count comes from the request"}}return null}return{CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,FUNCTIONS_WITH_TOOLS);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(optionsArg?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;if(!(0,sdk_1.optionValue)(optionsArg,"tools"))return;for(const key of STEP_KEYS){const value=(0,sdk_1.optionValue)(optionsArg,key);const reason=value&&unboundedReason(key,value);if(reason){context.report({node:value,messageId:"unboundedSteps",data:{function:matchedFunction,reason}})}}}}}});const FUNCTIONS_WITH_TOOLS=["generateText","streamText"];const STEP_KEYS=["maxSteps","max_steps","stopWhen"];const STEP_COUNTERS=new Set(["stepCountIs","isStepCount"]);
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRagContentValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireRagContentValidation=(0,eslint_devkit_1.createRule)({name:"require-rag-content-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-rag-content-validation.md",description:"Require validation of RAG content before including in AI prompts",cwe:"CWE-74",cvss:6},messages:{unsanitizedRagContent:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsanitized RAG Content",cwe:"CWE-74",owasp:"A03:2021",cvss:6,description:'RAG content from "{{source}}" used directly in prompt without validation. Poisoned documents can inject malicious instructions.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate RAG content: prompt: buildPrompt(validateRagContent(docs))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{ragPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting RAG operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate RAG content"}},additionalProperties:false}]},defaultOptions:[{ragPatterns:["search","retrieve","query","vectorStore","embeddings","similaritySearch","findSimilar","getDocuments","fetchDocs","documents","chunks","passages"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{ragPatterns,validatorFunctions}=options;const sourceCode=context.sourceCode;const ragVariables=new Set;function isRagCall(node){if(node.type!=="CallExpression")return null;const chain=(0,sdk_1.calleeChain)(node.callee);return ragPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))?sourceCode.getText(node.callee):null}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}function containsRagContent(node){if(node.type==="Identifier"&&ragVariables.has(node.name)){return node.name}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const rag=containsRagContent(expr);if(rag)return rag}}if(node.type==="AwaitExpression"){return containsRagContent(node.argument)}if(node.type==="CallExpression"){const ragSource=isRagCall(node);if(ragSource&&!isValidated(node)){return ragSource}}return null}return{VariableDeclarator(node){if(node.id.type!=="Identifier")return;if(!node.init)return;let initNode=node.init;if(initNode.type==="AwaitExpression"){initNode=initNode.argument}if(isRagCall(initNode)){ragVariables.add(node.id.name)}},CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(keyName!=="prompt"&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const ragSource=containsRagContent(prop.value);if(ragSource){context.report({node:prop.value,messageId:"unsanitizedRagContent",data:{source:ragSource}})}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRagContentValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.requireRagContentValidation=(0,eslint_devkit_1.createRule)({name:"require-rag-content-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-rag-content-validation.md",description:"Require validation of RAG content before including in AI prompts",cwe:"CWE-74",cvss:6},messages:{unsanitizedRagContent:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsanitized RAG Content",cwe:"CWE-74",owasp:"A03:2021",cvss:6,description:'RAG content from "{{source}}" used directly in prompt without validation. Poisoned documents can inject malicious instructions.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate RAG content: prompt: buildPrompt(validateRagContent(docs))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{ragPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting RAG operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate RAG content"}},additionalProperties:false}]},defaultOptions:[{ragPatterns:["search","retrieve","query","vectorStore","embeddings","similaritySearch","findSimilar","getDocuments","fetchDocs","documents","chunks","passages"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{ragPatterns,validatorFunctions}=options;const sourceCode=context.sourceCode;function isRagCall(node){if(node.type!=="CallExpression"||(0,flow_1.isDerivationCall)(node))return false;const chain=(0,sdk_1.calleeChain)(node.callee);return ragPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))&&!isValidated(node)}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}const ragFlow={sourceCode,isSource:isRagCall,isBarrier:node=>node.type==="CallExpression"&&!(0,flow_1.isDerivationCall)(node)&&isValidated(node)};function containsRagContent(node){const parts=node.type==="TemplateLiteral"?node.expressions:[node];const found=parts.find(part=>(0,flow_1.derivesFrom)(part,ragFlow));return found?sourceCode.getText(found):null}return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(keyName!=="prompt"&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const ragSource=containsRagContent(prop.value);if(ragSource){context.report({node:prop.value,messageId:"unsanitizedRagContent",data:{source:ragSource}})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md",description:"Require validated/sanitized prompts in generateText and streamText calls",cwe:"CWE-74",cvss:9},messages:{unsafePrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Prompt in Vercel AI SDK",cwe:"CWE-74",owasp:"A03:2021",cvss:9,description:'User input "{{input}}" passed directly to {{function}} prompt without validation',severity:"CRITICAL",compliance:["SOC2","GDPR"],fix:"Validate input before use: generateText({ prompt: validateInput(userInput) })",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8.5,description:"Dynamic value in system prompt can lead to prompt injection",severity:"HIGH",fix:"Use static system prompts or validate dynamic content",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{validatorFunctions:{type:"array",items:{type:"string"},description:"Function names considered as input validators"},userInputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest user input (regex)"},allowInTests:{type:"boolean",description:"Allow unsafe patterns in test files"}},additionalProperties:false}]},defaultOptions:[{validatorFunctions:["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"],userInputPatterns:["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"],allowInTests:false}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{validatorFunctions,userInputPatterns,allowInTests}=options;const sourceCode=context.sourceCode;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return{}}const pathPatterns=userInputPatterns.filter(p=>p.includes(".")).map(p=>p.split("."));const namePatterns=userInputPatterns.filter(p=>!p.includes("."));function isValidatedCall(node){const target=(0,sdk_1.unwrap)(node);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const name=(0,sdk_1.calleeName)(target.callee);return validatorFunctions.includes(name)||SCHEMA_PARSERS.has(name)}function isValidatedBinding(node){const def=(0,sdk_1.lookupVariable)(node.name,sourceCode.getScope(node))?.defs[0];return def?.type==="Variable"&&def.node.init!==null&&isValidatedCall(def.node.init)}function nameLooksLikeInput(name){return namePatterns.some(pattern=>(0,sdk_1.nameEndsWithWords)(name,pattern))}function pathLooksLikeInput(node){const path=(0,eslint_devkit_1.memberPath)(node);return path!==null&&pathPatterns.some(pattern=>pattern.every((part,i)=>path[i]===part))}function findUserInput(node){if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const found=findUserInput(expr);if(found)return found}return null}if(node.type==="BinaryExpression"){return findUserInput(node.left)??findUserInput(node.right)}if(node.type==="Identifier"){if(isValidatedBinding(node))return null;const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode.getScope(node))||nameLooksLikeInput(node.name);return unsafe?node.name:null}if(node.type==="MemberExpression"){const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode.getScope(node))||pathLooksLikeInput(node)||nameLooksLikeInput((0,eslint_devkit_1.propertyName)(node)??"");return unsafe?sourceCode.getText(node):null}return null}return{CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);const isPrompt=keyName==="prompt";if(!isPrompt&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const input=findUserInput(prop.value);if(!input)continue;context.report(isPrompt?{node:prop.value,messageId:"unsafePrompt",data:{input,function:matchedFunction}}:{node:prop.value,messageId:"unsafeSystemPrompt"})}}}}});const SCHEMA_PARSERS=new Set(["parse","safeParse","parseAsync","safeParseAsync","validateSync"]);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md",description:"Require validated/sanitized prompts in generateText and streamText calls",cwe:"CWE-74",cvss:9},messages:{unsafePrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Prompt in Vercel AI SDK",cwe:"CWE-74",owasp:"A03:2021",cvss:9,description:'User input "{{input}}" passed directly to {{function}} prompt without validation',severity:"CRITICAL",compliance:["SOC2","GDPR"],fix:"Validate input before use: generateText({ prompt: validateInput(userInput) })",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8.5,description:"Dynamic value in system prompt can lead to prompt injection",severity:"HIGH",fix:"Use static system prompts or validate dynamic content",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{validatorFunctions:{type:"array",items:{type:"string"},description:"Function names considered as input validators"},userInputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest user input (regex)"},allowInTests:{type:"boolean",description:"Allow unsafe patterns in test files"}},additionalProperties:false}]},defaultOptions:[{validatorFunctions:["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"],userInputPatterns:["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"],allowInTests:false}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{validatorFunctions,userInputPatterns,allowInTests}=options;const sourceCode=context.sourceCode;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return{}}const pathPatterns=userInputPatterns.filter(p=>p.includes(".")).map(p=>p.split("."));const namePatterns=userInputPatterns.filter(p=>!p.includes("."));function isValidatedCall(node){const target=(0,sdk_1.unwrap)(node);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const name=(0,sdk_1.calleeName)(target.callee);return validatorFunctions.includes(name)||SCHEMA_PARSERS.has(name)}function isValidatedBinding(node){const def=(0,sdk_1.lookupVariable)(node.name,sourceCode.getScope(node))?.defs[0];return def?.type==="Variable"&&def.node.init!==null&&isValidatedCall(def.node.init)}function nameLooksLikeInput(name){return namePatterns.some(pattern=>(0,sdk_1.nameEndsWithWords)(name,pattern))}function pathLooksLikeInput(node){const path=(0,eslint_devkit_1.memberPath)(node);return path!==null&&pathPatterns.some(pattern=>pattern.every((part,i)=>path[i]===part))}function findUserInput(node){if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const found=findUserInput(expr);if(found)return found}return null}if(node.type==="BinaryExpression"){return findUserInput(node.left)??findUserInput(node.right)}if(node.type==="Identifier"){if(isValidatedBinding(node))return null;const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode)||nameLooksLikeInput(node.name);return unsafe?node.name:null}if(node.type==="MemberExpression"){const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode)||pathLooksLikeInput(node)||nameLooksLikeInput((0,eslint_devkit_1.propertyName)(node)??"");return unsafe?sourceCode.getText(node):null}return null}return{CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);const isPrompt=keyName==="prompt";if(!isPrompt&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const input=findUserInput(prop.value);if(!input)continue;context.report(isPrompt?{node:prop.value,messageId:"unsafePrompt",data:{input,function:matchedFunction}}:{node:prop.value,messageId:"unsafeSystemPrompt"})}}}}});const SCHEMA_PARSERS=new Set(["parse","safeParse","parseAsync","safeParseAsync","validateSync"]);
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.lookupVariable=lookupVariable;exports.isDerivationCall=isDerivationCall;exports.sameFileFunction=sameFileFunction;exports.returnsOf=returnsOf;exports.derivesFrom=derivesFrom;const eslint_devkit_1=require("@interlace/eslint-devkit");const MAX_DEPTH=16;const MAX_STEPS=4e3;const DERIVATIONS=new Set(["map","flatMap","filter","slice","flat","join","concat","reduce","find","findLast","at","sort","toSorted","reverse","toString","trim","trimStart","trimEnd","toLowerCase","toUpperCase","replace","replaceAll","split","substring","padStart","padEnd","match","normalize"]);const ELEMENT_CALLBACKS=new Set(["map","flatMap","filter","forEach","find","findLast","some","every","reduce"]);const WRAPPERS=new Set(["JSON.stringify","String","Number","parseInt","parseFloat","Array.from","Object.values","Object.entries"]);function lookupVariable(name,scope){for(let current=scope;current;current=current.upper){const variable=current.set.get(name);if(variable)return variable}return void 0}function isFunction(node){return node?.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||node?.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||node?.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression}function isDerivationCall(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&DERIVATIONS.has((0,eslint_devkit_1.propertyName)(node.callee))}function sameFileFunction(callee,sourceCode){if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;const def=lookupVariable(callee.name,sourceCode.getScope(callee))?.defs[0];if(def?.type==="FunctionName")return def.node;if(def?.type==="Variable"&&isFunction(def.node.init))return def.node.init;return null}function returnsOf(fn,sourceCode){if(fn.body.type!==eslint_devkit_1.AST_NODE_TYPES.BlockStatement)return[fn.body];const found=[];const visit=node=>{if(isFunction(node))return;if(node.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&node.argument)found.push(node.argument);for(const key of sourceCode.visitorKeys[node.type]){const child=node[key];for(const item of Array.isArray(child)?child:[child]){if(item&&typeof item.type==="string")visit(item)}}};for(const statement of fn.body.body)visit(statement);return found}function contains(outer,inner){return outer.range[0]<=inner.range[0]&&inner.range[1]<=outer.range[1]}function functionVariable(fn,sourceCode){const parent=fn.parent;const id=fn.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration?fn.id:parent.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?parent.id:null;return id?lookupVariable(id.name,sourceCode.getScope(fn)):void 0}const EMPTY_ENV=new Map;function derivesFrom(node,flow){const{sourceCode}=flow;let steps=0;const onPath=new Set;function walk(current,depth,env){if(depth>MAX_DEPTH||++steps>MAX_STEPS)return false;if(flow.isSource(current))return true;if(flow.isBarrier?.(current))return false;const next=child=>child!==null&&walk(child,depth+1,env);switch(current.type){case eslint_devkit_1.AST_NODE_TYPES.AwaitExpression:case eslint_devkit_1.AST_NODE_TYPES.SpreadElement:return next(current.argument);case eslint_devkit_1.AST_NODE_TYPES.TSAsExpression:case eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression:case eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression:case eslint_devkit_1.AST_NODE_TYPES.ChainExpression:return next(current.expression);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return current.expressions.some(next);case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:case eslint_devkit_1.AST_NODE_TYPES.LogicalExpression:return next(current.left)||next(current.right);case eslint_devkit_1.AST_NODE_TYPES.ConditionalExpression:return next(current.consequent)||next(current.alternate);case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return next(current.object);case eslint_devkit_1.AST_NODE_TYPES.ArrayExpression:return current.elements.some(next);case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return current.properties.some(prop=>next(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property?prop.value:prop));case eslint_devkit_1.AST_NODE_TYPES.CallExpression:return fromCall(current,depth,env);case eslint_devkit_1.AST_NODE_TYPES.Identifier:return fromIdentifier(current,depth,env);default:return false}}function fromCall(call,depth,env){const callee=call.callee;if(isDerivationCall(call)){return walk(callee.object,depth+1,env)}const path=(0,eslint_devkit_1.memberPath)(callee);if(path!==null&&WRAPPERS.has(path.join("."))){const[first]=call.arguments;return first!==void 0&&walk(first,depth+1,env)}const fn=sameFileFunction(callee,sourceCode);if(!fn)return false;const inner=new Map;for(const variable of sourceCode.getDeclaredVariables(fn)){const index=fn.params.findIndex(param=>contains(param,variable.defs[0].name));const arg=call.arguments[index];if(arg)inner.set(variable,{node:arg,env})}return returnsOf(fn,sourceCode).some(expr=>walk(expr,depth+1,inner))}function fromIdentifier(id,depth,env){const variable=lookupVariable(id.name,sourceCode.getScope(id));if(!variable)return false;const bound=env.get(variable);if(bound)return walk(bound.node,depth+1,bound.env);if(onPath.has(variable))return false;onPath.add(variable);let found=variable.references.some(ref=>ref.writeExpr!=null&&walk(ref.writeExpr,depth+1,EMPTY_ENV));const def=variable.defs[0];if(!found&&def?.type==="Parameter"){found=fromParameter(variable,def.node,depth)}onPath.delete(variable);return found}function fromParameter(variable,fn,depth){if(flow.isSourceParameter?.(variable,fn))return true;const name=variable.defs[0].name;const index=fn.params.findIndex(param2=>contains(param2,name));const param=fn.params[index];const key=param.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern&&name.parent.type===eslint_devkit_1.AST_NODE_TYPES.Property?(0,eslint_devkit_1.objectKeyName)(name.parent):null;const owner=fn.parent;if(owner.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&owner.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&ELEMENT_CALLBACKS.has((0,eslint_devkit_1.propertyName)(owner.callee))){return walk(owner.callee.object,depth+1,EMPTY_ENV)}const fnVariable=functionVariable(fn,sourceCode);return(fnVariable?.references??[]).some(ref=>{const site=ref.identifier.parent;if(site.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&site.callee===ref.identifier){const arg=site.arguments[index];return arg!==void 0&&walk(arg,depth+1,EMPTY_ENV)}if(site.type===eslint_devkit_1.AST_NODE_TYPES.JSXOpeningElement&&index===0){return site.attributes.some(attr=>{if(attr.type===eslint_devkit_1.AST_NODE_TYPES.JSXSpreadAttribute){return walk(attr.argument,depth+1,EMPTY_ENV)}return(key===null||attr.name.name===key)&&attr.value?.type===eslint_devkit_1.AST_NODE_TYPES.JSXExpressionContainer&&attr.value.expression.type!==eslint_devkit_1.AST_NODE_TYPES.JSXEmptyExpression&&walk(attr.value.expression,depth+1,EMPTY_ENV)})}return false})}return walk(node,0,EMPTY_ENV)}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.isDatabaseRowRead=isDatabaseRowRead;const eslint_devkit_1=require("@interlace/eslint-devkit");const sdk_1=require("./sdk");const flow_1=require("./flow");const DATABASE_MODULES=new Set(["@prisma/client","pg","postgres","mysql2","mysql2/promise","knex","kysely","@vercel/postgres","better-sqlite3"]);function isDatabaseModule(module){return DATABASE_MODULES.has(module)||module.startsWith("drizzle-orm")}function isDatabaseHandle(root,sourceCode){if(root.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;const scope=sourceCode.getScope(root);const imported=(0,eslint_devkit_1.resolveModuleBinding)(root,scope);if(imported)return isDatabaseModule(imported.module);const def=(0,flow_1.lookupVariable)(root.name,scope)?.defs[0];if(def?.type!=="Variable"||def.node.init===null)return false;let init=def.node.init;if(init.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression)init=init.argument;if(init.type!==eslint_devkit_1.AST_NODE_TYPES.NewExpression&&init.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression){return false}const factory=(0,eslint_devkit_1.resolveModuleBinding)(init.callee,sourceCode.getScope(init));return factory!==void 0&&isDatabaseModule(factory.module)}function isProjection(call){const[first]=call.arguments;if(first?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression&&((0,sdk_1.optionValue)(first,"select")!==void 0||(0,sdk_1.optionValue)(first,"omit")!==void 0)){return true}if((0,sdk_1.calleeName)(call.callee)==="select"&&first!==void 0){return call.arguments.some(arg=>(0,eslint_devkit_1.staticString)(arg)!=="*")}const sql=first===void 0?null:(0,eslint_devkit_1.staticString)(first);return sql!==null&&/^\s*select\b/i.test(sql)&&!/^\s*select\s+\*/i.test(sql)}function isDatabaseRowRead(node,sourceCode){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const calls=[];let current=node;for(;;){if(current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){calls.push(current);current=current.callee}else if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}else{break}}return isDatabaseHandle(current,sourceCode)&&!calls.some(isProjection)}
package/src/utils/sdk.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AI_SDK_CALLS=void 0;exports.calleeName=calleeName;exports.sdkCallName=sdkCallName;exports.declaresOption=declaresOption;exports.optionValue=optionValue;exports.unwrap=unwrap;exports.lookupVariable=lookupVariable;exports.isRequestDerived=isRequestDerived;exports.isStaticText=isStaticText;exports.isDateValue=isDateValue;exports.nameEndsWithWords=nameEndsWithWords;exports.calleeChain=calleeChain;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.AI_SDK_CALLS=["generateText","streamText","generateObject","streamObject"];function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return(0,eslint_devkit_1.propertyName)(callee);return null}function sdkCallName(node,names=exports.AI_SDK_CALLS){const name=calleeName(node.callee);return name!==null&&names.includes(name)?name:null}function declaresOption(options,keys){return options.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?true:keys.includes((0,eslint_devkit_1.objectKeyName)(prop)))}function optionValue(options,key){for(const prop of options.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)===key){return prop.value}}return void 0}function unwrap(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression){current=current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression?current.argument:current.expression}return current}function lookupVariable(name,scope){for(let current=scope;current;current=current.upper){const variable=current.set.get(name);if(variable)return variable}return void 0}function isParameter(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return lookupVariable(node.name,scope)?.defs[0]?.type==="Parameter"}function isRequestBody(node,scope){const target=unwrap(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=target.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const method=(0,eslint_devkit_1.propertyName)(callee);if((method==="get"||method==="getAll")&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee.object)==="searchParams"){return true}return["json","formData","text"].includes(method)&&isParameter(callee.object,scope)}return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(target)==="body"&&isParameter(target.object,scope)}function isRequestDerived(node,scope){const target=unwrap(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return target.expressions.some(expr=>isRequestDerived(expr,scope))}if(target.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression||target.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression){return isRequestDerived(target.left,scope)||isRequestDerived(target.right,scope)}if(target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return isRequestBody(target,scope)||isRequestDerived(target.object,scope)}if(target.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return isRequestBody(target,scope);const def=lookupVariable(target.name,scope)?.defs[0];if(def?.type!=="Variable"||def.node.init===null)return false;const init=unwrap(def.node.init);return init.type===eslint_devkit_1.AST_NODE_TYPES.LogicalExpression?isRequestBody(init.left,scope):isRequestBody(init,scope)}function isDateValue(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression){return node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="Date"}if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return false}const receiver=node.callee.object;return receiver.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&receiver.name==="Date"||isDateValue(receiver)}function isStaticText(node,scope,resolving=new Set){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return true;case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.every(expr=>isStaticText(expr,scope,resolving));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return node.operator==="+"&&isStaticText(node.left,scope,resolving)&&isStaticText(node.right,scope,resolving);case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=lookupVariable(node.name,scope);const def=variable?.defs[0];if(def?.type!=="Variable"||def.parent.kind!=="const"||def.node.init===null||resolving.has(variable)){return false}resolving.add(variable);const result=isStaticText(def.node.init,scope,resolving);resolving.delete(variable);return result}default:return isDateValue(node)}}function nameEndsWithWords(name,term){const words=(0,eslint_devkit_1.identifierWords)(name);const tail=(0,eslint_devkit_1.identifierWords)(term);const offset=words.length-tail.length;return offset>=0&&tail.every((word,i)=>words[offset+i]===word)}function calleeChain(callee){const parts=[];let current=unwrap(callee);while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){parts.unshift((0,eslint_devkit_1.propertyName)(current)??"");current=unwrap(current.object)}else{current=unwrap(current.callee)}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)parts.unshift(current.name);return parts.join(".")}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.lookupVariable=exports.AI_SDK_CALLS=void 0;exports.calleeName=calleeName;exports.sdkCallName=sdkCallName;exports.declaresOption=declaresOption;exports.optionValue=optionValue;exports.unwrap=unwrap;exports.isRequestDerived=isRequestDerived;exports.isStaticText=isStaticText;exports.isDateValue=isDateValue;exports.nameEndsWithWords=nameEndsWithWords;exports.calleeChain=calleeChain;exports.isSdkHookCall=isSdkHookCall;exports.isSdkTypedParameter=isSdkTypedParameter;const eslint_devkit_1=require("@interlace/eslint-devkit");const flow_1=require("./flow");exports.AI_SDK_CALLS=["generateText","streamText","generateObject","streamObject"];function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return(0,eslint_devkit_1.propertyName)(callee);return null}function sdkCallName(node,names=exports.AI_SDK_CALLS){const name=calleeName(node.callee);return name!==null&&names.includes(name)?name:null}function declaresOption(options,keys){return options.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?true:keys.includes((0,eslint_devkit_1.objectKeyName)(prop)))}function optionValue(options,key){for(const prop of options.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)===key){return prop.value}}return void 0}function unwrap(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression){current=current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression?current.argument:current.expression}return current}var flow_2=require("./flow");Object.defineProperty(exports,"lookupVariable",{enumerable:true,get:function(){return flow_2.lookupVariable}});function isParameter(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,flow_1.lookupVariable)(node.name,scope)?.defs[0]?.type==="Parameter"}function isRequestBody(node,scope){const target=unwrap(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=target.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const method=(0,eslint_devkit_1.propertyName)(callee);if((method==="get"||method==="getAll")&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee.object)==="searchParams"){return true}return["json","formData","text"].includes(method)&&isParameter(callee.object,scope)}return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(target)==="body"&&isParameter(target.object,scope)}function isRequestDerived(node,sourceCode){return(0,flow_1.derivesFrom)(node,{sourceCode,isSource:candidate=>isRequestBody(candidate,sourceCode.getScope(candidate))})}function isDateValue(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression){return node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="Date"}if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return false}const receiver=node.callee.object;return receiver.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&receiver.name==="Date"||isDateValue(receiver)}function isStaticText(node,sourceCode,resolving=new Set){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return true;case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.every(expr=>isStaticText(expr,sourceCode,resolving));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return node.operator==="+"&&isStaticText(node.left,sourceCode,resolving)&&isStaticText(node.right,sourceCode,resolving);case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,flow_1.lookupVariable)(node.name,sourceCode.getScope(node));const def=variable?.defs[0];if(def?.type!=="Variable"||def.parent.kind!=="const"||def.node.init===null||resolving.has(variable)){return false}resolving.add(variable);const result=isStaticText(def.node.init,sourceCode,resolving);resolving.delete(variable);return result}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const fn=(0,flow_1.sameFileFunction)(node.callee,sourceCode);if(!fn||resolving.has(fn))return isDateValue(node);resolving.add(fn);const returns=(0,flow_1.returnsOf)(fn,sourceCode);const result=returns.length>0&&returns.every(expr=>isStaticText(expr,sourceCode,resolving));resolving.delete(fn);return result}default:return isDateValue(node)}}function nameEndsWithWords(name,term){const words=(0,eslint_devkit_1.identifierWords)(name);const tail=(0,eslint_devkit_1.identifierWords)(term);const offset=words.length-tail.length;return offset>=0&&tail.every((word,i)=>words[offset+i]===word)}function calleeChain(callee){const parts=[];let current=unwrap(callee);while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){parts.unshift((0,eslint_devkit_1.propertyName)(current)??"");current=unwrap(current.object)}else{current=unwrap(current.callee)}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)parts.unshift(current.name);return parts.join(".")}function isSdkModule(module){return module==="ai"||module.startsWith("ai/")||module.startsWith("@ai-sdk/")}const SDK_UI_HOOKS=new Set(["useChat","useCompletion","useObject","useAssistant"]);function isSdkHookCall(node,sourceCode){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));return binding!==void 0&&isSdkModule(binding.module)&&SDK_UI_HOOKS.has(binding.path[binding.path.length-1])}function referencesSdkType(node,sourceCode,seen=new Set){if(seen.has(node))return false;seen.add(node);if(node.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference){let name=node.typeName;while(name.type===eslint_devkit_1.AST_NODE_TYPES.TSQualifiedName)name=name.left;const id=name;const binding=(0,eslint_devkit_1.resolveModuleBinding)(id,sourceCode.getScope(node));if(binding&&isSdkModule(binding.module))return true;const alias=typeDeclaration(id,sourceCode);if(alias&&referencesSdkType(alias,sourceCode,seen))return true}for(const key of sourceCode.visitorKeys[node.type]){const child=node[key];for(const item of Array.isArray(child)?child:[child]){if(item&&typeof item.type==="string"&&referencesSdkType(item,sourceCode,seen)){return true}}}return false}function typeDeclaration(id,sourceCode){const def=(0,flow_1.lookupVariable)(id.name,sourceCode.getScope(id))?.defs[0];if(def?.node.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAliasDeclaration)return def.node.typeAnnotation;if(def?.node.type===eslint_devkit_1.AST_NODE_TYPES.TSInterfaceDeclaration)return def.node.body;return void 0}function memberAnnotation(type,key,sourceCode){let body=type;if(body?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference&&body.typeName.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){body=typeDeclaration(body.typeName,sourceCode)}const members=body?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeLiteral?body.members:body?.type===eslint_devkit_1.AST_NODE_TYPES.TSInterfaceBody?body.body:[];const member=members.find(m=>m.type===eslint_devkit_1.AST_NODE_TYPES.TSPropertySignature&&(0,eslint_devkit_1.objectKeyName)(m)===key);return member?.typeAnnotation}function isSdkTypedParameter(variable,sourceCode){const name=variable.defs[0].name;let annotation=name.typeAnnotation;const holder=name.parent;if(!annotation&&holder.type===eslint_devkit_1.AST_NODE_TYPES.Property&&holder.parent.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){annotation=memberAnnotation(holder.parent.typeAnnotation?.typeAnnotation,(0,eslint_devkit_1.objectKeyName)(holder),sourceCode)}return annotation!==void 0&&referencesSdkType(annotation,sourceCode)}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.typePropertyNames=typePropertyNames;const ANY_OR_UNKNOWN=1|2;function typePropertyNames(node,sourceCode){const services=sourceCode.parserServices;if(!services?.program)return null;const type=services.program.getTypeChecker().getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(node));if((type.flags&ANY_OR_UNKNOWN)!==0)return null;return type.getProperties().map(property=>property.getName())}