eslint-plugin-vercel-ai-security 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/rules/no-dynamic-system-prompt/index.js +1 -1
- package/src/rules/no-hardcoded-api-keys/index.js +1 -1
- package/src/rules/no-sensitive-in-prompt/index.js +1 -1
- package/src/rules/no-system-prompt-leak/index.js +1 -1
- package/src/rules/no-unsafe-output-handling/index.js +1 -1
- package/src/rules/require-abort-signal/index.js +1 -1
- package/src/rules/require-audit-logging/index.js +1 -1
- package/src/rules/require-embedding-validation/index.js +1 -1
- package/src/rules/require-error-handling/index.js +1 -1
- package/src/rules/require-max-steps/index.js +1 -1
- package/src/rules/require-max-tokens/index.js +1 -1
- package/src/rules/require-output-filtering/index.js +1 -1
- package/src/rules/require-rag-content-validation/index.js +1 -1
- package/src/rules/require-request-timeout/index.js +1 -1
- package/src/rules/require-tool-confirmation/index.js +1 -1
- package/src/rules/require-tool-schema/index.js +1 -1
- package/src/rules/require-validated-prompt/index.js +1 -1
- package/src/utils/sdk.js +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,9 @@ npm install oxlint-plugin-vercel-ai-security --save-dev
|
|
|
65
65
|
// vite.config.ts
|
|
66
66
|
import { defineConfig } from 'vite-plus';
|
|
67
67
|
|
|
68
|
-
export default defineConfig({
|
|
68
|
+
export default defineConfig({
|
|
69
|
+
lint: { jsPlugins: ['oxlint-plugin-vercel-ai-security'] },
|
|
70
|
+
});
|
|
69
71
|
```
|
|
70
72
|
|
|
71
73
|
**ESLint:**
|
|
@@ -219,7 +221,6 @@ Part of the **Interlace ESLint ecosystem** — AI-native rules with LLM-optimize
|
|
|
219
221
|
|
|
220
222
|
| Plugin | Downloads | Description |
|
|
221
223
|
| :--- | :---: | :--- |
|
|
222
|
-
| [`eslint-plugin-cli-floor`](https://www.npmjs.com/package/eslint-plugin-cli-floor) | [](https://www.npmjs.com/package/eslint-plugin-cli-floor) | CLI floor for commander, yargs and burgee. |
|
|
223
224
|
| [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) | [](https://www.npmjs.com/package/eslint-plugin-conventions) | Team-specific habits and styles. |
|
|
224
225
|
| [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [](https://www.npmjs.com/package/eslint-plugin-import-next) | Fast cycle + import-graph analysis. |
|
|
225
226
|
| [`eslint-plugin-maintainability`](https://www.npmjs.com/package/eslint-plugin-maintainability) | [](https://www.npmjs.com/package/eslint-plugin-maintainability) | Cognitive load and clean-code patterns. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vercel-ai-security",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "ESLint plugin for Vercel AI SDK security — detects prompt injection, system-prompt leaks, hardcoded API keys, and unvalidated model output in generateText and streamText.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-vercel-ai-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.4.
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};(0,eslint_devkit_1.withCanonicalDocsUrls)("plugin-vercel-ai-security",exports.rules);exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.4.1"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/require-max-steps":"off","vercel-ai-security/require-abort-signal":"off","vercel-ai-security/require-request-timeout":"off","vercel-ai-security/no-training-data-exposure":"off","vercel-ai-security/require-output-filtering":"off","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicSystemPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noDynamicSystemPrompt=(0,eslint_devkit_1.createRule)({name:"no-dynamic-system-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md",description:"Prevent dynamic content in system prompts to avoid agent confusion attacks",cwe:"CWE-74",cvss:8},messages:{dynamicSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt contains dynamic content. This can lead to agent confusion attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use a static system prompt defined as a constant. Avoid template literals or concatenation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),userControlledSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User-Controlled System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt is read straight from the request body. The caller can replace the agent's instructions.",severity:"HIGH",compliance:["SOC2"],fix:"Keep the system prompt server-side; let the request select from a fixed set of prompts by id",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],createOnce(context,currentOptions){let allowStaticTemplates;let sourceCode;function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length===0)return!allowStaticTemplates;return!(0,sdk_1.isStaticText)(node,sourceCode)}if(node.type==="BinaryExpression"
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicSystemPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.noDynamicSystemPrompt=(0,eslint_devkit_1.createRule)({name:"no-dynamic-system-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md",description:"Prevent dynamic content in system prompts to avoid agent confusion attacks",cwe:"CWE-74",cvss:8},messages:{dynamicSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt contains dynamic content. This can lead to agent confusion attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use a static system prompt defined as a constant. Avoid template literals or concatenation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),userControlledSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User-Controlled System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt is read straight from the request body. The caller can replace the agent's instructions.",severity:"HIGH",compliance:["SOC2"],fix:"Keep the system prompt server-side; let the request select from a fixed set of prompts by id",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],createOnce(context,currentOptions){let allowStaticTemplates;let sourceCode;function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length===0)return!allowStaticTemplates;return!(0,sdk_1.isStaticText)(node,sourceCode)}if(node.type==="BinaryExpression"||node.type==="CallExpression")return!(0,sdk_1.isStaticText)(node,sourceCode);return node.type==="AwaitExpression"}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({allowStaticTemplates}=options);sourceCode=context.sourceCode;return true},CallExpression(node){if(!(0,sdk_1.sdkCallName)(node,context.sourceCode))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;if(isDynamicContent(prop.value)){context.report({node:prop.value,messageId:"dynamicSystemPrompt"})}else if((0,sdk_1.isRequestDerived)(prop.value,sourceCode)){context.report({node:prop.value,messageId:"userControlledSystemPrompt"})}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.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:{interop:{runtimes:["eslint","oxlint"]},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","authorization"]}],createOnce(context,currentOptions){let apiKeyPatterns;function holderName(node){const parent=node.parent;if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property&&parent.value===node){return(0,eslint_devkit_1.objectKeyName)(parent)}if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return parent.id.name}return null}function check(node){const raw=(0,eslint_devkit_1.staticString)(node);if(raw===null)return;const value=raw.replace(/^Bearer\s+/i,"");const holder=holderName(node);const isKey=PROVIDER_KEY_SHAPES.some(shape=>shape.test(value))||holder!==null&&apiKeyPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(holder,pattern))&&isGenericSecret(value);if(!isKey)return;context.report({node,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({apiKeyPatterns}=options);return true},Literal:check,TemplateLiteral:check}}});const PROVIDER_KEY_SHAPES=[/^sk-(?:proj-|ant-|svcacct-|admin-)?(?=[\w-]*\d)[\w-]{20,}$/,/^AIza[\w-]{35}$/,/^(?:gsk|hf|r8)_(?=\w*\d)\w{20,}$/,/^(?:xai|pplx)-(?=\w*\d)\w{20,}$/,/^AKIA[0-9A-Z]{16}$/];function isGenericSecret(value){return value.length>20&&!value.startsWith("$")&&!/\s/.test(value)&&!value.includes("://")&&!(value.includes("_")&&/^[A-Z][A-Z0-9_]*$/.test(value))
|
|
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:{interop:{runtimes:["eslint","oxlint"]},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","authorization"]}],createOnce(context,currentOptions){let apiKeyPatterns;function holderName(node){const parent=node.parent;if((parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property||parent?.type===eslint_devkit_1.AST_NODE_TYPES.PropertyDefinition)&&parent.value===node){return(0,eslint_devkit_1.objectKeyName)(parent)}if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.JSXAttribute&&parent.name.type===eslint_devkit_1.AST_NODE_TYPES.JSXIdentifier){return parent.name.name}if(parent?.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&parent.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return parent.id.name}return null}function check(node){const raw=(0,eslint_devkit_1.staticString)(node);if(raw===null)return;const value=raw.replace(/^Bearer\s+/i,"");const holder=holderName(node);const isKey=PROVIDER_KEY_SHAPES.some(shape=>shape.test(value))||holder!==null&&apiKeyPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(holder,pattern))&&isGenericSecret(value);if(!isKey)return;context.report({node,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({apiKeyPatterns}=options);return true},Literal:check,TemplateLiteral:check}}});const PROVIDER_KEY_SHAPES=[/^sk-(?:proj-|ant-|svcacct-|admin-)?(?=[\w-]*\d)[\w-]{20,}$/,/^AIza[\w-]{35}$/,/^(?:gsk|hf|r8)_(?=\w*\d)\w{20,}$/,/^(?:xai|pplx)-(?=\w*\d)\w{20,}$/,/^AKIA[0-9A-Z]{16}$/];function isGenericSecret(value){return value.length>20&&!value.startsWith("$")&&!/\s/.test(value)&&!value.includes("://")&&!(value.includes("_")&&/^[A-Z][A-Z0-9_]*$/.test(value))&&!/^\/?[\w.-]+(?:\/[\w.-]+){2,}$/.test(value)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitiveInPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");const records_1=require("../../utils/records");const type_info_1=require("../../utils/type-info");exports.noSensitiveInPrompt=(0,eslint_devkit_1.createRule)({name:"no-sensitive-in-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md",description:"Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts",cwe:"CWE-200",cvss:8},messages:{sensitiveInPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in LLM Prompt",cwe:"CWE-200",owasp:"A01:2021",cvss:8,description:'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',severity:"CRITICAL",compliance:["SOC2","GDPR","HIPAA","PCI-DSS"],fix:"Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{sensitivePatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest sensitive data"}},additionalProperties:false}]},defaultOptions:[{sensitivePatterns:["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{sensitivePatterns}=options;const sourceCode=context.sourceCode;function isSensitiveIdentifier(name){return sensitivePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSensitiveData(node){if(node===null)return null;switch(node.type){case"Identifier":return isSensitiveIdentifier(node.name)?node.name:null;case"MemberExpression":{const name=(0,eslint_devkit_1.propertyName)(node);return name!==null&&isSensitiveIdentifier(name)?sourceCode.getText(node):null}case"TemplateLiteral":for(const expr of node.expressions){const found=findSensitiveData(expr)??wholeRecord(expr);if(found)return found}return null;case"CallExpression":{const path=(0,eslint_devkit_1.memberPath)(node.callee)?.join(".");const[first]=node.arguments;if(path!=="JSON.stringify"&&path!=="String"||first===void 0)return null;return findSensitiveData(first)??wholeRecord(first)}case"BinaryExpression":return findSensitiveData(node.left)??findSensitiveData(node.right);case"ArrayExpression":return firstSensitive(node.elements);case"SpreadElement":return findSensitiveData(node.argument);case"ObjectExpression":return firstSensitive(node.properties.map(prop=>prop.type==="Property"?prop.value:prop));default:return null}}function wholeRecord(node){const label=sourceCode.getText(node);const names=(0,type_info_1.typePropertyNames)(node,sourceCode);if(names!==null)return names.some(isSensitiveIdentifier)?label:null;if(node.type==="MemberExpression")return null;return(0,flow_1.derivesFrom)(node,recordFlow)?label:null}const recordFlow={sourceCode,isSource:candidate=>candidate.type==="ObjectExpression"&&candidate.properties.some(prop=>prop.type==="Property"&&isSensitiveIdentifier((0,eslint_devkit_1.objectKeyName)(prop)??""))||(0,records_1.isDatabaseRowRead)(candidate,sourceCode)};function firstSensitive(nodes){for(const node of nodes){const found=findSensitiveData(node);if(found)return found}return null}const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!keyName||!propsToCheck.has(keyName))continue;const sensitiveVar=findSensitiveData(prop.value);if(sensitiveVar){context.report({node:prop.value,messageId:"sensitiveInPrompt",data:{variable:sensitiveVar}})}}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitiveInPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");const records_1=require("../../utils/records");const type_info_1=require("../../utils/type-info");exports.noSensitiveInPrompt=(0,eslint_devkit_1.createRule)({name:"no-sensitive-in-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md",description:"Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts",cwe:"CWE-200",cvss:8},messages:{sensitiveInPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in LLM Prompt",cwe:"CWE-200",owasp:"A01:2021",cvss:8,description:'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',severity:"CRITICAL",compliance:["SOC2","GDPR","HIPAA","PCI-DSS"],fix:"Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{sensitivePatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest sensitive data"}},additionalProperties:false}]},defaultOptions:[{sensitivePatterns:["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{sensitivePatterns}=options;const sourceCode=context.sourceCode;function isSensitiveIdentifier(name){return sensitivePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSensitiveData(node){if(node===null)return null;switch(node.type){case"Identifier":return isSensitiveIdentifier(node.name)?node.name:null;case"MemberExpression":{const name=(0,eslint_devkit_1.propertyName)(node);return name!==null&&isSensitiveIdentifier(name)?sourceCode.getText(node):null}case"TemplateLiteral":for(const expr of node.expressions){const found=findSensitiveData(expr)??wholeRecord(expr);if(found)return found}return null;case"CallExpression":{const path=(0,eslint_devkit_1.memberPath)(node.callee)?.join(".");const[first]=node.arguments;if(path!=="JSON.stringify"&&path!=="String"||first===void 0)return null;return findSensitiveData(first)??wholeRecord(first)}case"BinaryExpression":return findSensitiveData(node.left)??findSensitiveData(node.right);case"ArrayExpression":return firstSensitive(node.elements);case"SpreadElement":return findSensitiveData(node.argument);case"ObjectExpression":return firstSensitive(node.properties.map(prop=>prop.type==="Property"?prop.value:prop));default:return null}}function wholeRecord(node){const label=sourceCode.getText(node);const names=(0,type_info_1.typePropertyNames)(node,sourceCode);if(names!==null)return names.some(isSensitiveIdentifier)?label:null;if(node.type==="MemberExpression")return null;return(0,flow_1.derivesFrom)(node,recordFlow)?label:null}const recordFlow={sourceCode,isSource:candidate=>candidate.type==="ObjectExpression"&&candidate.properties.some(prop=>prop.type==="Property"&&isSensitiveIdentifier((0,eslint_devkit_1.objectKeyName)(prop)??""))||(0,records_1.isDatabaseRowRead)(candidate,sourceCode)};function firstSensitive(nodes){for(const node of nodes){const found=findSensitiveData(node);if(found)return found}return null}const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node,context.sourceCode))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!keyName||!propsToCheck.has(keyName))continue;const sensitiveVar=findSensitiveData(prop.value);if(sensitiveVar){context.report({node:prop.value,messageId:"sensitiveInPrompt",data:{variable:sensitiveVar}})}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSystemPromptLeak=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const prompt_props_1=require("../../utils/prompt-props");const sdk_1=require("../../utils/sdk");exports.noSystemPromptLeak=(0,eslint_devkit_1.createRule)({name:"no-system-prompt-leak",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let systemPromptPatterns;let sourceCode;function isSystemPromptVariable(name){return systemPromptPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSystemPromptVar(node){if(node.type==="Identifier"&&isSystemPromptVariable(node.name)){return node.name}if(node.type==="MemberExpression"){const name=(0,eslint_devkit_1.propertyName)(node);if(name!==null&&isSystemPromptVariable(name))return sourceCode.getText(node)}return null}function checkObjectForLeaks(node,inResponse){for(const prop of node.properties){if(prop.type!=="Property")continue;const key=(0,eslint_devkit_1.objectKeyName)(prop);const leaked=findSystemPromptVar(prop.value)??(inResponse&&key!==null&&((0,prompt_props_1.isSystemPromptProp)(key)||isSystemPromptVariable(key))?key:null);if(leaked){context.report({node:prop,messageId:"systemPromptLeak",data:{variable:leaked}})}else if(inResponse&&prop.value.type==="ObjectExpression"){checkObjectForLeaks(prop.value,true)}}}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({systemPromptPatterns}=options);sourceCode=context.sourceCode;return true},ReturnStatement(node){if(!node.argument)return;if(node.argument.type==="ObjectExpression"){if((0,sdk_1.optionValue)(node.argument,"model"))return;checkObjectForLeaks(node.argument,false)}const leakedVar=findSystemPromptVar(node.argument);if(leakedVar){context.report({node,messageId:"systemPromptLeak",data:{variable:leakedVar}})}},CallExpression(node){
|
|
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");const prompt_props_1=require("../../utils/prompt-props");const sdk_1=require("../../utils/sdk");exports.noSystemPromptLeak=(0,eslint_devkit_1.createRule)({name:"no-system-prompt-leak",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let systemPromptPatterns;let sourceCode;function isSystemPromptVariable(name){return systemPromptPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(name,pattern))}function findSystemPromptVar(node){if(node.type==="Identifier"&&isSystemPromptVariable(node.name)){return node.name}if(node.type==="MemberExpression"){const name=(0,eslint_devkit_1.propertyName)(node);if(name!==null&&isSystemPromptVariable(name))return sourceCode.getText(node)}return null}function checkObjectForLeaks(node,inResponse){for(const prop of node.properties){if(prop.type!=="Property")continue;const key=(0,eslint_devkit_1.objectKeyName)(prop);const leaked=findSystemPromptVar(prop.value)??(inResponse&&key!==null&&((0,prompt_props_1.isSystemPromptProp)(key)||isSystemPromptVariable(key))?key:null);if(leaked){context.report({node:prop,messageId:"systemPromptLeak",data:{variable:leaked}})}else if(inResponse&&prop.value.type==="ObjectExpression"){checkObjectForLeaks(prop.value,true)}}}function checkPayload(payload){if(payload?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){checkObjectForLeaks(payload,true);return}const leaked=payload&&findSystemPromptVar(payload);if(leaked){context.report({node:payload,messageId:"systemPromptLeak",data:{variable:leaked}})}}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({systemPromptPatterns}=options);sourceCode=context.sourceCode;return true},ReturnStatement(node){if(!node.argument)return;if(node.argument.type==="ObjectExpression"){if((0,sdk_1.optionValue)(node.argument,"model"))return;checkObjectForLeaks(node.argument,false)}const leakedVar=findSystemPromptVar(node.argument);if(leakedVar){context.report({node,messageId:"systemPromptLeak",data:{variable:leakedVar}})}},CallExpression(node){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression||!RESPONSE_METHODS.has((0,eslint_devkit_1.propertyName)(node.callee)))return;checkPayload(node.arguments[0])},NewExpression(node){if(node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier||node.callee.name!=="Response")return;const body=node.arguments[0];if(body?.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return;if((0,eslint_devkit_1.memberPath)(body.callee)?.join(".")!=="JSON.stringify")return;checkPayload(body.arguments[0])}}}});const RESPONSE_METHODS=new Set(["json","jsonp","send"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeOutputHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.noUnsafeOutputHandling=(0,eslint_devkit_1.createRule)({name:"no-unsafe-output-handling",meta:{interop:{runtimes:["eslint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-unsafe-output-handling.md",description:"Prevent using AI output directly in dangerous operations (eval, SQL, HTML)",cwe:"CWE-94",cvss:9.8,confidence:"medium"},messages:{unsafeOutputExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output Used in Code Execution",cwe:"CWE-94",owasp:"A03:2021",cvss:9.8,description:'AI-generated content "{{variable}}" passed to {{function}}. This can lead to Remote Code Execution.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:"Never execute AI-generated code directly. Use sandboxed execution with validation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeOutputInSQL:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in SQL Query",cwe:"CWE-89",owasp:"A03:2021",cvss:9,description:"AI-generated content used in SQL query. Use parameterized queries instead.",severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:'Use parameterized queries: db.query("SELECT * FROM users WHERE id = ?", [aiOutput])',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"}),unsafeOutputInHTML:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in innerHTML",cwe:"CWE-79",owasp:"A03:2021",cvss:7.5,description:"AI-generated content assigned to innerHTML. This can lead to XSS attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use textContent or sanitize HTML: element.textContent = aiOutput",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"}),unsafeOutputInRequest:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI-Chosen Request URL",cwe:"CWE-918",owasp:"A10:2021",cvss:8.6,description:"Model-controlled value chooses the URL passed to fetch(). This can lead to Server-Side Request Forgery.",severity:"HIGH",compliance:["SOC2"],fix:"Fix the host and pass model input only as a path or query value, or check the URL against an allow-list",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{aiOutputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest AI output"}},additionalProperties:false}]},defaultOptions:[{aiOutputPatterns:["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput","llmResponse","modelOutput",".text"]}],createOnce(context,currentOptions){let aiOutputPatterns;let sourceCode;let AI_SDK_CALLS;let outputFlow;function isAISDKCall(call){const callee=call.callee;if(callee.type==="Identifier")return AI_SDK_CALLS.has(callee.name);return callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),AI_SDK_CALLS)}function isBoundOutput(node){return(0,flow_1.derivesFrom)(node,outputFlow)}function isLikelyAIOutput(node){if(isBoundOutput(node))return true;if(node.type!=="Identifier"&&node.type!=="MemberExpression")return false;const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}function containsAIOutput(node){if(node.type==="TemplateLiteral"){return node.expressions.some(containsAIOutput)}if(node.type==="BinaryExpression"){return node.operator==="+"&&(containsAIOutput(node.left)||containsAIOutput(node.right))}return isLikelyAIOutput(node)}function isGlobal(node,name){return node.type==="Identifier"&&node.name===name&&!(0,sdk_1.lookupVariable)(name,sourceCode.getScope(node))?.defs.length}function isCodeExecutionCallee(callee){if(isGlobal(callee,"eval")||isGlobal(callee,"Function"))return true;const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,sourceCode.getScope(callee));if(binding){const fn=binding.path[binding.path.length-1];return binding.module==="child_process"&&CHILD_PROCESS_SINKS.has(fn)||binding.module==="vm"&&VM_SINKS.has(fn)}return callee.type==="Identifier"&&CHILD_PROCESS_SINKS.has(callee.name)&&!(0,sdk_1.lookupVariable)(callee.name,sourceCode.getScope(callee))?.defs.length}function isToolExecute(node){if((0,eslint_devkit_1.objectKeyName)(node)!=="execute")return false;const definition=node.parent;const owner=definition.parent;if(owner.type==="CallExpression"&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(owner.callee))){return true}return definition.properties.some(prop=>prop.type==="Property"&&SCHEMA_KEYS.has((0,eslint_devkit_1.objectKeyName)(prop)))}function choosesRequestTarget(node){if(node.type==="TemplateLiteral"){return node.quasis[0].value.raw===""&&isBoundOutput(node.expressions[0])}return isBoundOutput(node)}function reportExecution(node,callee,args){for(const arg of args){if(containsAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:sourceCode.getText(callee)}})}}}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({aiOutputPatterns}=options);sourceCode=context.sourceCode;AI_SDK_CALLS=new Set(["generateText","streamText","generateObject","streamObject"]);outputFlow={sourceCode,isSource:node=>node.type==="CallExpression"&&isAISDKCall(node)||(0,sdk_1.isSdkHookCall)(node,sourceCode),isSourceParameter:(variable,fn)=>fn.parent.type==="Property"&&isToolExecute(fn.parent)||(0,sdk_1.isSdkTypedParameter)(variable,sourceCode)};return true},NewExpression(node){const isFunction=isGlobal(node.callee,"Function");const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));if(isFunction||binding?.module==="vm"&&binding.path[0]==="Script"){reportExecution(node,node.callee,node.arguments)}},CallExpression(node){if(isCodeExecutionCallee(node.callee)){reportExecution(node,node.callee,node.arguments);return}const name=(0,sdk_1.calleeName)(node.callee);if(isGlobal(node.callee,"fetch")){const[target]=node.arguments;if(target&&choosesRequestTarget(target)){context.report({node:target,messageId:"unsafeOutputInRequest"})}return}if(!SQL_SINKS.has(name))return;const[query]=node.arguments;if(query&&containsAIOutput(query)){context.report({node:query,messageId:"unsafeOutputInSQL"})}},AssignmentExpression(node){if(node.left.type==="MemberExpression")
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noUnsafeOutputHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.noUnsafeOutputHandling=(0,eslint_devkit_1.createRule)({name:"no-unsafe-output-handling",meta:{interop:{runtimes:["eslint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-unsafe-output-handling.md",description:"Prevent using AI output directly in dangerous operations (eval, SQL, HTML)",cwe:"CWE-94",cvss:9.8,confidence:"medium"},messages:{unsafeOutputExecution:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output Used in Code Execution",cwe:"CWE-94",owasp:"A03:2021",cvss:9.8,description:'AI-generated content "{{variable}}" passed to {{function}}. This can lead to Remote Code Execution.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:"Never execute AI-generated code directly. Use sandboxed execution with validation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeOutputInSQL:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in SQL Query",cwe:"CWE-89",owasp:"A03:2021",cvss:9,description:"AI-generated content used in SQL query. Use parameterized queries instead.",severity:"CRITICAL",compliance:["SOC2","PCI-DSS"],fix:'Use parameterized queries: db.query("SELECT * FROM users WHERE id = ?", [aiOutput])',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"}),unsafeOutputInHTML:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI Output in innerHTML",cwe:"CWE-79",owasp:"A03:2021",cvss:7.5,description:"AI-generated content assigned to innerHTML. This can lead to XSS attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use textContent or sanitize HTML: element.textContent = aiOutput",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"}),unsafeOutputInRequest:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"AI-Chosen Request URL",cwe:"CWE-918",owasp:"A10:2021",cvss:8.6,description:"Model-controlled value chooses the URL passed to fetch(). This can lead to Server-Side Request Forgery.",severity:"HIGH",compliance:["SOC2"],fix:"Fix the host and pass model input only as a path or query value, or check the URL against an allow-list",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{aiOutputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest AI output"}},additionalProperties:false}]},defaultOptions:[{aiOutputPatterns:["result.text","response.text","completion","generated","aiOutput","aiResponse","llmOutput","llmResponse","modelOutput",".text"]}],createOnce(context,currentOptions){let aiOutputPatterns;let sourceCode;let AI_SDK_CALLS;let outputFlow;function isAISDKCall(call){const callee=call.callee;if(callee.type==="Identifier")return AI_SDK_CALLS.has(callee.name);return callee.type==="MemberExpression"&&(0,eslint_devkit_1.namesOneOf)((0,eslint_devkit_1.propertyName)(callee),AI_SDK_CALLS)}function isBoundOutput(node){return(0,flow_1.derivesFrom)(node,outputFlow)}function isLikelyAIOutput(node){if(isBoundOutput(node))return true;if(node.type!=="Identifier"&&node.type!=="MemberExpression")return false;const text=sourceCode.getText(node);return aiOutputPatterns.some(pattern=>text.includes(pattern))}function containsAIOutput(node){if(node.type==="TemplateLiteral"){return node.expressions.some(containsAIOutput)}if(node.type==="BinaryExpression"){return node.operator==="+"&&(containsAIOutput(node.left)||containsAIOutput(node.right))}return isLikelyAIOutput(node)}function isGlobal(node,name){return node.type==="Identifier"&&node.name===name&&!(0,sdk_1.lookupVariable)(name,sourceCode.getScope(node))?.defs.length}function isCodeExecutionCallee(callee){if(isGlobal(callee,"eval")||isGlobal(callee,"Function"))return true;const binding=(0,eslint_devkit_1.resolveModuleBinding)(callee,sourceCode.getScope(callee));if(binding){const fn=binding.path[binding.path.length-1];return binding.module==="child_process"&&CHILD_PROCESS_SINKS.has(fn)||binding.module==="vm"&&VM_SINKS.has(fn)}return callee.type==="Identifier"&&CHILD_PROCESS_SINKS.has(callee.name)&&!(0,sdk_1.lookupVariable)(callee.name,sourceCode.getScope(callee))?.defs.length}function isToolExecute(node){if((0,eslint_devkit_1.objectKeyName)(node)!=="execute")return false;const definition=node.parent;const owner=definition.parent;if(owner.type==="CallExpression"&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(owner.callee))){return true}return definition.properties.some(prop=>prop.type==="Property"&&SCHEMA_KEYS.has((0,eslint_devkit_1.objectKeyName)(prop)))}function choosesRequestTarget(node){if(node.type==="TemplateLiteral"){return node.quasis[0].value.raw===""&&isBoundOutput(node.expressions[0])}return isBoundOutput(node)}function reportExecution(node,callee,args){for(const arg of args){if(containsAIOutput(arg)){context.report({node:arg,messageId:"unsafeOutputExecution",data:{variable:sourceCode.getText(arg),function:sourceCode.getText(callee)}})}}}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({aiOutputPatterns}=options);sourceCode=context.sourceCode;AI_SDK_CALLS=new Set(["generateText","streamText","generateObject","streamObject"]);outputFlow={sourceCode,isSource:node=>node.type==="CallExpression"&&isAISDKCall(node)||(0,sdk_1.isSdkHookCall)(node,sourceCode),isSourceParameter:(variable,fn)=>fn.parent.type==="Property"&&isToolExecute(fn.parent)||(0,sdk_1.isSdkTypedParameter)(variable,sourceCode)};return true},NewExpression(node){const isFunction=isGlobal(node.callee,"Function");const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));if(isFunction||binding?.module==="vm"&&binding.path[0]==="Script"){reportExecution(node,node.callee,node.arguments)}},CallExpression(node){if(isCodeExecutionCallee(node.callee)){reportExecution(node,node.callee,node.arguments);return}const name=(0,sdk_1.calleeName)(node.callee);if(isGlobal(node.callee,"fetch")){const[target]=node.arguments;if(target&&choosesRequestTarget(target)){context.report({node:target,messageId:"unsafeOutputInRequest"})}return}if(!SQL_SINKS.has(name))return;const[query]=node.arguments;if(query&&containsAIOutput(query)){context.report({node:query,messageId:"unsafeOutputInSQL"})}},AssignmentExpression(node){if(node.left.type==="MemberExpression"&&(0,eslint_devkit_1.propertyName)(node.left)==="innerHTML"&&isLikelyAIOutput(node.right)){context.report({node:node.right,messageId:"unsafeOutputInHTML"})}},JSXAttribute(node){if(node.name.name!=="dangerouslySetInnerHTML")return;const container=node.value;if(container?.type!==eslint_devkit_1.AST_NODE_TYPES.JSXExpressionContainer||container.expression.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return}for(const prop of container.expression.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)==="__html"&&isLikelyAIOutput(prop.value)){context.report({node:prop.value,messageId:"unsafeOutputInHTML"})}}}}}});const CHILD_PROCESS_SINKS=new Set(["exec","execSync","execFile","execFileSync","spawn","spawnSync","fork"]);const VM_SINKS=new Set(["runInNewContext","runInThisContext","runInContext","compileFunction"]);const SQL_SINKS=new Set(["query","execute","raw","run","unsafe","$queryRawUnsafe","$executeRawUnsafe"]);const TOOL_FACTORIES=new Set(["tool","dynamicTool"]);const SCHEMA_KEYS=new Set(["inputSchema","parameters"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAbortSignal=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireAbortSignal=(0,eslint_devkit_1.createRule)({name:"require-abort-signal",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let targetFunctions;return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({targetFunctions}=options);return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,targetFunctions);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;if(!(0,sdk_1.declaresOption)(optionsArg,["abortSignal","signal"])){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");const sdk_1=require("../../utils/sdk");exports.requireAbortSignal=(0,eslint_devkit_1.createRule)({name:"require-abort-signal",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let targetFunctions;return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({targetFunctions}=options);return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode,targetFunctions);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;if(!(0,sdk_1.declaresOption)(optionsArg,["abortSignal","signal"])){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");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const path=(0,eslint_devkit_1.memberPath)(node.expression.callee);if(path===null)return false;return LOGGER_ROOTS.has(path[0].toLowerCase())||path.length>1&&LOG_LEVELS.has(path[path.length-1])}function hasTelemetry(node){const optionsArg=node.arguments[0];if(optionsArg?.type!=="ObjectExpression")return false;return["experimental_telemetry","telemetry"].some(key=>{const value=(0,sdk_1.optionValue)(optionsArg,key);return value?.type==="ObjectExpression"&&(0,sdk_1.optionValue)(value,"isEnabled")?.type==="Literal"&&(0,sdk_1.optionValue)(value,"isEnabled").value===true})}function hasNearbyLogging(node){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.body;const idx=statements.indexOf(statement);for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node);if(!matchedFunction)return;if(!hasTelemetry(node)&&!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});const LOGGER_ROOTS=new Set(["log","logger","console","debug","winston","pino","bunyan","audit"]);const LOG_LEVELS=new Set(["log","info","warn","error","debug","trace","fatal","audit"]);
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const path=(0,eslint_devkit_1.memberPath)(node.expression.callee);if(path===null)return false;return LOGGER_ROOTS.has(path[0].toLowerCase())||path.length>1&&LOG_LEVELS.has(path[path.length-1])}function hasTelemetry(node){const optionsArg=node.arguments[0];if(optionsArg?.type!=="ObjectExpression")return false;return["experimental_telemetry","telemetry"].some(key=>{const value=(0,sdk_1.optionValue)(optionsArg,key);return value?.type==="ObjectExpression"&&(0,sdk_1.optionValue)(value,"isEnabled")?.type==="Literal"&&(0,sdk_1.optionValue)(value,"isEnabled").value===true})}function hasNearbyLogging(node){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.body;const idx=statements.indexOf(statement);for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode);if(!matchedFunction)return;if(!hasTelemetry(node)&&!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});const LOGGER_ROOTS=new Set(["log","logger","console","debug","winston","pino","bunyan","audit"]);const LOG_LEVELS=new Set(["log","info","warn","error","debug","trace","fatal","audit"]);
|
|
@@ -1 +1 @@
|
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireEmbeddingValidation=(0,eslint_devkit_1.createRule)({name:"require-embedding-validation",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let embeddingPatterns;let validatorFunctions;let sourceCode;let vectorStoreOps;function isEmbeddingCall(node){const chain=(0,sdk_1.calleeChain)(node.callee);return embeddingPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))?sourceCode.getText(node.callee):null}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}function isVectorStoreOp(node){const chain=(0,sdk_1.calleeChain)(node.callee);return vectorStoreOps.some(op=>(0,eslint_devkit_1.nameHasWord)(chain,op))}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({embeddingPatterns,validatorFunctions}=options);sourceCode=context.sourceCode;vectorStoreOps=["upsert","insert","add","store","index","save"];return true},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=(0,eslint_devkit_1.objectKeyName)(prop);if(keyName==="embedding"||keyName==="vector"||keyName==="values"){
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireEmbeddingValidation=(0,eslint_devkit_1.createRule)({name:"require-embedding-validation",meta:{interop:{runtimes:["eslint","oxlint"]},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"]}],createOnce(context,currentOptions){let embeddingPatterns;let validatorFunctions;let sourceCode;let vectorStoreOps;function isEmbeddingCall(node){const chain=(0,sdk_1.calleeChain)(node.callee);return embeddingPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))?sourceCode.getText(node.callee):null}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}function isVectorStoreOp(node){const chain=(0,sdk_1.calleeChain)(node.callee);return vectorStoreOps.some(op=>(0,eslint_devkit_1.nameHasWord)(chain,op))}function sourceCall(value){let target=(0,sdk_1.unwrap)(value);if(target.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const variable=(0,sdk_1.lookupVariable)(target.name,sourceCode.getScope(target));const def=variable?.defs[0];if(def?.type!=="Variable"||def.node.init===null||variable?.references.some(ref=>ref.isWrite()&&!ref.init)){return null}target=(0,sdk_1.unwrap)(def.node.init)}return target.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression?target:null}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({embeddingPatterns,validatorFunctions}=options);sourceCode=context.sourceCode;vectorStoreOps=["upsert","insert","add","store","index","save"];return true},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=(0,eslint_devkit_1.objectKeyName)(prop);if(keyName==="embedding"||keyName==="vector"||keyName==="values"){const valueNode=sourceCall(prop.value);if(valueNode){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");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){function isInsideTryBlock(node){let parent=node.parent;
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){function isAwaitedCallback(fn){let current=fn;while(current.parent?.type==="CallExpression"||current.parent?.type==="ArrayExpression"){current=current.parent}return current.parent?.type==="AwaitExpression"}function isInsideTryBlock(node){let child=node;for(let parent=node.parent;parent;child=parent,parent=parent.parent){if(parent.type==="TryStatement"&&parent.block===child&&parent.handler){return true}if((child.type==="ArrowFunctionExpression"||child.type==="FunctionExpression"||child.type==="FunctionDeclaration")&&!isAwaitedCallback(child)){return false}}return false}function hasStreamErrorHandler(node,fn){const optionsArg=node.arguments[0];return STREAMING.has(fn)&&optionsArg?.type==="ObjectExpression"&&(0,sdk_1.optionValue)(optionsArg,"onError")!==void 0}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode);if(!matchedFunction)return;if(hasStreamErrorHandler(node,matchedFunction))return;if(!isInsideTryBlock(node)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}}}});const STREAMING=new Set(["streamText","streamObject"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireMaxSteps=(0,eslint_devkit_1.createRule)({name:"require-max-steps",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-steps.md",description:"Disallow unbounded multi-step tool loops (hasToolCall-only stop conditions, request-controlled step limits)",cwe:"CWE-834",cvss:6.5},messages:{unboundedSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unbounded Multi-Step Tool Loop",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools sets a step limit that does not bound the loop ({{reason}}).",severity:"MEDIUM",compliance:["SOC2"],fix:"Bound the loop with a server-side constant: stopWhen: [hasToolCall(...), stepCountIs(
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxSteps=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireMaxSteps=(0,eslint_devkit_1.createRule)({name:"require-max-steps",meta:{interop:{runtimes:["eslint","oxlint"]},type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-steps.md",description:"Disallow unbounded multi-step tool loops (hasToolCall-only stop conditions, request-controlled step limits)",cwe:"CWE-834",cvss:6.5},messages:{unboundedSteps:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unbounded Multi-Step Tool Loop",cwe:"CWE-834",owasp:"A05:2021",cvss:6.5,description:"{{function}} with tools sets a step limit that does not bound the loop ({{reason}}).",severity:"MEDIUM",compliance:["SOC2"],fix:"Bound the loop with a server-side constant: stopWhen: [hasToolCall(...), stepCountIs({{limit}})] (v5+) or maxSteps: {{limit}} (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:"Step limit the fix text suggests (`stepCountIs(n)` / `maxSteps: n`)",default:5}},additionalProperties:false}]},defaultOptions:[{suggestedMaxSteps:5}],createOnce(context,currentOptions){let sourceCode;let suggestedMaxSteps;function unboundedReason(key,value){if((0,sdk_1.isRequestDerived)(value,sourceCode))return`${key} comes from the request`;if(value.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&value.name==="Infinity"){return`${key} is Infinity`}if(key!=="stopWhen")return null;const conditions=value.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression?value.elements:[value];const named=conditions.map(c=>c?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression?(0,sdk_1.calleeName)(c.callee):null);if(named.every(name=>name==="hasToolCall")){return"hasToolCall only stops if the model calls that tool"}for(const condition of conditions){if(condition?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&STEP_COUNTERS.has((0,sdk_1.calleeName)(condition.callee))&&condition.arguments.some(arg=>(0,sdk_1.isRequestDerived)(arg,sourceCode))){return"the step count comes from the request"}}return null}return{before(){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;sourceCode=context.sourceCode;({suggestedMaxSteps}=currentOptions()[0]);return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode,FUNCTIONS_WITH_TOOLS);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(optionsArg?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;if(!(0,sdk_1.optionValue)(optionsArg,"tools"))return;for(const key of STEP_KEYS){const value=(0,sdk_1.optionValue)(optionsArg,key);const reason=value&&unboundedReason(key,value);if(reason){context.report({node:value,messageId:"unboundedSteps",data:{function:matchedFunction,reason,limit:String(suggestedMaxSteps)}})}}}}}});const FUNCTIONS_WITH_TOOLS=["generateText","streamText"];const STEP_KEYS=["maxSteps","max_steps","stopWhen"];const STEP_COUNTERS=new Set(["stepCountIs","isStepCount"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireMaxTokens=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireMaxTokens=(0,eslint_devkit_1.createRule)({name:"require-max-tokens",meta:{interop:{runtimes:["eslint","oxlint"]},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:
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireMaxTokens=(0,eslint_devkit_1.createRule)({name:"require-max-tokens",meta:{interop:{runtimes:["eslint","oxlint"]},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: {{limit}}, ... }) (v5+) or maxTokens (v4)",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/generating-text"})},schema:[{type:"object",properties:{suggestedLimit:{type:"number",description:"Token limit the fix text suggests (`maxOutputTokens: n`)",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"]}],createOnce(context,currentOptions){let targetFunctions;let suggestedLimit;return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({targetFunctions,suggestedLimit}=options);return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode,targetFunctions);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;if(!(0,sdk_1.declaresOption)(optionsArg,TOKEN_LIMIT_KEYS)){context.report({node,messageId:"missingMaxTokens",data:{function:matchedFunction,limit:String(suggestedLimit)}})}}}}});const TOKEN_LIMIT_KEYS=["maxTokens","max_tokens","maxOutputTokens","max_output_tokens"];
|
|
@@ -1 +1 @@
|
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireOutputFiltering=(0,eslint_devkit_1.createRule)({name:"require-output-filtering",meta:{interop:{runtimes:["eslint","oxlint"]},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","aggregate","database","db","sql","mongo","prisma","supabase"],filterFunctions:["filter","sanitize","redact","mask","clean","filterSensitive","removePII","scrub"]}],createOnce(context,currentOptions){let dataSourcePatterns;let filterFunctions;let sourceCode;function isDataSourceCall(node){const target=(0,sdk_1.unwrap)(node);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return null;if(target.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;const words=(0,sdk_1.calleeChain)(target.callee);if(!dataSourcePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(words,pattern)))return null;if(filterFunctions.some(filter=>(0,eslint_devkit_1.nameHasWord)(words,filter)))return null;return sourceCode.getText(target.callee)}function boundDataSource(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return isDataSourceCall(node);const def=(0,sdk_1.lookupVariable)(node.name,sourceCode.getScope(node))?.defs[0];return def?.type==="Variable"&&def.node.init!==null?isDataSourceCall(def.node.init):null}function getToolName(node){let current=node.parent;
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireOutputFiltering=(0,eslint_devkit_1.createRule)({name:"require-output-filtering",meta:{interop:{runtimes:["eslint","oxlint"]},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","aggregate","database","db","sql","mongo","prisma","supabase"],filterFunctions:["filter","sanitize","redact","mask","clean","filterSensitive","removePII","scrub"]}],createOnce(context,currentOptions){let dataSourcePatterns;let filterFunctions;let sourceCode;function isDataSourceCall(node){const target=(0,sdk_1.unwrap)(node);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return null;if(target.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;const words=(0,sdk_1.calleeChain)(target.callee);if(!dataSourcePatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(words,pattern)))return null;if(filterFunctions.some(filter=>(0,eslint_devkit_1.nameHasWord)(words,filter)))return null;return sourceCode.getText(target.callee)}function boundDataSource(node){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return isDataSourceCall(node);const def=(0,sdk_1.lookupVariable)(node.name,sourceCode.getScope(node))?.defs[0];return def?.type==="Variable"&&def.node.init!==null?isDataSourceCall(def.node.init):null}function getToolName(node){for(let current=node.parent;current;current=current.parent){const holder=current.parent?.parent;if(current.type==="Property"&&holder?.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(holder)==="tools"){return(0,eslint_devkit_1.objectKeyName)(current)??"unknown"}}return"unknown"}function isExecute(fn){const parent=fn.parent;return parent?.type==="Property"&&(0,eslint_devkit_1.objectKeyName)(parent)==="execute"}function report(node,fn,source){context.report({node,messageId:"missingOutputFilter",data:{toolName:getToolName(fn),source}})}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({dataSourcePatterns,filterFunctions}=options);sourceCode=context.sourceCode;return true},ArrowFunctionExpression(node){if(!isExecute(node))return;const source=isDataSourceCall(node.body);if(source)report(node.body,node,source)},ReturnStatement(node){if(!node.argument)return;let fn=node.parent;while(fn&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionExpression&&fn.type!==eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration){fn=fn.parent}if(!fn||!isExecute(fn))return;const source=boundDataSource((0,sdk_1.unwrap)(node.argument));if(source)report(node.argument,fn,source)}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRagContentValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.requireRagContentValidation=(0,eslint_devkit_1.createRule)({name:"require-rag-content-validation",meta:{interop:{runtimes:["eslint","oxlint"]},type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-rag-content-validation.md",description:"Require validation of RAG content before including in AI prompts",cwe:"CWE-74",cvss:6},messages:{unsanitizedRagContent:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsanitized RAG Content",cwe:"CWE-74",owasp:"A03:2021",cvss:6,description:'RAG content from "{{source}}" used directly in prompt without validation. Poisoned documents can inject malicious instructions.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate RAG content: prompt: buildPrompt(validateRagContent(docs))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{ragPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting RAG operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate RAG content"}},additionalProperties:false}]},defaultOptions:[{ragPatterns:["search","retrieve","query","vectorStore","embeddings","similaritySearch","findSimilar","getDocuments","fetchDocs","documents","chunks","passages"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{ragPatterns,validatorFunctions}=options;const sourceCode=context.sourceCode;function isRagCall(node){if(node.type!=="CallExpression"||(0,flow_1.isDerivationCall)(node))return false;const chain=(0,sdk_1.calleeChain)(node.callee);return ragPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))&&!isValidated(node)}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}const ragFlow={sourceCode,isSource:isRagCall,isBarrier:node=>node.type==="CallExpression"&&!(0,flow_1.isDerivationCall)(node)&&isValidated(node)};function containsRagContent(node){const parts=node.type==="TemplateLiteral"?node.expressions:[node];const found=parts.find(part=>(0,flow_1.derivesFrom)(part,ragFlow));return found?sourceCode.getText(found):null}return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(keyName!=="prompt"&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const ragSource=containsRagContent(prop.value);if(ragSource){context.report({node:prop.value,messageId:"unsanitizedRagContent",data:{source:ragSource}})}}}}}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRagContentValidation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");const flow_1=require("../../utils/flow");exports.requireRagContentValidation=(0,eslint_devkit_1.createRule)({name:"require-rag-content-validation",meta:{interop:{runtimes:["eslint","oxlint"]},type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-rag-content-validation.md",description:"Require validation of RAG content before including in AI prompts",cwe:"CWE-74",cvss:6},messages:{unsanitizedRagContent:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unsanitized RAG Content",cwe:"CWE-74",owasp:"A03:2021",cvss:6,description:'RAG content from "{{source}}" used directly in prompt without validation. Poisoned documents can inject malicious instructions.',severity:"MEDIUM",compliance:["SOC2"],fix:"Validate RAG content: prompt: buildPrompt(validateRagContent(docs))",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{ragPatterns:{type:"array",items:{type:"string"},description:"Patterns suggesting RAG operations"},validatorFunctions:{type:"array",items:{type:"string"},description:"Functions that validate RAG content"}},additionalProperties:false}]},defaultOptions:[{ragPatterns:["search","retrieve","query","vectorStore","embeddings","similaritySearch","findSimilar","getDocuments","fetchDocs","documents","chunks","passages"],validatorFunctions:["validate","sanitize","filter","clean","verify","validateRag","sanitizeContent","filterDocs"]}],create(context,[options]){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const{ragPatterns,validatorFunctions}=options;const sourceCode=context.sourceCode;function isRagCall(node){if(node.type!=="CallExpression"||(0,flow_1.isDerivationCall)(node))return false;const chain=(0,sdk_1.calleeChain)(node.callee);return ragPatterns.some(pattern=>(0,eslint_devkit_1.nameHasWord)(chain,pattern))&&!isValidated(node)}function isValidated(node){const chain=(0,sdk_1.calleeChain)(node.callee);return validatorFunctions.some(fn=>(0,eslint_devkit_1.nameHasWord)(chain,fn))}const ragFlow={sourceCode,isSource:isRagCall,isBarrier:node=>node.type==="CallExpression"&&!(0,flow_1.isDerivationCall)(node)&&isValidated(node)};function containsRagContent(node){const parts=node.type==="TemplateLiteral"?node.expressions:[node];const found=parts.find(part=>(0,flow_1.derivesFrom)(part,ragFlow));return found?sourceCode.getText(found):null}return{CallExpression(node){if(!(0,sdk_1.sdkCallName)(node,context.sourceCode))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");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(optionsArg&&optionsArg.type!=="ObjectExpression")return;if(!optionsArg||!(0,sdk_1.declaresOption)(optionsArg,TIMEOUT_KEYS)){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});const TIMEOUT_KEYS=["timeout","abortSignal","signal","timeoutMs","requestTimeout"];
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(optionsArg&&optionsArg.type!=="ObjectExpression")return;if(!optionsArg||!(0,sdk_1.declaresOption)(optionsArg,TIMEOUT_KEYS)){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});const TIMEOUT_KEYS=["timeout","abortSignal","signal","timeoutMs","requestTimeout"];
|
|
@@ -1 +1 @@
|
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireToolConfirmation=(0,eslint_devkit_1.createRule)({name:"require-tool-confirmation",meta:{interop:{runtimes:["eslint","oxlint"]},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 needsApproval: true to the tool (or toolApproval on the call), or omit execute so the client confirms",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","exec","eval","spawn","shell","update","modify","alter","insert","write"]}],createOnce(context,currentOptions){let destructivePatterns;function toolDefinition(value){if(value.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return value;if(value.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(value.callee))&&value.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return value.arguments[0]}return null}function isConfirmed(def){let hasExecute=false;for(const prop of def.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return true;const key=(0,eslint_devkit_1.objectKeyName)(prop);if(CONFIRMATION_PROPS.has(key))return true;if(key==="execute")hasExecute=true}return!hasExecute}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({destructivePatterns}=options);return true},Property(node){const toolName=(0,eslint_devkit_1.objectKeyName)(node);if(toolName===null)return;const def=toolDefinition(node.value);if(!def)return;const operation=destructivePatterns.find(pattern=>(0,eslint_devkit_1.nameHasWord)(toolName,pattern));if(!operation)return;const tools=node.parent;if(tools?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;const toolsProp=tools.parent;if(toolsProp?.type!==eslint_devkit_1.AST_NODE_TYPES.Property||(0,eslint_devkit_1.objectKeyName)(toolsProp)!=="tools")return;if((0,sdk_1.optionValue)(toolsProp.parent,"toolApproval"))return;if(isConfirmed(def))return;context.report({node,messageId:"missingConfirmation",data:{toolName,operation}})}}}});const TOOL_FACTORIES=new Set(["tool","dynamicTool"]);const CONFIRMATION_PROPS=new Set(["needsApproval","requiresConfirmation","requireConfirmation","confirmation","requiresApproval","requireApproval","approval"]);
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireToolConfirmation=(0,eslint_devkit_1.createRule)({name:"require-tool-confirmation",meta:{interop:{runtimes:["eslint","oxlint"]},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 needsApproval: true to the tool (or toolApproval on the call), or omit execute so the client confirms",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","exec","eval","spawn","shell","update","modify","alter","insert","write"]}],createOnce(context,currentOptions){let destructivePatterns;function toolDefinition(value){if(value.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return value;if(value.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&TOOL_FACTORIES.has((0,sdk_1.calleeName)(value.callee))&&value.arguments[0]?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){return value.arguments[0]}return null}function isConfirmed(def){let hasExecute=false;for(const prop of def.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement)return true;const key=(0,eslint_devkit_1.objectKeyName)(prop);if(CONFIRMATION_PROPS.has(key))return true;if(key==="execute")hasExecute=true}return!hasExecute}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({destructivePatterns}=options);return true},Property(node){const toolName=(0,eslint_devkit_1.objectKeyName)(node);if(toolName===null)return;const def=toolDefinition(node.value);if(!def)return;const operation=destructivePatterns.find(pattern=>(0,eslint_devkit_1.nameHasWord)(toolName,pattern));if(!operation)return;const tools=node.parent;if(tools?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;const toolsProp=tools.parent;if(toolsProp?.type!==eslint_devkit_1.AST_NODE_TYPES.Property||(0,eslint_devkit_1.objectKeyName)(toolsProp)!=="tools")return;if((0,sdk_1.optionValue)(toolsProp.parent,"toolApproval"))return;if(isConfirmed(def))return;context.report({node,messageId:"missingConfirmation",data:{toolName,operation}})}}}});const TOOL_FACTORIES=new Set(["tool","dynamicTool"]);const CONFIRMATION_PROPS=new Set(["needsApproval","requiresConfirmation","requireConfirmation","confirmation","requiresApproval","requireApproval","approval","confirm"]);
|
|
@@ -1 +1 @@
|
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){let sourceCode;function toolKey(node){const parent=node.parent;return parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property?(0,eslint_devkit_1.objectKeyName)(parent):null}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;sourceCode=context.sourceCode;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const callee=sourceCode.getText(node.callee);const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression&&!(0,sdk_1.declaresOption)(toolArg,SCHEMA_KEYS)){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:toolKey(node)??"unnamed tool"}})}return}if(!(0,sdk_1.sdkCallName)(node))return;const optionsArg=node.arguments[0];if(optionsArg?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;const tools=(0,sdk_1.optionValue)(optionsArg,"tools");if(tools?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;
|
|
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");const sdk_1=require("../../utils/sdk");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){let sourceCode;function toolKey(node){const parent=node.parent;return parent?.type===eslint_devkit_1.AST_NODE_TYPES.Property?(0,eslint_devkit_1.objectKeyName)(parent):null}function checkToolsObject(tools,byName){for(const toolDef of tools.properties){if(toolDef.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;if(toolDef.value.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)continue;if(byName&&!(0,sdk_1.optionValue)(toolDef.value,"execute"))continue;if(!(0,sdk_1.declaresOption)(toolDef.value,SCHEMA_KEYS)){context.report({node:toolDef.value,messageId:"missingInputSchema",data:{toolName:(0,eslint_devkit_1.objectKeyName)(toolDef)??"unknown"}})}}}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;const{allowInTests}=options;sourceCode=context.sourceCode;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}return true},CallExpression(node){const callee=sourceCode.getText(node.callee);const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression&&!(0,sdk_1.declaresOption)(toolArg,SCHEMA_KEYS)){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:toolKey(node)??"unnamed tool"}})}return}if(!(0,sdk_1.sdkCallName)(node,context.sourceCode))return;const optionsArg=node.arguments[0];if(optionsArg?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;const tools=(0,sdk_1.optionValue)(optionsArg,"tools");if(tools?.type!==eslint_devkit_1.AST_NODE_TYPES.ObjectExpression)return;checkToolsObject(tools,false)},VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(0,sdk_1.nameEndsWithWords)(node.id.name,"tools")&&node.init?.type===eslint_devkit_1.AST_NODE_TYPES.ObjectExpression){checkToolsObject(node.init,true)}}}}});const SCHEMA_KEYS=["inputSchema","parameters"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){let validatorFunctions;let userInputPatterns;let allowInTests;let sourceCode;let pathPatterns;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");const sdk_1=require("../../utils/sdk");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{interop:{runtimes:["eslint","oxlint"]},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}],createOnce(context,currentOptions){let validatorFunctions;let userInputPatterns;let allowInTests;let sourceCode;let pathPatterns;function isValidatedCall(node){const target=(0,sdk_1.unwrap)(node);if(target.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const name=(0,sdk_1.calleeName)(target.callee);return validatorFunctions.includes(name)||SCHEMA_PARSERS.has(name)}function isValidatedBinding(node){const def=(0,sdk_1.lookupVariable)(node.name,sourceCode.getScope(node))?.defs[0];return def?.type==="Variable"&&def.node.init!==null&&isValidatedCall(def.node.init)}function nameLooksLikeInput(name){return userInputPatterns.some(pattern=>(0,sdk_1.nameEndsWithWords)(name,pattern))}function pathLooksLikeInput(node){const path=(0,eslint_devkit_1.memberPath)(node);return path!==null&&pathPatterns.some(pattern=>pattern.every((part,i)=>path[i]===part))}function findUserInput(node){if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const found=findUserInput(expr);if(found)return found}return null}if(node.type==="BinaryExpression"){return findUserInput(node.left)??findUserInput(node.right)}if(node.type==="Identifier"){if(isValidatedBinding(node))return null;const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode)||nameLooksLikeInput(node.name);return unsafe?node.name:null}if(node.type==="MemberExpression"){const unsafe=(0,sdk_1.isRequestDerived)(node,sourceCode)||pathLooksLikeInput(node)||nameLooksLikeInput((0,eslint_devkit_1.propertyName)(node)??"");return unsafe?sourceCode.getText(node):null}if(node.type==="CallExpression"&&!isValidatedCall(node)){const operands=node.callee.type==="MemberExpression"?[node.callee.object,...node.arguments]:node.arguments;for(const operand of operands){const found=findUserInput(operand);if(found)return found}}return null}return{before(){const[options]=currentOptions();if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return false;({validatorFunctions,userInputPatterns,allowInTests}=options);sourceCode=context.sourceCode;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(context.filename)){return false}pathPatterns=userInputPatterns.filter(p=>p.includes(".")).map(p=>p.split("."));return true},CallExpression(node){const matchedFunction=(0,sdk_1.sdkCallName)(node,context.sourceCode);if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);const isPrompt=keyName==="prompt";if(!isPrompt&&!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;const input=findUserInput(prop.value);if(!input)continue;context.report(isPrompt?{node:prop.value,messageId:"unsafePrompt",data:{input,function:matchedFunction}}:{node:prop.value,messageId:"unsafeSystemPrompt"})}}}}});const SCHEMA_PARSERS=new Set(["parse","safeParse","parseAsync","safeParseAsync","validateSync"]);
|
package/src/utils/sdk.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.lookupVariable=exports.AI_SDK_CALLS=void 0;exports.calleeName=calleeName;exports.sdkCallName=sdkCallName;exports.declaresOption=declaresOption;exports.optionValue=optionValue;exports.unwrap=unwrap;exports.isRequestDerived=isRequestDerived;exports.isStaticText=isStaticText;exports.isDateValue=isDateValue;exports.nameEndsWithWords=nameEndsWithWords;exports.calleeChain=calleeChain;exports.isSdkHookCall=isSdkHookCall;exports.isSdkTypedParameter=isSdkTypedParameter;const eslint_devkit_1=require("@interlace/eslint-devkit");const flow_1=require("./flow");exports.AI_SDK_CALLS=["generateText","streamText","generateObject","streamObject"];function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return(0,eslint_devkit_1.propertyName)(callee);return null}function sdkCallName(node,names=exports.AI_SDK_CALLS){const name=calleeName(node.callee);return name!==null&&names.includes(name)?name:null}function declaresOption(options,keys){return options.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?true:keys.includes((0,eslint_devkit_1.objectKeyName)(prop)))}function optionValue(options,key){for(const prop of options.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)===key){return prop.value}}return void 0}function unwrap(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression){current=current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression?current.argument:current.expression}return current}var flow_2=require("./flow");Object.defineProperty(exports,"lookupVariable",{enumerable:true,get:function(){return flow_2.lookupVariable}});function isParameter(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,flow_1.lookupVariable)(node.name,scope)?.defs[0]?.type==="Parameter"}function isRequestBody(node,scope){const target=unwrap(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=target.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const method=(0,eslint_devkit_1.propertyName)(callee);if((method==="get"||method==="getAll")&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee.object)==="searchParams"){return true}return["json","formData","text"].includes(method)&&isParameter(callee.object,scope)}return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(target)
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.lookupVariable=exports.AI_SDK_CALLS=void 0;exports.calleeName=calleeName;exports.sdkCallName=sdkCallName;exports.declaresOption=declaresOption;exports.optionValue=optionValue;exports.unwrap=unwrap;exports.isRequestDerived=isRequestDerived;exports.isStaticText=isStaticText;exports.isDateValue=isDateValue;exports.nameEndsWithWords=nameEndsWithWords;exports.calleeChain=calleeChain;exports.isSdkHookCall=isSdkHookCall;exports.isSdkTypedParameter=isSdkTypedParameter;const eslint_devkit_1=require("@interlace/eslint-devkit");const flow_1=require("./flow");exports.AI_SDK_CALLS=["generateText","streamText","generateObject","streamObject"];function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return(0,eslint_devkit_1.propertyName)(callee);return null}function sdkCallName(node,sourceCode,names=exports.AI_SDK_CALLS){const binding=node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node)):void 0;const name=binding&&isSdkModule(binding.module)&&binding.path.length===1?binding.path[0]:calleeName(node.callee);return name!==null&&names.includes(name)?name:null}function declaresOption(options,keys){return options.properties.some(prop=>prop.type===eslint_devkit_1.AST_NODE_TYPES.SpreadElement?true:keys.includes((0,eslint_devkit_1.objectKeyName)(prop)))}function optionValue(options,key){for(const prop of options.properties){if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Property&&(0,eslint_devkit_1.objectKeyName)(prop)===key){return prop.value}}return void 0}function unwrap(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSAsExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSNonNullExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.TSSatisfiesExpression){current=current.type===eslint_devkit_1.AST_NODE_TYPES.AwaitExpression?current.argument:current.expression}return current}var flow_2=require("./flow");Object.defineProperty(exports,"lookupVariable",{enumerable:true,get:function(){return flow_2.lookupVariable}});function isParameter(node,scope){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return false;return(0,flow_1.lookupVariable)(node.name,scope)?.defs[0]?.type==="Parameter"}function isRequestBody(node,scope){const target=unwrap(node);if(target.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=target.callee;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return false;const method=(0,eslint_devkit_1.propertyName)(callee);if((method==="get"||method==="getAll")&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&(0,eslint_devkit_1.propertyName)(callee.object)==="searchParams"){return true}return["json","formData","text"].includes(method)&&isParameter(callee.object,scope)}return target.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&REQUEST_INPUTS.has((0,eslint_devkit_1.propertyName)(target))&&isParameter(target.object,scope)}const REQUEST_INPUTS=new Set(["body","query"]);function isRequestDerived(node,sourceCode){return(0,flow_1.derivesFrom)(node,{sourceCode,isSource:candidate=>isRequestBody(candidate,sourceCode.getScope(candidate))})}function isDateValue(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression){return node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="Date"}if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression||node.callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression){return false}const receiver=node.callee.object;return receiver.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&receiver.name==="Date"||isDateValue(receiver)}function isStaticText(node,sourceCode,resolving=new Set){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Literal:return true;case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.every(expr=>isStaticText(expr,sourceCode,resolving));case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return node.operator==="+"&&isStaticText(node.left,sourceCode,resolving)&&isStaticText(node.right,sourceCode,resolving);case eslint_devkit_1.AST_NODE_TYPES.Identifier:{const variable=(0,flow_1.lookupVariable)(node.name,sourceCode.getScope(node));const def=variable?.defs[0];if(def?.type!=="Variable"||def.parent.kind!=="const"||def.node.init===null||resolving.has(variable)){return false}resolving.add(variable);const result=isStaticText(def.node.init,sourceCode,resolving);resolving.delete(variable);return result}case eslint_devkit_1.AST_NODE_TYPES.CallExpression:{const fn=(0,flow_1.sameFileFunction)(node.callee,sourceCode);if(!fn||resolving.has(fn))return isDateValue(node);resolving.add(fn);const returns=(0,flow_1.returnsOf)(fn,sourceCode);const result=returns.length>0&&returns.every(expr=>isStaticText(expr,sourceCode,resolving));resolving.delete(fn);return result}default:return isDateValue(node)}}function nameEndsWithWords(name,term){const words=(0,eslint_devkit_1.identifierWords)(name);const tail=(0,eslint_devkit_1.identifierWords)(term);const offset=words.length-tail.length;return offset>=0&&tail.every((word,i)=>words[offset+i]===word)}function calleeChain(callee){const parts=[];let current=unwrap(callee);while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){if(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){parts.unshift((0,eslint_devkit_1.propertyName)(current)??"");current=unwrap(current.object)}else{current=unwrap(current.callee)}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)parts.unshift(current.name);return parts.join(".")}function isSdkModule(module){return module==="ai"||module.startsWith("ai/")||module.startsWith("@ai-sdk/")}const SDK_UI_HOOKS=new Set(["useChat","useCompletion","useObject","useAssistant"]);function isSdkHookCall(node,sourceCode){if(node.type!==eslint_devkit_1.AST_NODE_TYPES.CallExpression)return false;const binding=(0,eslint_devkit_1.resolveModuleBinding)(node.callee,sourceCode.getScope(node));return binding!==void 0&&isSdkModule(binding.module)&&SDK_UI_HOOKS.has(binding.path[binding.path.length-1])}function referencesSdkType(node,sourceCode,seen=new Set){if(seen.has(node))return false;seen.add(node);if(node.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference){let name=node.typeName;while(name.type===eslint_devkit_1.AST_NODE_TYPES.TSQualifiedName)name=name.left;const id=name;const binding=(0,eslint_devkit_1.resolveModuleBinding)(id,sourceCode.getScope(node));if(binding&&isSdkModule(binding.module))return true;const alias=typeDeclaration(id,sourceCode);if(alias&&referencesSdkType(alias,sourceCode,seen))return true}for(const key of sourceCode.visitorKeys[node.type]){const child=node[key];for(const item of Array.isArray(child)?child:[child]){if(item&&typeof item.type==="string"&&referencesSdkType(item,sourceCode,seen)){return true}}}return false}function typeDeclaration(id,sourceCode){const def=(0,flow_1.lookupVariable)(id.name,sourceCode.getScope(id))?.defs[0];if(def?.node.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeAliasDeclaration)return def.node.typeAnnotation;if(def?.node.type===eslint_devkit_1.AST_NODE_TYPES.TSInterfaceDeclaration)return def.node.body;return void 0}function memberAnnotation(type,key,sourceCode){let body=type;if(body?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeReference&&body.typeName.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){body=typeDeclaration(body.typeName,sourceCode)}const members=body?.type===eslint_devkit_1.AST_NODE_TYPES.TSTypeLiteral?body.members:body?.type===eslint_devkit_1.AST_NODE_TYPES.TSInterfaceBody?body.body:[];const member=members.find(m=>m.type===eslint_devkit_1.AST_NODE_TYPES.TSPropertySignature&&(0,eslint_devkit_1.objectKeyName)(m)===key);return member?.typeAnnotation}function isSdkTypedParameter(variable,sourceCode){const name=variable.defs[0].name;let annotation=name.typeAnnotation;const holder=name.parent;if(!annotation&&holder.type===eslint_devkit_1.AST_NODE_TYPES.Property&&holder.parent.type===eslint_devkit_1.AST_NODE_TYPES.ObjectPattern){annotation=memberAnnotation(holder.parent.typeAnnotation?.typeAnnotation,(0,eslint_devkit_1.objectKeyName)(holder),sourceCode)}return annotation!==void 0&&referencesSdkType(annotation,sourceCode)}
|