eslint-plugin-vercel-ai-security 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -23
- package/package.json +2 -2
- package/src/index.js +1 -1
- package/src/rules/no-hardcoded-api-keys/index.js +1 -1
- package/src/rules/require-audit-logging/index.js +1 -1
- package/src/rules/require-error-handling/index.js +1 -1
- package/src/rules/require-request-timeout/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/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security" target="blank"><img src="https://eslint.interlace.tools/logos/interlace.svg" alt="Interlace" height="90" /></a>
|
|
2
|
+
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security" target="blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://eslint.interlace.tools/logos/interlace-dark.svg"><img src="https://eslint.interlace.tools/logos/interlace-light.svg" alt="Interlace" height="90" /></picture></a>
|
|
3
3
|
|
|
4
4
|
<a href="https://vercel.com/docs/ai-sdk" target="_blank"><img src="https://eslint.interlace.tools/logos/vercel.svg" alt="Vercel AI SDK" height="90" /></a>
|
|
5
5
|
|
|
6
|
-
<a href="https://oxc.rs" target="_blank"><img src="https://eslint.interlace.tools/logos/oxlint.svg" alt="oxlint" height="90" /></a>
|
|
6
|
+
<a href="https://oxc.rs" target="_blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://eslint.interlace.tools/logos/oxlint-dark.svg"><img src="https://eslint.interlace.tools/logos/oxlint-light.svg" alt="oxlint" height="90" /></picture></a>
|
|
7
7
|
|
|
8
|
-
<a href="https://eslint.org" target="_blank"><img src="https://eslint.interlace.tools/logos/eslint.svg" alt="ESLint" height="90" /></a>
|
|
8
|
+
<a href="https://eslint.org" target="_blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://eslint.interlace.tools/logos/eslint-dark.svg"><img src="https://eslint.interlace.tools/logos/eslint-light.svg" alt="ESLint" height="90" /></picture></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
|
|
19
19
|
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=eslint-plugin-vercel-ai-security" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-plugin-vercel-ai-security" alt="Codecov" /></a>
|
|
20
20
|
<a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
|
|
21
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/ofri-peretz/eslint" target="_blank"><img src="https://api.securityscorecards.dev/projects/github.com/ofri-peretz/eslint/badge" alt="OpenSSF Scorecard" /></a>
|
|
21
22
|
</p>
|
|
22
23
|
|
|
23
24
|
> ⭐ If this plugin caught a real bug for you, [**star the repo**](https://github.com/ofri-peretz/eslint) — it's the signal that keeps these rules maintained.
|
|
@@ -26,18 +27,25 @@
|
|
|
26
27
|
|
|
27
28
|
This plugin provides Security rules for Vercel AI SDK usage (prompt injection, data handling).
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
<!-- AUTO-GENERATED:DOCTRINE:START - Do not edit manually -->
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
- **Why** — a linter nobody reads protects nothing. We would rather miss a finding
|
|
33
|
+
than spend your attention on one that was never real.
|
|
34
|
+
- **How** — evidence, not names. A rule fires on what the code *does*, resolved
|
|
35
|
+
through the AST and ESLint's own scope analysis.
|
|
36
|
+
- **What** — every finding carries its fix, in prose for a human and as structured
|
|
37
|
+
JSON for an agent. Security rules add a CWE mapping and, where assigned, a CVSS score.
|
|
38
|
+
|
|
39
|
+
That trade costs recall, and we measure it:
|
|
40
|
+
[methodology](https://github.com/ofri-peretz/eslint/blob/main/BENCHMARK-METHODOLOGY.md)
|
|
41
|
+
· [results](https://github.com/ofri-peretz/eslint/blob/main/BENCHMARK-RESULTS.md)
|
|
42
|
+
· [a false positive is a bug](https://github.com/ofri-peretz/eslint/issues).
|
|
43
|
+
|
|
44
|
+
<!-- AUTO-GENERATED:DOCTRINE:END -->
|
|
32
45
|
|
|
33
46
|
## Getting Started
|
|
34
47
|
|
|
35
48
|
- To check out the [guide](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security). 📚
|
|
36
|
-
- 要查看中文 [指南](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security). 📚
|
|
37
|
-
- [가이드](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security)에서 확인하실 수 있습니다. 📚
|
|
38
|
-
- [ガイド](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security)は [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security)でご確認ください。 📚
|
|
39
|
-
- Para ver la [guía](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security), visita [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security). 📚
|
|
40
|
-
- للاطلاع على [الدليل](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security). 📚
|
|
41
49
|
|
|
42
50
|
```bash
|
|
43
51
|
npm install eslint-plugin-vercel-ai-security --save-dev
|
|
@@ -150,23 +158,52 @@ See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) fo
|
|
|
150
158
|
| [require-tool-schema](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-tool-schema?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-20 | | | Get weather | 🟢 | | | | | |
|
|
151
159
|
| [require-validated-prompt](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-validated-prompt?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-74 | | | This rule identifies code patterns where user-controlled input is passed directly to AI prompts without val… | 🟢 | | | | | |
|
|
152
160
|
<!-- AUTO-GENERATED:RULES_TABLE:END -->
|
|
161
|
+
<!-- AUTO-GENERATED:ECOSYSTEM_TABLE:START - Do not edit manually -->
|
|
162
|
+
|
|
153
163
|
## 🔗 Related ESLint Plugins
|
|
154
164
|
|
|
155
|
-
Part of the **Interlace ESLint
|
|
165
|
+
Part of the **Interlace ESLint ecosystem** — AI-native rules with LLM-optimized error messages:
|
|
166
|
+
|
|
167
|
+
**Security**
|
|
168
|
+
|
|
169
|
+
| Plugin | Downloads | Description |
|
|
170
|
+
| :--- | :---: | :--- |
|
|
171
|
+
| [`eslint-plugin-anthropic-security`](https://www.npmjs.com/package/eslint-plugin-anthropic-security) | [](https://www.npmjs.com/package/eslint-plugin-anthropic-security) | Anthropic SDK security. |
|
|
172
|
+
| [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) | [](https://www.npmjs.com/package/eslint-plugin-browser-security) | XSS, DOM security. |
|
|
173
|
+
| [`eslint-plugin-drizzle-security`](https://www.npmjs.com/package/eslint-plugin-drizzle-security) | [](https://www.npmjs.com/package/eslint-plugin-drizzle-security) | Drizzle security. |
|
|
174
|
+
| [`eslint-plugin-express-security`](https://www.npmjs.com/package/eslint-plugin-express-security) | [](https://www.npmjs.com/package/eslint-plugin-express-security) | Express middleware hardening. |
|
|
175
|
+
| [`eslint-plugin-gemini-security`](https://www.npmjs.com/package/eslint-plugin-gemini-security) | [](https://www.npmjs.com/package/eslint-plugin-gemini-security) | Google Gemini SDK security. |
|
|
176
|
+
| [`eslint-plugin-jwt-security`](https://www.npmjs.com/package/eslint-plugin-jwt-security) | [](https://www.npmjs.com/package/eslint-plugin-jwt-security) | Token security. |
|
|
177
|
+
| [`eslint-plugin-knex-security`](https://www.npmjs.com/package/eslint-plugin-knex-security) | [](https://www.npmjs.com/package/eslint-plugin-knex-security) | Knex security. |
|
|
178
|
+
| [`eslint-plugin-lambda-security`](https://www.npmjs.com/package/eslint-plugin-lambda-security) | [](https://www.npmjs.com/package/eslint-plugin-lambda-security) | AWS Lambda hardening. |
|
|
179
|
+
| [`eslint-plugin-mcp-sdk-security`](https://www.npmjs.com/package/eslint-plugin-mcp-sdk-security) | [](https://www.npmjs.com/package/eslint-plugin-mcp-sdk-security) | MCP SDK security. |
|
|
180
|
+
| [`eslint-plugin-mongodb-security`](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | [](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | MongoDB injection. |
|
|
181
|
+
| [`eslint-plugin-mysql-security`](https://www.npmjs.com/package/eslint-plugin-mysql-security) | [](https://www.npmjs.com/package/eslint-plugin-mysql-security) | MySQL security. |
|
|
182
|
+
| [`eslint-plugin-nestjs-security`](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | [](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | NestJS framework hardening. |
|
|
183
|
+
| [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [](https://www.npmjs.com/package/eslint-plugin-node-security) | Server-side patterns. |
|
|
184
|
+
| [`eslint-plugin-openai-security`](https://www.npmjs.com/package/eslint-plugin-openai-security) | [](https://www.npmjs.com/package/eslint-plugin-openai-security) | OpenAI SDK security. |
|
|
185
|
+
| [`eslint-plugin-postgresql-security`](https://www.npmjs.com/package/eslint-plugin-postgresql-security) | [](https://www.npmjs.com/package/eslint-plugin-postgresql-security) | PostgreSQL security. |
|
|
186
|
+
| [`eslint-plugin-prisma-security`](https://www.npmjs.com/package/eslint-plugin-prisma-security) | [](https://www.npmjs.com/package/eslint-plugin-prisma-security) | Prisma security. |
|
|
187
|
+
| [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [](https://www.npmjs.com/package/eslint-plugin-secure-coding) | Injection prevention. |
|
|
188
|
+
| [`eslint-plugin-sequelize-security`](https://www.npmjs.com/package/eslint-plugin-sequelize-security) | [](https://www.npmjs.com/package/eslint-plugin-sequelize-security) | Sequelize ORM security. |
|
|
189
|
+
| [`eslint-plugin-sqlite-security`](https://www.npmjs.com/package/eslint-plugin-sqlite-security) | [](https://www.npmjs.com/package/eslint-plugin-sqlite-security) | SQLite security. |
|
|
190
|
+
| [`eslint-plugin-typeorm-security`](https://www.npmjs.com/package/eslint-plugin-typeorm-security) | [](https://www.npmjs.com/package/eslint-plugin-typeorm-security) | TypeORM security. |
|
|
191
|
+
|
|
192
|
+
**Code quality**
|
|
156
193
|
|
|
157
194
|
| Plugin | Downloads | Description |
|
|
158
195
|
| :--- | :---: | :--- |
|
|
159
|
-
| [`eslint-plugin-
|
|
160
|
-
| [`eslint-plugin-
|
|
161
|
-
| [`eslint-plugin-
|
|
162
|
-
| [`eslint-plugin-
|
|
163
|
-
| [`eslint-plugin-
|
|
164
|
-
| [`eslint-plugin-
|
|
165
|
-
| [`eslint-plugin-
|
|
166
|
-
| [`eslint-plugin-
|
|
167
|
-
| [`eslint-plugin-
|
|
168
|
-
|
|
169
|
-
|
|
196
|
+
| [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) | [](https://www.npmjs.com/package/eslint-plugin-conventions) | Team-specific habits and styles. |
|
|
197
|
+
| [`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. |
|
|
198
|
+
| [`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. |
|
|
199
|
+
| [`eslint-plugin-modernization`](https://www.npmjs.com/package/eslint-plugin-modernization) | [](https://www.npmjs.com/package/eslint-plugin-modernization) | ESNext migration + syntax evolution. |
|
|
200
|
+
| [`eslint-plugin-modularity`](https://www.npmjs.com/package/eslint-plugin-modularity) | [](https://www.npmjs.com/package/eslint-plugin-modularity) | Structural integrity and DDD patterns. |
|
|
201
|
+
| [`eslint-plugin-operability`](https://www.npmjs.com/package/eslint-plugin-operability) | [](https://www.npmjs.com/package/eslint-plugin-operability) | Production readiness and resource health. |
|
|
202
|
+
| [`eslint-plugin-react-a11y`](https://www.npmjs.com/package/eslint-plugin-react-a11y) | [](https://www.npmjs.com/package/eslint-plugin-react-a11y) | React accessibility / WCAG. |
|
|
203
|
+
| [`eslint-plugin-react-features`](https://www.npmjs.com/package/eslint-plugin-react-features) | [](https://www.npmjs.com/package/eslint-plugin-react-features) | React best practices and optimization. |
|
|
204
|
+
| [`eslint-plugin-reliability`](https://www.npmjs.com/package/eslint-plugin-reliability) | [](https://www.npmjs.com/package/eslint-plugin-reliability) | Runtime stability and error safety. |
|
|
205
|
+
|
|
206
|
+
<!-- AUTO-GENERATED:ECOSYSTEM_TABLE:END -->
|
|
170
207
|
|
|
171
208
|
<!-- INTERLACE:STAR_CTA:START -->
|
|
172
209
|
## ⭐ Support & follow
|
|
@@ -186,5 +223,5 @@ MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
|
|
186
223
|
</p>
|
|
187
224
|
|
|
188
225
|
<p align="center">
|
|
189
|
-
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security" target="blank"><img src="https://eslint.interlace.tools/
|
|
226
|
+
<a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security" target="blank"><picture><source media="(prefers-color-scheme: dark)" srcset="https://eslint.interlace.tools/logos/interlace-dark.svg"><img src="https://eslint.interlace.tools/logos/interlace-light.svg" alt="Interlace" height="70" /></picture></a>
|
|
190
227
|
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vercel-ai-security",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "ESLint plugin for Vercel AI SDK security — detects prompt injection, system-prompt leaks, hardcoded API keys, and unvalidated model output in generateText and streamText.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@interlace/eslint-devkit": "^1.
|
|
86
|
+
"@interlace/eslint-devkit": "^1.18.0"
|
|
87
87
|
},
|
|
88
88
|
"funding": {
|
|
89
89
|
"type": "github",
|
package/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"2.
|
|
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.1.0"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/no-training-data-exposure":"warn","vercel-ai-security/require-request-timeout":"warn","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;const staticText1=(0,eslint_devkit_1.staticString)(node.value);if(staticText1!==null){const value=staticText1;if(value===""||value==="YOUR_API_KEY"||value.startsWith("$")){return}if(looksLikeApiKey(value)||value.length>20){context.report({node:node.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isProvider=providerFunctions.some(fn=>callee.includes(fn));if(!isProvider)return;const optionsArg=node.arguments[1];if(optionsArg&&optionsArg.type==="ObjectExpression"){for(const prop of optionsArg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="apiKey"||keyName==="api_key"){const staticText2=(0,eslint_devkit_1.staticString)(prop.value);if(staticText2!==null){const value=staticText2;if(looksLikeApiKey(value)||value.length>20){context.report({node:prop.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}}}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAuditLogging=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md",description:"Suggest audit logging for AI SDK operations",cwe:"CWE-778",cvss:4},messages:{missingAuditLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Audit Logging",cwe:"CWE-778",owasp:"A09:2021",cvss:4,description:"{{function}} call lacks audit logging. AI operations should be logged for security monitoring.",severity:"LOW",compliance:["SOC2","PCI-DSS"],fix:'Add logging: logger.info("AI call", { userId }); before the call',documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow missing logging in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAuditLogging=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md",description:"Suggest audit logging for AI SDK operations",cwe:"CWE-778",cvss:4},messages:{missingAuditLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Audit Logging",cwe:"CWE-778",owasp:"A09:2021",cvss:4,description:"{{function}} call lacks audit logging. AI operations should be logged for security monitoring.",severity:"LOW",compliance:["SOC2","PCI-DSS"],fix:'Add logging: logger.info("AI call", { userId }); before the call',documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow missing logging in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const loggingPatterns=["log","logger","console","winston","pino","bunyan","debug","info","warn","error","trace"];function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const callee=sourceCode.getText(node.expression.callee);return loggingPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()))}function hasNearbyLogging(node){const parent=node.parent;if(!parent)return false;let statement=node;while(statement&&statement.type!=="ExpressionStatement"&&statement.type!=="VariableDeclaration"&&statement.type!=="ReturnStatement"){statement=statement.parent??null}if(!statement)return false;const block=statement.parent;if(!block||block.type!=="BlockStatement"&&block.type!=="Program"){return false}const statements=block.type==="BlockStatement"?block.body:block.body;const idx=statements.indexOf(statement);if(idx===-1)return false;for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;if(!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireErrorHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md",description:"Require error handling for AI SDK calls to prevent cascading failures",cwe:"CWE-755",cvss:5},messages:{missingErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled AI SDK Call",cwe:"CWE-755",owasp:"A05:2021",cvss:5,description:"{{function}} call is not wrapped in try-catch. AI API failures can cause cascading errors.",severity:"MEDIUM",compliance:["SOC2"],fix:"Wrap in try-catch: try { await {{function}}(...) } catch (e) { /* handle error */ }",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/error-handling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow unhandled AI calls in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireErrorHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md",description:"Require error handling for AI SDK calls to prevent cascading failures",cwe:"CWE-755",cvss:5},messages:{missingErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled AI SDK Call",cwe:"CWE-755",owasp:"A05:2021",cvss:5,description:"{{function}} call is not wrapped in try-catch. AI API failures can cause cascading errors.",severity:"MEDIUM",compliance:["SOC2"],fix:"Wrap in try-catch: try { await {{function}}(...) } catch (e) { /* handle error */ }",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/error-handling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow unhandled AI calls in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isInsideTryBlock(node){let parent=node.parent;while(parent){if(parent.type==="TryStatement"){return true}parent=parent.parent}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const parent=node.parent;if(parent?.type==="AwaitExpression"){if(!isInsideTryBlock(parent)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}else if(!isInsideTryBlock(node)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRequestTimeout=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md",description:"Require timeout configuration for AI SDK calls to prevent DoS",cwe:"CWE-400",cvss:5},messages:{missingTimeout:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Request Timeout",cwe:"CWE-400",owasp:"A05:2021",cvss:5,description:"{{function}} call lacks timeout configuration. This can lead to denial of service.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add timeout configuration or use AbortController with setTimeout",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Skip in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRequestTimeout=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md",description:"Require timeout configuration for AI SDK calls to prevent DoS",cwe:"CWE-400",cvss:5},messages:{missingTimeout:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Request Timeout",cwe:"CWE-400",owasp:"A05:2021",cvss:5,description:"{{function}} call lacks timeout configuration. This can lead to denial of service.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add timeout configuration or use AbortController with setTimeout",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Skip in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const timeoutProperties=new Set(["timeout","abortSignal","signal","timeoutMs","requestTimeout"]);return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}});return}const hasTimeout=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&timeoutProperties.has(keyName)});if(!hasTimeout){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolSchema=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md",description:"Require inputSchema (Zod schema) for all AI SDK tools",cwe:"CWE-20",cvss:7.5},messages:{missingInputSchema:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Tool Missing Input Schema",cwe:"CWE-20",owasp:"A03:2021",cvss:7.5,description:'Tool "{{toolName}}" is missing inputSchema. Unvalidated tool parameters can lead to injection attacks.',severity:"HIGH",compliance:["SOC2"],fix:"Add inputSchema using Zod: tool({ inputSchema: z.object({ ... }), execute: ... })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"}),emptyToolsObject:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Empty Tools Object",cwe:"CWE-20",owasp:"A03:2021",description:"Tools object is empty or has no tool definitions",severity:"LOW",fix:"Define tools with proper schemas or remove tools property",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow tools without inputSchema in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolSchema=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md",description:"Require inputSchema (Zod schema) for all AI SDK tools",cwe:"CWE-20",cvss:7.5},messages:{missingInputSchema:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Tool Missing Input Schema",cwe:"CWE-20",owasp:"A03:2021",cvss:7.5,description:'Tool "{{toolName}}" is missing inputSchema. Unvalidated tool parameters can lead to injection attacks.',severity:"HIGH",compliance:["SOC2"],fix:"Add inputSchema using Zod: tool({ inputSchema: z.object({ ... }), execute: ... })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"}),emptyToolsObject:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Empty Tools Object",cwe:"CWE-20",owasp:"A03:2021",description:"Tools object is empty or has no tool definitions",severity:"LOW",fix:"Define tools with proper schemas or remove tools property",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow tools without inputSchema in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const functionsWithTools=["generateText","streamText","generateObject","streamObject"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=functionsWithTools.some(fn=>callee.includes(fn));const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg&&toolArg.type==="ObjectExpression"){const hasInputSchema=toolArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:"unnamed tool"}})}}return}if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const toolsProp=optionsArg.properties.find(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!toolsProp)return;if(toolsProp.value.type==="ObjectExpression"){for(const toolDef of toolsProp.value.properties){if(toolDef.type!=="Property")continue;const toolName=toolDef.key.type==="Identifier"?toolDef.key.name:toolDef.key.type==="Literal"?String(toolDef.key.value):"unknown";if(toolDef.value.type==="ObjectExpression"){const hasInputSchema=toolDef.value.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolDef.value,messageId:"missingInputSchema",data:{toolName}})}}}}}}}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md",description:"Require validated/sanitized prompts in generateText and streamText calls",cwe:"CWE-74",cvss:9},messages:{unsafePrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Prompt in Vercel AI SDK",cwe:"CWE-74",owasp:"A03:2021",cvss:9,description:'User input "{{input}}" passed directly to {{function}} prompt without validation',severity:"CRITICAL",compliance:["SOC2","GDPR"],fix:"Validate input before use: generateText({ prompt: validateInput(userInput) })",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8.5,description:"Dynamic value in system prompt can lead to prompt injection",severity:"HIGH",fix:"Use static system prompts or validate dynamic content",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{validatorFunctions:{type:"array",items:{type:"string"},description:"Function names considered as input validators"},userInputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest user input (regex)"},allowInTests:{type:"boolean",description:"Allow unsafe patterns in test files"}},additionalProperties:false}]},defaultOptions:[{validatorFunctions:["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"],userInputPatterns:["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"],allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const validatorFunctions=options.validatorFunctions??["validateInput","sanitizeInput","validatePrompt","sanitizePrompt"];const userInputPatterns=options.userInputPatterns??["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"];const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md",description:"Require validated/sanitized prompts in generateText and streamText calls",cwe:"CWE-74",cvss:9},messages:{unsafePrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Prompt in Vercel AI SDK",cwe:"CWE-74",owasp:"A03:2021",cvss:9,description:'User input "{{input}}" passed directly to {{function}} prompt without validation',severity:"CRITICAL",compliance:["SOC2","GDPR"],fix:"Validate input before use: generateText({ prompt: validateInput(userInput) })",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8.5,description:"Dynamic value in system prompt can lead to prompt injection",severity:"HIGH",fix:"Use static system prompts or validate dynamic content",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{validatorFunctions:{type:"array",items:{type:"string"},description:"Function names considered as input validators"},userInputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest user input (regex)"},allowInTests:{type:"boolean",description:"Allow unsafe patterns in test files"}},additionalProperties:false}]},defaultOptions:[{validatorFunctions:["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"],userInputPatterns:["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"],allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const validatorFunctions=options.validatorFunctions??["validateInput","sanitizeInput","validatePrompt","sanitizePrompt"];const userInputPatterns=options.userInputPatterns??["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"];const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(0,eslint_devkit_1.isTestFilePath)(filename)){return{}}const vercelAIFunctions=["generateText","streamText","generateObject","streamObject"];function isValidatedCall(node){if(node.type!=="CallExpression")return false;const callee=sourceCode.getText(node.callee);return validatorFunctions.some(v=>callee.includes(v))}function isUserInput(node){const text=sourceCode.getText(node);return userInputPatterns.some(pattern=>text.includes(pattern))}function hasUnsafeUserInput(node){if(isValidatedCall(node)){return{unsafe:false}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const result=hasUnsafeUserInput(expr);if(result.unsafe)return result}}if(node.type==="Identifier"){if(isUserInput(node)){return{unsafe:true,input:node.name}}}if(node.type==="MemberExpression"){const text=sourceCode.getText(node);if(isUserInput(node)){return{unsafe:true,input:text}}}if(node.type==="BinaryExpression"){const leftResult=hasUnsafeUserInput(node.left);if(leftResult.unsafe)return leftResult;const rightResult=hasUnsafeUserInput(node.right);if(rightResult.unsafe)return rightResult}return{unsafe:false}}function checkAICall(node){const callee=sourceCode.getText(node.callee);const matchedFunction=vercelAIFunctions.find(fn=>callee.includes(fn));if(!matchedFunction){return}const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){return}for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(keyName==="prompt"){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafePrompt",data:{input:result.input||"user input",function:matchedFunction}})}}if((0,prompt_props_1.isSystemPromptProp)(keyName)){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafeSystemPrompt"})}}}}return{CallExpression:checkAICall}}});
|