eslint-plugin-vercel-ai-security 1.5.3 → 2.0.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": "1.5.3",
3
+ "version": "2.0.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",
@@ -74,7 +74,7 @@
74
74
  "node": ">=18.0.0"
75
75
  },
76
76
  "peerDependencies": {
77
- "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
77
+ "eslint": "^8.40.0 || ^9.0.0 || ^10.0.0",
78
78
  "ai": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
79
79
  },
80
80
  "peerDependenciesMeta": {
@@ -83,7 +83,7 @@
83
83
  }
84
84
  },
85
85
  "dependencies": {
86
- "@interlace/eslint-devkit": "^1.10.0"
86
+ "@interlace/eslint-devkit": "^1.12.0"
87
87
  },
88
88
  "funding": {
89
89
  "type": "github",
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;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}};exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"1.5.3"},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":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/no-training-data-exposure":"warn","vercel-ai-security/require-request-timeout":"warn","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-abort-signal":"error","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;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}};exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.0.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":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/no-training-data-exposure":"warn","vercel-ai-security/require-request-timeout":"warn","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-abort-signal":"error","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");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/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],create(context){const[options={}]=context.options;const allowStaticTemplates=options.allowStaticTemplates??true;const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length>0){return true}return!allowStaticTemplates}if(node.type==="BinaryExpression"&&node.operator==="+"){return true}if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)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"})}}}}}});
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");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/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowStaticTemplates=options.allowStaticTemplates??true;const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length>0){return true}return!allowStaticTemplates}if(node.type==="BinaryExpression"&&node.operator==="+"){return true}if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)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"})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;if(node.value.type==="Literal"&&typeof node.value.value==="string"){const value=node.value.value;if(value===""||value==="YOUR_API_KEY"||value.startsWith("$")){return}if(looksLikeApiKey(value)||value.length>20){context.report({node:node.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isProvider=providerFunctions.some(fn=>callee.includes(fn));if(!isProvider)return;const optionsArg=node.arguments[1];if(optionsArg&&optionsArg.type==="ObjectExpression"){for(const prop of optionsArg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="apiKey"||keyName==="api_key"){if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){const value=prop.value.value;if(looksLikeApiKey(value)||value.length>20){context.report({node:prop.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;if(node.value.type==="Literal"&&typeof node.value.value==="string"){const value=node.value.value;if(value===""||value==="YOUR_API_KEY"||value.startsWith("$")){return}if(looksLikeApiKey(value)||value.length>20){context.report({node:node.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isProvider=providerFunctions.some(fn=>callee.includes(fn));if(!isProvider)return;const optionsArg=node.arguments[1];if(optionsArg&&optionsArg.type==="ObjectExpression"){for(const prop of optionsArg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="apiKey"||keyName==="api_key"){if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){const value=prop.value.value;if(looksLikeApiKey(value)||value.length>20){context.report({node:prop.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}}}}}}}});
@@ -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");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){const[options={}]=context.options;const sensitivePatterns=options.sensitivePatterns??["password","secret","apiKey","api_key","token","credential","ssn","creditCard","privateKey","accessToken"];const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isSensitiveIdentifier(name){const lowerName=name.toLowerCase();return sensitivePatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}function findSensitiveData(node){if(node.type==="Identifier"&&isSensitiveIdentifier(node.name)){return node.name}if(node.type==="MemberExpression"&&node.property.type==="Identifier"){if(isSensitiveIdentifier(node.property.name)){return sourceCode.getText(node)}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const sensitive=findSensitiveData(expr);if(sensitive)return sensitive}}if(node.type==="BinaryExpression"){const leftSensitive=findSensitiveData(node.left);if(leftSensitive)return leftSensitive;const rightSensitive=findSensitiveData(node.right);if(rightSensitive)return rightSensitive}return null}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);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");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){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const sensitivePatterns=options.sensitivePatterns??["password","secret","apiKey","api_key","token","credential","ssn","creditCard","privateKey","accessToken"];const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isSensitiveIdentifier(name){const lowerName=name.toLowerCase();return sensitivePatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}function findSensitiveData(node){if(node.type==="Identifier"&&isSensitiveIdentifier(node.name)){return node.name}if(node.type==="MemberExpression"&&node.property.type==="Identifier"){if(isSensitiveIdentifier(node.property.name)){return sourceCode.getText(node)}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const sensitive=findSensitiveData(expr);if(sensitive)return sensitive}}if(node.type==="BinaryExpression"){const leftSensitive=findSensitiveData(node.left);if(leftSensitive)return leftSensitive;const rightSensitive=findSensitiveData(node.right);if(rightSensitive)return rightSensitive}return null}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);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.noSystemPromptLeak=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noSystemPromptLeak=(0,eslint_devkit_1.createRule)({name:"no-system-prompt-leak",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-system-prompt-leak.md",description:"Prevent system prompts from being exposed in API responses or client code",cwe:"CWE-200",cvss:7.5},messages:{systemPromptLeak:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"System Prompt Leakage",cwe:"CWE-200",owasp:"A01:2021",cvss:7.5,description:'System prompt "{{variable}}" is exposed in return statement. This reveals AI behavior instructions to users.',severity:"HIGH",compliance:["SOC2"],fix:"Remove system prompt from API response. Only return the AI-generated content.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{systemPromptPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest system prompts"}},additionalProperties:false}]},defaultOptions:[{systemPromptPatterns:["systemPrompt","system_prompt","SYSTEM_PROMPT","systemMessage","system_message","SYSTEM_MESSAGE","instructions","INSTRUCTIONS","aiInstructions","agentPrompt","basePrompt","contextPrompt"]}],create(context){const[options={}]=context.options;const systemPromptPatterns=options.systemPromptPatterns??["systemPrompt","system_prompt","SYSTEM_PROMPT","systemMessage","instructions","agentPrompt"];const sourceCode=context.sourceCode;function isSystemPromptVariable(name){const lowerName=name.toLowerCase();return systemPromptPatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}function findSystemPromptVar(node){if(node.type==="Identifier"&&isSystemPromptVariable(node.name)){return node.name}if(node.type==="MemberExpression"&&node.property.type==="Identifier"){if(isSystemPromptVariable(node.property.name)){return sourceCode.getText(node)}}return null}function checkObjectForLeaks(node){for(const prop of node.properties){if(prop.type!=="Property")continue;const leakedVar=findSystemPromptVar(prop.value);if(leakedVar){context.report({node:prop,messageId:"systemPromptLeak",data:{variable:leakedVar}})}}}return{ReturnStatement(node){if(!node.argument)return;if(node.argument.type==="ObjectExpression"){checkObjectForLeaks(node.argument)}const leakedVar=findSystemPromptVar(node.argument);if(leakedVar){context.report({node,messageId:"systemPromptLeak",data:{variable:leakedVar}})}},CallExpression(node){const callee=sourceCode.getText(node.callee);if(!callee.match(/\.(json|send)\s*$/))return;const arg=node.arguments[0];if(arg&&arg.type==="ObjectExpression"){checkObjectForLeaks(arg)}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSystemPromptLeak=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noSystemPromptLeak=(0,eslint_devkit_1.createRule)({name:"no-system-prompt-leak",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-system-prompt-leak.md",description:"Prevent system prompts from being exposed in API responses or client code",cwe:"CWE-200",cvss:7.5},messages:{systemPromptLeak:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"System Prompt Leakage",cwe:"CWE-200",owasp:"A01:2021",cvss:7.5,description:'System prompt "{{variable}}" is exposed in return statement. This reveals AI behavior instructions to users.',severity:"HIGH",compliance:["SOC2"],fix:"Remove system prompt from API response. Only return the AI-generated content.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{systemPromptPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest system prompts"}},additionalProperties:false}]},defaultOptions:[{systemPromptPatterns:["systemPrompt","system_prompt","SYSTEM_PROMPT","systemMessage","system_message","SYSTEM_MESSAGE","instructions","INSTRUCTIONS","aiInstructions","agentPrompt","basePrompt","contextPrompt"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const systemPromptPatterns=options.systemPromptPatterns??["systemPrompt","system_prompt","SYSTEM_PROMPT","systemMessage","instructions","agentPrompt"];const sourceCode=context.sourceCode;function isSystemPromptVariable(name){const lowerName=name.toLowerCase();return systemPromptPatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}function findSystemPromptVar(node){if(node.type==="Identifier"&&isSystemPromptVariable(node.name)){return node.name}if(node.type==="MemberExpression"&&node.property.type==="Identifier"){if(isSystemPromptVariable(node.property.name)){return sourceCode.getText(node)}}return null}function checkObjectForLeaks(node){for(const prop of node.properties){if(prop.type!=="Property")continue;const leakedVar=findSystemPromptVar(prop.value);if(leakedVar){context.report({node:prop,messageId:"systemPromptLeak",data:{variable:leakedVar}})}}}return{ReturnStatement(node){if(!node.argument)return;if(node.argument.type==="ObjectExpression"){checkObjectForLeaks(node.argument)}const leakedVar=findSystemPromptVar(node.argument);if(leakedVar){context.report({node,messageId:"systemPromptLeak",data:{variable:leakedVar}})}},CallExpression(node){const callee=sourceCode.getText(node.callee);if(!callee.match(/\.(json|send)\s*$/))return;const arg=node.arguments[0];if(arg&&arg.type==="ObjectExpression"){checkObjectForLeaks(arg)}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTrainingDataExposure=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noTrainingDataExposure=(0,eslint_devkit_1.createRule)({name:"no-training-data-exposure",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-training-data-exposure.md",description:"Prevent user data from being sent to LLM training endpoints",cwe:"CWE-359",cvss:7},messages:{trainingDataExposure:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Training Data Exposure",cwe:"CWE-359",owasp:"A01:2021",cvss:7,description:'User data may be exposed for model training via "{{pattern}}". This can lead to data poisoning and privacy violations.',severity:"HIGH",compliance:["GDPR","SOC2"],fix:"Disable training data collection or avoid sending PII to training endpoints",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{trainingPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting training endpoints"}},additionalProperties:false}]},defaultOptions:[{trainingPatterns:["train","training","finetune","fine-tune","fine_tune","feedback","improve","learn"]}],create(context){const[options={}]=context.options;const trainingPatterns=options.trainingPatterns??["train","training","finetune","feedback"];function isTrainingRelated(name){const lowerName=name.toLowerCase();return trainingPatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;if(isTrainingRelated(keyName)){if(node.value.type==="Literal"&&node.value.value===true){context.report({node,messageId:"trainingDataExposure",data:{pattern:keyName}})}}},Literal(node){if(typeof node.value!=="string")return;const value=node.value.toLowerCase();if(value.includes("/train")||value.includes("/finetune")||value.includes("/fine-tune")||value.includes("/feedback")){context.report({node,messageId:"trainingDataExposure",data:{pattern:node.value}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTrainingDataExposure=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noTrainingDataExposure=(0,eslint_devkit_1.createRule)({name:"no-training-data-exposure",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-training-data-exposure.md",description:"Prevent user data from being sent to LLM training endpoints",cwe:"CWE-359",cvss:7},messages:{trainingDataExposure:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Training Data Exposure",cwe:"CWE-359",owasp:"A01:2021",cvss:7,description:'User data may be exposed for model training via "{{pattern}}". This can lead to data poisoning and privacy violations.',severity:"HIGH",compliance:["GDPR","SOC2"],fix:"Disable training data collection or avoid sending PII to training endpoints",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{trainingPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting training endpoints"}},additionalProperties:false}]},defaultOptions:[{trainingPatterns:["train","training","finetune","fine-tune","fine_tune","feedback","improve","learn"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const trainingPatterns=options.trainingPatterns??["train","training","finetune","feedback"];function isTrainingRelated(name){const lowerName=name.toLowerCase();return trainingPatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;if(isTrainingRelated(keyName)){if(node.value.type==="Literal"&&node.value.value===true){context.report({node,messageId:"trainingDataExposure",data:{pattern:keyName}})}}},Literal(node){if(typeof node.value!=="string")return;const value=node.value.toLowerCase();if(value.includes("/train")||value.includes("/finetune")||value.includes("/fine-tune")||value.includes("/feedback")){context.report({node,messageId:"trainingDataExposure",data:{pattern:node.value}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeOutputHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");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"})},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","textContent",".text"]}],create(context){const[options={}]=context.options;const aiOutputPatterns=options.aiOutputPatterns??["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput",".text"];const sourceCode=context.sourceCode;const dangerousFunctions=["eval","Function","execSync","exec","spawn","execFile"];const sqlPatterns=["query","execute","run","raw"];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"&&callee.property.type==="Identifier"&&AI_SDK_CALLS.has(callee.property.name))return true;return false}function isLikelyAIOutput(node){if(node.type==="MemberExpression"&&isAIBound(node.object)){return true}if(isAIBound(node)){return true}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)}return{VariableDeclarator(node){if(!node.init||!isAISDKCall(node.init))return;for(const variable of sourceCode.getDeclaredVariables(node)){aiBoundVariables.add(variable)}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isDangerous=dangerousFunctions.some(fn=>callee.includes(fn));if(isDangerous){for(const arg of node.arguments){if(isLikelyAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:callee}})}}}const isSQLFunction=sqlPatterns.some(fn=>callee.includes(fn));if(isSQLFunction){for(const arg of node.arguments){if(arg.type==="TemplateLiteral"||arg.type==="BinaryExpression"){if(containsAIOutput(arg)){context.report({node:arg,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"})}}}}}}});
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");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"})},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","textContent",".text"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const aiOutputPatterns=options.aiOutputPatterns??["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput",".text"];const sourceCode=context.sourceCode;const dangerousFunctions=["eval","Function","execSync","exec","spawn","execFile"];const sqlPatterns=["query","execute","run","raw"];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"&&callee.property.type==="Identifier"&&AI_SDK_CALLS.has(callee.property.name))return true;return false}function isLikelyAIOutput(node){if(node.type==="MemberExpression"&&isAIBound(node.object)){return true}if(isAIBound(node)){return true}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)}return{VariableDeclarator(node){if(!node.init||!isAISDKCall(node.init))return;for(const variable of sourceCode.getDeclaredVariables(node)){aiBoundVariables.add(variable)}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isDangerous=dangerousFunctions.some(fn=>callee.includes(fn));if(isDangerous){for(const arg of node.arguments){if(isLikelyAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:callee}})}}}const isSQLFunction=sqlPatterns.some(fn=>callee.includes(fn));if(isSQLFunction){for(const arg of node.arguments){if(arg.type==="TemplateLiteral"||arg.type==="BinaryExpression"){if(containsAIOutput(arg)){context.report({node:arg,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"})}}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAbortSignal=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireAbortSignal=(0,eslint_devkit_1.createRule)({name:"require-abort-signal",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-abort-signal.md",description:"Require AbortSignal for streaming AI calls to enable proper cleanup",cwe:"CWE-404",cvss:4},messages:{missingAbortSignal:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing AbortSignal in Streaming Call",cwe:"CWE-404",owasp:"A05:2021",cvss:4,description:"{{function}} call without abortSignal. Users cannot cancel long-running streams.",severity:"LOW",fix:"Add abortSignal option: {{function}}({ ..., abortSignal: controller.signal })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/generating-text"})},schema:[{type:"object",properties:{targetFunctions:{type:"array",items:{type:"string"},description:"Functions that should have abort signal"}},additionalProperties:false}]},defaultOptions:[{targetFunctions:["streamText","streamObject"]}],create(context){const[options={}]=context.options;const targetFunctions=options.targetFunctions??["streamText","streamObject"];const sourceCode=context.sourceCode;return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=targetFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const hasAbortSignal=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="abortSignal"||keyName==="signal"});if(!hasAbortSignal){context.report({node,messageId:"missingAbortSignal",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAbortSignal=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireAbortSignal=(0,eslint_devkit_1.createRule)({name:"require-abort-signal",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-abort-signal.md",description:"Require AbortSignal for streaming AI calls to enable proper cleanup",cwe:"CWE-404",cvss:4},messages:{missingAbortSignal:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing AbortSignal in Streaming Call",cwe:"CWE-404",owasp:"A05:2021",cvss:4,description:"{{function}} call without abortSignal. Users cannot cancel long-running streams.",severity:"LOW",fix:"Add abortSignal option: {{function}}({ ..., abortSignal: controller.signal })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/generating-text"})},schema:[{type:"object",properties:{targetFunctions:{type:"array",items:{type:"string"},description:"Functions that should have abort signal"}},additionalProperties:false}]},defaultOptions:[{targetFunctions:["streamText","streamObject"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const targetFunctions=options.targetFunctions??["streamText","streamObject"];const sourceCode=context.sourceCode;return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=targetFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const hasAbortSignal=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="abortSignal"||keyName==="signal"});if(!hasAbortSignal){context.report({node,messageId:"missingAbortSignal",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAuditLogging=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md",description:"Suggest audit logging for AI SDK operations",cwe:"CWE-778",cvss:4},messages:{missingAuditLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Audit Logging",cwe:"CWE-778",owasp:"A09:2021",cvss:4,description:"{{function}} call lacks audit logging. AI operations should be logged for security monitoring.",severity:"LOW",compliance:["SOC2","PCI-DSS"],fix:'Add logging: logger.info("AI call", { userId }); before the call',documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow missing logging in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const loggingPatterns=["log","logger","console","winston","pino","bunyan","debug","info","warn","error","trace"];function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const callee=sourceCode.getText(node.expression.callee);return loggingPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()))}function hasNearbyLogging(node){const parent=node.parent;if(!parent)return false;let statement=node;while(statement&&statement.type!=="ExpressionStatement"&&statement.type!=="VariableDeclaration"&&statement.type!=="ReturnStatement"){statement=statement.parent??null}if(!statement)return false;const block=statement.parent;if(!block||block.type!=="BlockStatement"&&block.type!=="Program"){return false}const statements=block.type==="BlockStatement"?block.body:block.body;const idx=statements.indexOf(statement);if(idx===-1)return false;for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;if(!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAuditLogging=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md",description:"Suggest audit logging for AI SDK operations",cwe:"CWE-778",cvss:4},messages:{missingAuditLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Audit Logging",cwe:"CWE-778",owasp:"A09:2021",cvss:4,description:"{{function}} call lacks audit logging. AI operations should be logged for security monitoring.",severity:"LOW",compliance:["SOC2","PCI-DSS"],fix:'Add logging: logger.info("AI call", { userId }); before the call',documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow missing logging in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const loggingPatterns=["log","logger","console","winston","pino","bunyan","debug","info","warn","error","trace"];function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const callee=sourceCode.getText(node.expression.callee);return loggingPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()))}function hasNearbyLogging(node){const parent=node.parent;if(!parent)return false;let statement=node;while(statement&&statement.type!=="ExpressionStatement"&&statement.type!=="VariableDeclaration"&&statement.type!=="ReturnStatement"){statement=statement.parent??null}if(!statement)return false;const block=statement.parent;if(!block||block.type!=="BlockStatement"&&block.type!=="Program"){return false}const statements=block.type==="BlockStatement"?block.body:block.body;const idx=statements.indexOf(statement);if(idx===-1)return false;for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;if(!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireEmbeddingValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireEmbeddingValidation=(0,eslint_devkit_1.createRule)({name:"require-embedding-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-embedding-validation.md",description:"Require validation of embeddings before storage or similarity search",cwe:"CWE-20",cvss:5.5},messages:{unvalidatedEmbedding:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unvalidated Embedding",cwe:"CWE-20",owasp:"A03:2021",cvss:5.5,description:'Embedding from "{{source}}" used without validation. Malicious embeddings can poison vector stores.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate embeddings before use: const validated = validateEmbedding(embedding)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{embeddingPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting embedding operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate embeddings"}},additionalProperties:false}]},defaultOptions:[{embeddingPatterns:["embed","embedding","embeddings","vector","encode","createEmbedding","getEmbedding","generateEmbedding"],validatorFunctions:["validate","verify","check","sanitize","normalize","validateEmbedding","verifyVector"]}],create(context){const[options={}]=context.options;const embeddingPatterns=options.embeddingPatterns??["embed","embedding","vector","encode"];const validatorFunctions=options.validatorFunctions??["validate","verify","check","sanitize"];const sourceCode=context.sourceCode;const vectorStoreOps=["upsert","insert","add","store","index","save"];function isEmbeddingCall(node){const callee=sourceCode.getText(node.callee);for(const pattern of embeddingPatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isValidated(node){const callee=sourceCode.getText(node.callee);return validatorFunctions.some(fn=>callee.toLowerCase().includes(fn.toLowerCase()))}function isVectorStoreOp(node){const callee=sourceCode.getText(node.callee);return vectorStoreOps.some(op=>callee.toLowerCase().includes(op.toLowerCase()))}return{CallExpression(node){if(!isVectorStoreOp(node))return;for(const arg of node.arguments){if(arg.type==="ObjectExpression"){for(const prop of arg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="embedding"||keyName==="vector"||keyName==="values"){let valueNode=prop.value;if(valueNode.type==="AwaitExpression"){valueNode=valueNode.argument}if(valueNode.type==="CallExpression"){const embeddingSource=isEmbeddingCall(valueNode);if(embeddingSource&&!isValidated(valueNode)){context.report({node:prop.value,messageId:"unvalidatedEmbedding",data:{source:embeddingSource}})}}}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireEmbeddingValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireEmbeddingValidation=(0,eslint_devkit_1.createRule)({name:"require-embedding-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-embedding-validation.md",description:"Require validation of embeddings before storage or similarity search",cwe:"CWE-20",cvss:5.5},messages:{unvalidatedEmbedding:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unvalidated Embedding",cwe:"CWE-20",owasp:"A03:2021",cvss:5.5,description:'Embedding from "{{source}}" used without validation. Malicious embeddings can poison vector stores.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate embeddings before use: const validated = validateEmbedding(embedding)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{embeddingPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting embedding operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate embeddings"}},additionalProperties:false}]},defaultOptions:[{embeddingPatterns:["embed","embedding","embeddings","vector","encode","createEmbedding","getEmbedding","generateEmbedding"],validatorFunctions:["validate","verify","check","sanitize","normalize","validateEmbedding","verifyVector"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const embeddingPatterns=options.embeddingPatterns??["embed","embedding","vector","encode"];const validatorFunctions=options.validatorFunctions??["validate","verify","check","sanitize"];const sourceCode=context.sourceCode;const vectorStoreOps=["upsert","insert","add","store","index","save"];function isEmbeddingCall(node){const callee=sourceCode.getText(node.callee);for(const pattern of embeddingPatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isValidated(node){const callee=sourceCode.getText(node.callee);return validatorFunctions.some(fn=>callee.toLowerCase().includes(fn.toLowerCase()))}function isVectorStoreOp(node){const callee=sourceCode.getText(node.callee);return vectorStoreOps.some(op=>callee.toLowerCase().includes(op.toLowerCase()))}return{CallExpression(node){if(!isVectorStoreOp(node))return;for(const arg of node.arguments){if(arg.type==="ObjectExpression"){for(const prop of arg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="embedding"||keyName==="vector"||keyName==="values"){let valueNode=prop.value;if(valueNode.type==="AwaitExpression"){valueNode=valueNode.argument}if(valueNode.type==="CallExpression"){const embeddingSource=isEmbeddingCall(valueNode);if(embeddingSource&&!isValidated(valueNode)){context.report({node:prop.value,messageId:"unvalidatedEmbedding",data:{source:embeddingSource}})}}}}}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireErrorHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md",description:"Require error handling for AI SDK calls to prevent cascading failures",cwe:"CWE-755",cvss:5},messages:{missingErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled AI SDK Call",cwe:"CWE-755",owasp:"A05:2021",cvss:5,description:"{{function}} call is not wrapped in try-catch. AI API failures can cause cascading errors.",severity:"MEDIUM",compliance:["SOC2"],fix:"Wrap in try-catch: try { await {{function}}(...) } catch (e) { /* handle error */ }",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/error-handling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow unhandled AI calls in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isInsideTryBlock(node){let parent=node.parent;while(parent){if(parent.type==="TryStatement"){return true}parent=parent.parent}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const parent=node.parent;if(parent?.type==="AwaitExpression"){if(!isInsideTryBlock(parent)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}else if(!isInsideTryBlock(node)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireErrorHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md",description:"Require error handling for AI SDK calls to prevent cascading failures",cwe:"CWE-755",cvss:5},messages:{missingErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled AI SDK Call",cwe:"CWE-755",owasp:"A05:2021",cvss:5,description:"{{function}} call is not wrapped in try-catch. AI API failures can cause cascading errors.",severity:"MEDIUM",compliance:["SOC2"],fix:"Wrap in try-catch: try { await {{function}}(...) } catch (e) { /* handle error */ }",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/error-handling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow unhandled AI calls in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isInsideTryBlock(node){let parent=node.parent;while(parent){if(parent.type==="TryStatement"){return true}parent=parent.parent}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const parent=node.parent;if(parent?.type==="AwaitExpression"){if(!isInsideTryBlock(parent)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}else if(!isInsideTryBlock(node)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");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:"Require maxSteps limit for multi-step tool calling to prevent infinite loops",cwe:"CWE-834",cvss:6.5},messages:{missingMaxSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Step Limit in Multi-Step Tool Calling",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools is missing a step limit. Without a limit, tool calls can loop indefinitely.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add a step limit: {{function}}({ ..., stopWhen: stepCountIs(5) }) (v5+) or maxSteps: 5 (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){const sourceCode=context.sourceCode;const functionsWithTools=["generateText","streamText"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=functionsWithTools.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const hasTools=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!hasTools)return;const hasMaxSteps=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="maxSteps"||keyName==="max_steps"||keyName==="stopWhen"});if(!hasMaxSteps){context.report({node,messageId:"missingMaxSteps",data:{function:matchedFunction}})}}}}});
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");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:"Require maxSteps limit for multi-step tool calling to prevent infinite loops",cwe:"CWE-834",cvss:6.5},messages:{missingMaxSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Step Limit in Multi-Step Tool Calling",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools is missing a step limit. Without a limit, tool calls can loop indefinitely.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add a step limit: {{function}}({ ..., stopWhen: stepCountIs(5) }) (v5+) or maxSteps: 5 (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;const functionsWithTools=["generateText","streamText"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=functionsWithTools.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const hasTools=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!hasTools)return;const hasMaxSteps=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="maxSteps"||keyName==="max_steps"||keyName==="stopWhen"});if(!hasMaxSteps){context.report({node,messageId:"missingMaxSteps",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxTokens=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireMaxTokens=(0,eslint_devkit_1.createRule)({name:"require-max-tokens",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-tokens.md",description:"Require maxTokens limit in generateText and streamText calls",cwe:"CWE-770",cvss:6.5},messages:{missingMaxTokens:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Token Limit in Vercel AI SDK",cwe:"CWE-770",owasp:"A05:2021",cvss:6.5,description:"{{function}} call without a token limit can lead to excessive resource consumption",severity:"MEDIUM",compliance:["SOC2"],fix:"Add a token limit: {{function}}({ maxOutputTokens: 4096, ... }) (v5+) or maxTokens (v4)",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/generating-text"})},schema:[{type:"object",properties:{suggestedLimit:{type:"number",description:"Default max tokens limit to suggest",default:4096},targetFunctions:{type:"array",items:{type:"string"},description:"Function names that require max tokens"}},additionalProperties:false}]},defaultOptions:[{suggestedLimit:4096,targetFunctions:["generateText","streamText","generateObject","streamObject"]}],create(context){const[options={}]=context.options;const targetFunctions=options.targetFunctions??["generateText","streamText","generateObject","streamObject"];const sourceCode=context.sourceCode;return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=targetFunctions.find(fn=>callee.includes(fn));if(!matchedFunction){return}const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){return}const hasMaxTokens=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="maxTokens"||keyName==="max_tokens"||keyName==="maxOutputTokens"||keyName==="max_output_tokens"});if(!hasMaxTokens){context.report({node,messageId:"missingMaxTokens",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxTokens=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireMaxTokens=(0,eslint_devkit_1.createRule)({name:"require-max-tokens",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-tokens.md",description:"Require maxTokens limit in generateText and streamText calls",cwe:"CWE-770",cvss:6.5},messages:{missingMaxTokens:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Token Limit in Vercel AI SDK",cwe:"CWE-770",owasp:"A05:2021",cvss:6.5,description:"{{function}} call without a token limit can lead to excessive resource consumption",severity:"MEDIUM",compliance:["SOC2"],fix:"Add a token limit: {{function}}({ maxOutputTokens: 4096, ... }) (v5+) or maxTokens (v4)",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/generating-text"})},schema:[{type:"object",properties:{suggestedLimit:{type:"number",description:"Default max tokens limit to suggest",default:4096},targetFunctions:{type:"array",items:{type:"string"},description:"Function names that require max tokens"}},additionalProperties:false}]},defaultOptions:[{suggestedLimit:4096,targetFunctions:["generateText","streamText","generateObject","streamObject"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const targetFunctions=options.targetFunctions??["generateText","streamText","generateObject","streamObject"];const sourceCode=context.sourceCode;return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=targetFunctions.find(fn=>callee.includes(fn));if(!matchedFunction){return}const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){return}const hasMaxTokens=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:prop.key.type==="Literal"?String(prop.key.value):null;return keyName==="maxTokens"||keyName==="max_tokens"||keyName==="maxOutputTokens"||keyName==="max_output_tokens"});if(!hasMaxTokens){context.report({node,messageId:"missingMaxTokens",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireOutputFiltering=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireOutputFiltering=(0,eslint_devkit_1.createRule)({name:"require-output-filtering",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-filtering.md",description:"Require filtering of sensitive data returned by AI tools",cwe:"CWE-200",cvss:6.5},messages:{missingOutputFilter:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unfiltered Tool Output",cwe:"CWE-200",owasp:"A01:2021",cvss:6.5,description:'Tool "{{toolName}}" returns data from "{{source}}" without filtering. This may leak sensitive information.',severity:"MEDIUM",compliance:["SOC2","GDPR"],fix:"Filter sensitive fields before returning: return filterSensitive(data)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{dataSourcePatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting data sources"},filterFunctions:{type:"array",items:{type:"string"},description:"Functions considered safe filters"}},additionalProperties:false}]},defaultOptions:[{dataSourcePatterns:["query","find","select","fetch","get","read","load","database","db","sql","mongo","prisma","supabase"],filterFunctions:["filter","sanitize","redact","mask","clean","filterSensitive","removePII","scrub"]}],create(context){const[options={}]=context.options;const dataSourcePatterns=options.dataSourcePatterns??["query","find","select","fetch","get","read"];const filterFunctions=options.filterFunctions??["filter","sanitize","redact","mask"];const sourceCode=context.sourceCode;function isDataSourceCall(node){const callee=sourceCode.getText(node.callee);for(const pattern of dataSourcePatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isFilteredCall(node){const callee=sourceCode.getText(node.callee);return filterFunctions.some(filter=>callee.toLowerCase().includes(filter.toLowerCase()))}function getToolName(node){let current=node.parent;while(current){if(current.type==="Property"&&current.parent?.type==="ObjectExpression"){const grandparent=current.parent.parent;if(grandparent?.type==="Property"&&grandparent.key.type==="Identifier"&&grandparent.key.name==="tools"){if(current.key.type==="Identifier"){return current.key.name}}}current=current.parent}return"unknown"}return{ArrowFunctionExpression(node){const parent=node.parent;if(parent?.type!=="Property")return;const keyName=parent.key.type==="Identifier"?parent.key.name:null;if(keyName!=="execute")return;if(node.body.type==="CallExpression"){const source=isDataSourceCall(node.body);if(source&&!isFilteredCall(node.body)){context.report({node:node.body,messageId:"missingOutputFilter",data:{toolName:getToolName(node),source}})}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireOutputFiltering=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireOutputFiltering=(0,eslint_devkit_1.createRule)({name:"require-output-filtering",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-filtering.md",description:"Require filtering of sensitive data returned by AI tools",cwe:"CWE-200",cvss:6.5},messages:{missingOutputFilter:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unfiltered Tool Output",cwe:"CWE-200",owasp:"A01:2021",cvss:6.5,description:'Tool "{{toolName}}" returns data from "{{source}}" without filtering. This may leak sensitive information.',severity:"MEDIUM",compliance:["SOC2","GDPR"],fix:"Filter sensitive fields before returning: return filterSensitive(data)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{dataSourcePatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting data sources"},filterFunctions:{type:"array",items:{type:"string"},description:"Functions considered safe filters"}},additionalProperties:false}]},defaultOptions:[{dataSourcePatterns:["query","find","select","fetch","get","read","load","database","db","sql","mongo","prisma","supabase"],filterFunctions:["filter","sanitize","redact","mask","clean","filterSensitive","removePII","scrub"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const dataSourcePatterns=options.dataSourcePatterns??["query","find","select","fetch","get","read"];const filterFunctions=options.filterFunctions??["filter","sanitize","redact","mask"];const sourceCode=context.sourceCode;function isDataSourceCall(node){const callee=sourceCode.getText(node.callee);for(const pattern of dataSourcePatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isFilteredCall(node){const callee=sourceCode.getText(node.callee);return filterFunctions.some(filter=>callee.toLowerCase().includes(filter.toLowerCase()))}function getToolName(node){let current=node.parent;while(current){if(current.type==="Property"&&current.parent?.type==="ObjectExpression"){const grandparent=current.parent.parent;if(grandparent?.type==="Property"&&grandparent.key.type==="Identifier"&&grandparent.key.name==="tools"){if(current.key.type==="Identifier"){return current.key.name}}}current=current.parent}return"unknown"}return{ArrowFunctionExpression(node){const parent=node.parent;if(parent?.type!=="Property")return;const keyName=parent.key.type==="Identifier"?parent.key.name:null;if(keyName!=="execute")return;if(node.body.type==="CallExpression"){const source=isDataSourceCall(node.body);if(source&&!isFilteredCall(node.body)){context.report({node:node.body,messageId:"missingOutputFilter",data:{toolName:getToolName(node),source}})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireOutputValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireOutputValidation=(0,eslint_devkit_1.createRule)({name:"require-output-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-validation.md",description:"Require validation of AI output before displaying to users",cwe:"CWE-707",cvss:5},messages:{unvalidatedOutput:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unvalidated AI Output",cwe:"CWE-707",owasp:"A03:2021",cvss:5,description:'AI output displayed via "{{method}}" without validation. This can propagate misinformation.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate AI output before display: display(validateOutput(result.text))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{displayPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting display operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate output"}},additionalProperties:false}]},defaultOptions:[{displayPatterns:["render","display","show","print","send","respond","setContent","setText","setMessage","Response.json"],validatorFunctions:["validate","verify","check","sanitize","filter","validateOutput","factCheck","verifyFacts"]}],create(context){const[options={}]=context.options;const displayPatterns=options.displayPatterns??["render","display","show","send","respond"];const sourceCode=context.sourceCode;const aiOutputPatterns=[".text",".content",".message",".response",".output"];const aiResultVariables=new Set;function isAIOutput(node){if(node.type==="MemberExpression"){const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}if(node.type==="Identifier"&&aiResultVariables.has(node.name)){return true}return false}return{VariableDeclarator(node){if(node.id.type!=="Identifier")return;if(!node.init)return;if(node.init.type==="MemberExpression"){const text=sourceCode.getText(node.init);if(aiOutputPatterns.some(pattern=>text.includes(pattern))){aiResultVariables.add(node.id.name)}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isDisplay=displayPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()));if(!isDisplay)return;for(const arg of node.arguments){if(isAIOutput(arg)){context.report({node:arg,messageId:"unvalidatedOutput",data:{method:callee}})}if(arg.type==="ObjectExpression"){for(const prop of arg.properties){if(prop.type!=="Property")continue;if(isAIOutput(prop.value)){context.report({node:prop.value,messageId:"unvalidatedOutput",data:{method:callee}})}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireOutputValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireOutputValidation=(0,eslint_devkit_1.createRule)({name:"require-output-validation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-validation.md",description:"Require validation of AI output before displaying to users",cwe:"CWE-707",cvss:5},messages:{unvalidatedOutput:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unvalidated AI Output",cwe:"CWE-707",owasp:"A03:2021",cvss:5,description:'AI output displayed via "{{method}}" without validation. This can propagate misinformation.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate AI output before display: display(validateOutput(result.text))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{displayPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting display operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate output"}},additionalProperties:false}]},defaultOptions:[{displayPatterns:["render","display","show","print","send","respond","setContent","setText","setMessage","Response.json"],validatorFunctions:["validate","verify","check","sanitize","filter","validateOutput","factCheck","verifyFacts"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const displayPatterns=options.displayPatterns??["render","display","show","send","respond"];const sourceCode=context.sourceCode;const aiOutputPatterns=[".text",".content",".message",".response",".output"];const aiResultVariables=new Set;function isAIOutput(node){if(node.type==="MemberExpression"){const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}if(node.type==="Identifier"&&aiResultVariables.has(node.name)){return true}return false}return{VariableDeclarator(node){if(node.id.type!=="Identifier")return;if(!node.init)return;if(node.init.type==="MemberExpression"){const text=sourceCode.getText(node.init);if(aiOutputPatterns.some(pattern=>text.includes(pattern))){aiResultVariables.add(node.id.name)}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isDisplay=displayPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()));if(!isDisplay)return;for(const arg of node.arguments){if(isAIOutput(arg)){context.report({node:arg,messageId:"unvalidatedOutput",data:{method:callee}})}if(arg.type==="ObjectExpression"){for(const prop of arg.properties){if(prop.type!=="Property")continue;if(isAIOutput(prop.value)){context.report({node:prop.value,messageId:"unvalidatedOutput",data:{method:callee}})}}}}}}}});
@@ -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");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","context"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context){const[options={}]=context.options;const ragPatterns=options.ragPatterns??["search","retrieve","query","vectorStore","documents"];const validatorFunctions=options.validatorFunctions??["validate","sanitize","filter","clean"];const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const ragVariables=new Set;function isRagCall(node){if(node.type!=="CallExpression")return null;const callee=sourceCode.getText(node.callee);for(const pattern of ragPatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isValidated(node){const callee=sourceCode.getText(node.callee);return validatorFunctions.some(fn=>callee.toLowerCase().includes(fn.toLowerCase()))}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){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)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");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","context"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const ragPatterns=options.ragPatterns??["search","retrieve","query","vectorStore","documents"];const validatorFunctions=options.validatorFunctions??["validate","sanitize","filter","clean"];const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const ragVariables=new Set;function isRagCall(node){if(node.type!=="CallExpression")return null;const callee=sourceCode.getText(node.callee);for(const pattern of ragPatterns){if(callee.toLowerCase().includes(pattern.toLowerCase())){return callee}}return null}function isValidated(node){const callee=sourceCode.getText(node.callee);return validatorFunctions.some(fn=>callee.toLowerCase().includes(fn.toLowerCase()))}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){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)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.requireRequestTimeout=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md",description:"Require timeout configuration for AI SDK calls to prevent DoS",cwe:"CWE-400",cvss:5},messages:{missingTimeout:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Request Timeout",cwe:"CWE-400",owasp:"A05:2021",cvss:5,description:"{{function}} call lacks timeout configuration. This can lead to denial of service.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add timeout configuration or use AbortController with setTimeout",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Skip in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const timeoutProperties=new Set(["timeout","abortSignal","signal","timeoutMs","requestTimeout"]);return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}});return}const hasTimeout=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&timeoutProperties.has(keyName)});if(!hasTimeout){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRequestTimeout=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md",description:"Require timeout configuration for AI SDK calls to prevent DoS",cwe:"CWE-400",cvss:5},messages:{missingTimeout:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Request Timeout",cwe:"CWE-400",owasp:"A05:2021",cvss:5,description:"{{function}} call lacks timeout configuration. This can lead to denial of service.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add timeout configuration or use AbortController with setTimeout",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Skip in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const timeoutProperties=new Set(["timeout","abortSignal","signal","timeoutMs","requestTimeout"]);return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}});return}const hasTimeout=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&timeoutProperties.has(keyName)});if(!hasTimeout){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolConfirmation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireToolConfirmation=(0,eslint_devkit_1.createRule)({name:"require-tool-confirmation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-confirmation.md",description:"Require human confirmation for destructive tool operations (delete, transfer, execute)",cwe:"CWE-862",cvss:7},messages:{missingConfirmation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Destructive Tool Without Confirmation",cwe:"CWE-862",owasp:"A01:2021",cvss:7,description:'Tool "{{toolName}}" performs destructive operation "{{operation}}" without requiring confirmation.',severity:"HIGH",compliance:["SOC2"],fix:"Add requiresConfirmation: true or implement confirmation logic in the tool",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{destructivePatterns:{type:"array",items:{type:"string"},description:"Patterns that suggest destructive operations"}},additionalProperties:false}]},defaultOptions:[{destructivePatterns:["delete","remove","drop","truncate","destroy","transfer","send","pay","withdraw","purchase","execute","run","eval","exec","spawn","update","modify","change","alter","create","insert","post","write"]}],create(context){const[options={}]=context.options;const destructivePatterns=options.destructivePatterns??["delete","remove","transfer","execute","update","create"];function isDestructiveTool(name){const lowerName=name.toLowerCase();for(const pattern of destructivePatterns){if(lowerName.includes(pattern.toLowerCase())){return pattern}}return null}function hasConfirmationFlag(toolDef){const confirmationProps=new Set(["requiresConfirmation","requireConfirmation","confirmation","requiresApproval","requireApproval","approval","dangerouslyAllowBrowser"]);return toolDef.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&confirmationProps.has(keyName)})}return{Property(node){if(node.key.type!=="Identifier"&&node.key.type!=="Literal")return;const toolName=node.key.type==="Identifier"?node.key.name:String(node.key.value);if(node.value.type!=="ObjectExpression"&&node.value.type!=="CallExpression"){return}const destructiveOp=isDestructiveTool(toolName);if(!destructiveOp)return;const parent=node.parent;if(parent?.type!=="ObjectExpression")return;const grandparent=parent.parent;if(grandparent?.type!=="Property")return;const toolsKey=grandparent.key;if(toolsKey.type!=="Identifier"||toolsKey.name!=="tools")return;if(node.value.type==="ObjectExpression"){if(!hasConfirmationFlag(node.value)){context.report({node,messageId:"missingConfirmation",data:{toolName,operation:destructiveOp}})}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolConfirmation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireToolConfirmation=(0,eslint_devkit_1.createRule)({name:"require-tool-confirmation",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-confirmation.md",description:"Require human confirmation for destructive tool operations (delete, transfer, execute)",cwe:"CWE-862",cvss:7},messages:{missingConfirmation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Destructive Tool Without Confirmation",cwe:"CWE-862",owasp:"A01:2021",cvss:7,description:'Tool "{{toolName}}" performs destructive operation "{{operation}}" without requiring confirmation.',severity:"HIGH",compliance:["SOC2"],fix:"Add requiresConfirmation: true or implement confirmation logic in the tool",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{destructivePatterns:{type:"array",items:{type:"string"},description:"Patterns that suggest destructive operations"}},additionalProperties:false}]},defaultOptions:[{destructivePatterns:["delete","remove","drop","truncate","destroy","transfer","send","pay","withdraw","purchase","execute","run","eval","exec","spawn","update","modify","change","alter","create","insert","post","write"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const destructivePatterns=options.destructivePatterns??["delete","remove","transfer","execute","update","create"];function isDestructiveTool(name){const lowerName=name.toLowerCase();for(const pattern of destructivePatterns){if(lowerName.includes(pattern.toLowerCase())){return pattern}}return null}function hasConfirmationFlag(toolDef){const confirmationProps=new Set(["requiresConfirmation","requireConfirmation","confirmation","requiresApproval","requireApproval","approval","dangerouslyAllowBrowser"]);return toolDef.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&confirmationProps.has(keyName)})}return{Property(node){if(node.key.type!=="Identifier"&&node.key.type!=="Literal")return;const toolName=node.key.type==="Identifier"?node.key.name:String(node.key.value);if(node.value.type!=="ObjectExpression"&&node.value.type!=="CallExpression"){return}const destructiveOp=isDestructiveTool(toolName);if(!destructiveOp)return;const parent=node.parent;if(parent?.type!=="ObjectExpression")return;const grandparent=parent.parent;if(grandparent?.type!=="Property")return;const toolsKey=grandparent.key;if(toolsKey.type!=="Identifier"||toolsKey.name!=="tools")return;if(node.value.type==="ObjectExpression"){if(!hasConfirmationFlag(node.value)){context.report({node,messageId:"missingConfirmation",data:{toolName,operation:destructiveOp}})}}}}}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolSchema=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md",description:"Require inputSchema (Zod schema) for all AI SDK tools",cwe:"CWE-20",cvss:7.5},messages:{missingInputSchema:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Tool Missing Input Schema",cwe:"CWE-20",owasp:"A03:2021",cvss:7.5,description:'Tool "{{toolName}}" is missing inputSchema. Unvalidated tool parameters can lead to injection attacks.',severity:"HIGH",compliance:["SOC2"],fix:"Add inputSchema using Zod: tool({ inputSchema: z.object({ ... }), execute: ... })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"}),emptyToolsObject:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Empty Tools Object",cwe:"CWE-20",owasp:"A03:2021",description:"Tools object is empty or has no tool definitions",severity:"LOW",fix:"Define tools with proper schemas or remove tools property",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow tools without inputSchema in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context){const[options={}]=context.options;const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const functionsWithTools=["generateText","streamText","generateObject","streamObject"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=functionsWithTools.some(fn=>callee.includes(fn));const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg&&toolArg.type==="ObjectExpression"){const hasInputSchema=toolArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:"unnamed tool"}})}}return}if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const toolsProp=optionsArg.properties.find(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!toolsProp)return;if(toolsProp.value.type==="ObjectExpression"){for(const toolDef of toolsProp.value.properties){if(toolDef.type!=="Property")continue;const toolName=toolDef.key.type==="Identifier"?toolDef.key.name:toolDef.key.type==="Literal"?String(toolDef.key.value):"unknown";if(toolDef.value.type==="ObjectExpression"){const hasInputSchema=toolDef.value.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolDef.value,messageId:"missingInputSchema",data:{toolName}})}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolSchema=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md",description:"Require inputSchema (Zod schema) for all AI SDK tools",cwe:"CWE-20",cvss:7.5},messages:{missingInputSchema:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Tool Missing Input Schema",cwe:"CWE-20",owasp:"A03:2021",cvss:7.5,description:'Tool "{{toolName}}" is missing inputSchema. Unvalidated tool parameters can lead to injection attacks.',severity:"HIGH",compliance:["SOC2"],fix:"Add inputSchema using Zod: tool({ inputSchema: z.object({ ... }), execute: ... })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"}),emptyToolsObject:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Empty Tools Object",cwe:"CWE-20",owasp:"A03:2021",description:"Tools object is empty or has no tool definitions",severity:"LOW",fix:"Define tools with proper schemas or remove tools property",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow tools without inputSchema in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const functionsWithTools=["generateText","streamText","generateObject","streamObject"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=functionsWithTools.some(fn=>callee.includes(fn));const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg&&toolArg.type==="ObjectExpression"){const hasInputSchema=toolArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:"unnamed tool"}})}}return}if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const toolsProp=optionsArg.properties.find(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!toolsProp)return;if(toolsProp.value.type==="ObjectExpression"){for(const toolDef of toolsProp.value.properties){if(toolDef.type!=="Property")continue;const toolName=toolDef.key.type==="Identifier"?toolDef.key.name:toolDef.key.type==="Literal"?String(toolDef.key.value):"unknown";if(toolDef.value.type==="ObjectExpression"){const hasInputSchema=toolDef.value.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolDef.value,messageId:"missingInputSchema",data:{toolName}})}}}}}}}});
@@ -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");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){const[options={}]=context.options;const validatorFunctions=options.validatorFunctions??["validateInput","sanitizeInput","validatePrompt","sanitizePrompt"];const userInputPatterns=options.userInputPatterns??["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"];const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const vercelAIFunctions=["generateText","streamText","generateObject","streamObject"];function isValidatedCall(node){if(node.type!=="CallExpression")return false;const callee=sourceCode.getText(node.callee);return validatorFunctions.some(v=>callee.includes(v))}function isUserInput(node){const text=sourceCode.getText(node);return userInputPatterns.some(pattern=>text.includes(pattern))}function hasUnsafeUserInput(node){if(isValidatedCall(node)){return{unsafe:false}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const result=hasUnsafeUserInput(expr);if(result.unsafe)return result}}if(node.type==="Identifier"){if(isUserInput(node)){return{unsafe:true,input:node.name}}}if(node.type==="MemberExpression"){const text=sourceCode.getText(node);if(isUserInput(node)){return{unsafe:true,input:text}}}if(node.type==="BinaryExpression"){const leftResult=hasUnsafeUserInput(node.left);if(leftResult.unsafe)return leftResult;const rightResult=hasUnsafeUserInput(node.right);if(rightResult.unsafe)return rightResult}return{unsafe:false}}function checkAICall(node){const callee=sourceCode.getText(node.callee);const matchedFunction=vercelAIFunctions.find(fn=>callee.includes(fn));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);if(keyName==="prompt"){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafePrompt",data:{input:result.input||"user input",function:matchedFunction}})}}if((0,prompt_props_1.isSystemPromptProp)(keyName)){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafeSystemPrompt"})}}}}return{CallExpression:checkAICall}}});
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");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){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const validatorFunctions=options.validatorFunctions??["validateInput","sanitizeInput","validatePrompt","sanitizePrompt"];const userInputPatterns=options.userInputPatterns??["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"];const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const vercelAIFunctions=["generateText","streamText","generateObject","streamObject"];function isValidatedCall(node){if(node.type!=="CallExpression")return false;const callee=sourceCode.getText(node.callee);return validatorFunctions.some(v=>callee.includes(v))}function isUserInput(node){const text=sourceCode.getText(node);return userInputPatterns.some(pattern=>text.includes(pattern))}function hasUnsafeUserInput(node){if(isValidatedCall(node)){return{unsafe:false}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const result=hasUnsafeUserInput(expr);if(result.unsafe)return result}}if(node.type==="Identifier"){if(isUserInput(node)){return{unsafe:true,input:node.name}}}if(node.type==="MemberExpression"){const text=sourceCode.getText(node);if(isUserInput(node)){return{unsafe:true,input:text}}}if(node.type==="BinaryExpression"){const leftResult=hasUnsafeUserInput(node.left);if(leftResult.unsafe)return leftResult;const rightResult=hasUnsafeUserInput(node.right);if(rightResult.unsafe)return rightResult}return{unsafe:false}}function checkAICall(node){const callee=sourceCode.getText(node.callee);const matchedFunction=vercelAIFunctions.find(fn=>callee.includes(fn));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);if(keyName==="prompt"){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafePrompt",data:{input:result.input||"user input",function:matchedFunction}})}}if((0,prompt_props_1.isSystemPromptProp)(keyName)){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafeSystemPrompt"})}}}}return{CallExpression:checkAICall}}});
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.fileUsesVercelAi=fileUsesVercelAi;const eslint_devkit_1=require("@interlace/eslint-devkit");const AI_PACKAGES=new Set(["ai"]);const AI_SCOPES=new Set(["@ai-sdk"]);function normalizeSpecifier(specifier){if(specifier.startsWith("npm:"))return specifier.slice(4);const deno=/^https?:\/\/deno\.land\/x\/([^@/]+)/.exec(specifier);if(deno)return deno[1];return specifier}function isAiSpecifier(specifier){specifier=normalizeSpecifier(specifier);if(specifier.startsWith(".")||specifier.startsWith("/"))return false;if(specifier.startsWith("@")){const scope=specifier.split("/")[0];return AI_SCOPES.has(scope)}return AI_PACKAGES.has(specifier.split("/")[0])}function isImportEqualsLoad(node){return node.type===eslint_devkit_1.AST_NODE_TYPES.TSImportEqualsDeclaration&&node.moduleReference.type===eslint_devkit_1.AST_NODE_TYPES.TSExternalModuleReference&&node.moduleReference.expression.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.moduleReference.expression.value==="string"&&isAiSpecifier(node.moduleReference.expression.value)}function bindsRequire(node){if(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){return node.params.some(p=>p.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&p.name==="require")}if(node.type===eslint_devkit_1.AST_NODE_TYPES.Program||node.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){return node.body.some(stmt=>stmt.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration&&stmt.declarations.some(d=>d.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&d.id.name==="require"))}return false}function isAiDynamicLoad(node,requireIsShadowed){const argument=node.type===eslint_devkit_1.AST_NODE_TYPES.ImportExpression?node.source:!requireIsShadowed&&node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"?node.arguments[0]:void 0;return argument?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof argument.value==="string"&&isAiSpecifier(argument.value)}const cache=new WeakMap;function fileUsesVercelAi(ast){const cached=cache.get(ast);if(cached!==void 0)return cached;const result=computeUsesVercelAi(ast);cache.set(ast,result);return result}function computeUsesVercelAi(ast){let found=false;const visit=(node,requireIsShadowed)=>{if((node.type===eslint_devkit_1.AST_NODE_TYPES.ImportDeclaration||node.type===eslint_devkit_1.AST_NODE_TYPES.ExportNamedDeclaration||node.type===eslint_devkit_1.AST_NODE_TYPES.ExportAllDeclaration)&&node.source?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof node.source.value==="string"&&isAiSpecifier(node.source.value)){found=true;return}if(isImportEqualsLoad(node)||isAiDynamicLoad(node,requireIsShadowed)){found=true;return}const shadowedHere=requireIsShadowed||bindsRequire(node);for(const key of Object.keys(node)){if(key==="parent")continue;const value=node[key];if(Array.isArray(value)){for(const child of value){if(child&&typeof child.type==="string"){visit(child,shadowedHere);if(found)return}}}else if(value&&typeof value==="object"&&typeof value.type==="string"){visit(value,shadowedHere);if(found)return}}};visit(ast,false);return found}