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 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
  &nbsp;&nbsp;
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
  &nbsp;&nbsp;
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
  &nbsp;&nbsp;
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
- ## Philosophy
30
+ <!-- AUTO-GENERATED:DOCTRINE:START - Do not edit manually -->
30
31
 
31
- **Interlace** fosters **strength through integration**. Instead of stacking isolated rules, we **interlace** security directly into your workflow to create a resilient fabric of code. We believe tools should **guide rather than gatekeep**, providing educational feedback that strengthens the developer with every interaction.
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 Ecosystem** — AI-native security plugins with LLM-optimized error messages:
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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-anthropic-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-browser-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-drizzle-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-express-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-gemini-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-jwt-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-knex-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-lambda-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mcp-sdk-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mongodb-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mysql-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-nestjs-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-node-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-openai-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-postgresql-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-prisma-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-secure-coding.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-sequelize-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-sqlite-security.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-typeorm-security.svg?style=flat-square)](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-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-secure-coding.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-secure-coding) | General security rules & OWASP guidelines. |
160
- | [`eslint-plugin-pg`](https://www.npmjs.com/package/eslint-plugin-pg) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-pg.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-pg) | PostgreSQL security & best practices. |
161
- | [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-node-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-node-security) | Node.js core-module security (fs, child_process, vm, crypto, Buffer). |
162
- | [`eslint-plugin-jwt`](https://www.npmjs.com/package/eslint-plugin-jwt) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-jwt.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-jwt) | JWT security & best practices. |
163
- | [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-browser-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-browser-security) | Browser-specific security & XSS prevention. |
164
- | [`eslint-plugin-express-security`](https://www.npmjs.com/package/eslint-plugin-express-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-express-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-express-security) | Express.js security hardening rules. |
165
- | [`eslint-plugin-lambda-security`](https://www.npmjs.com/package/eslint-plugin-lambda-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-lambda-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-lambda-security) | AWS Lambda security best practices. |
166
- | [`eslint-plugin-nestjs-security`](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-nestjs-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | NestJS security rules & patterns. |
167
- | [`eslint-plugin-mongodb-security`](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mongodb-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | MongoDB security best practices. |
168
- | [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-vercel-ai-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | Vercel AI SDK security hardening. |
169
- | [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-import-next) | Next-gen import sorting & architecture. |
196
+ | [`eslint-plugin-conventions`](https://www.npmjs.com/package/eslint-plugin-conventions) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-conventions.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-maintainability.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-modernization.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-modernization) | ESNext migration + syntax evolution. |
200
+ | [`eslint-plugin-modularity`](https://www.npmjs.com/package/eslint-plugin-modularity) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-modularity.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-operability.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-react-a11y.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-react-features.svg?style=flat-square)](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) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-reliability.svg?style=flat-square)](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/icon-light.svg" alt="Interlace" height="70" /></a>
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.0.0",
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.12.0"
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.0.0"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/no-training-data-exposure":"warn","vercel-ai-security/require-request-timeout":"warn","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
1
+ "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;if(node.value.type==="Literal"&&typeof node.value.value==="string"){const value=node.value.value;if(value===""||value==="YOUR_API_KEY"||value.startsWith("$")){return}if(looksLikeApiKey(value)||value.length>20){context.report({node:node.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isProvider=providerFunctions.some(fn=>callee.includes(fn));if(!isProvider)return;const optionsArg=node.arguments[1];if(optionsArg&&optionsArg.type==="ObjectExpression"){for(const prop of optionsArg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="apiKey"||keyName==="api_key"){if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){const value=prop.value.value;if(looksLikeApiKey(value)||value.length>20){context.report({node:prop.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;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&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const loggingPatterns=["log","logger","console","winston","pino","bunyan","debug","info","warn","error","trace"];function isLoggingStatement(node){if(node.type!=="ExpressionStatement")return false;if(node.expression.type!=="CallExpression")return false;const callee=sourceCode.getText(node.expression.callee);return loggingPatterns.some(pattern=>callee.toLowerCase().includes(pattern.toLowerCase()))}function hasNearbyLogging(node){const parent=node.parent;if(!parent)return false;let statement=node;while(statement&&statement.type!=="ExpressionStatement"&&statement.type!=="VariableDeclaration"&&statement.type!=="ReturnStatement"){statement=statement.parent??null}if(!statement)return false;const block=statement.parent;if(!block||block.type!=="BlockStatement"&&block.type!=="Program"){return false}const statements=block.type==="BlockStatement"?block.body:block.body;const idx=statements.indexOf(statement);if(idx===-1)return false;for(let i=Math.max(0,idx-3);i<idx;i++){if(isLoggingStatement(statements[i]))return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;if(!hasNearbyLogging(node)){context.report({node,messageId:"missingAuditLogging",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireAuditLogging=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireAuditLogging=(0,eslint_devkit_1.createRule)({name:"require-audit-logging",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md",description:"Suggest audit logging for AI SDK operations",cwe:"CWE-778",cvss:4},messages:{missingAuditLogging:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Audit Logging",cwe:"CWE-778",owasp:"A09:2021",cvss:4,description:"{{function}} call lacks audit logging. AI operations should be logged for security monitoring.",severity:"LOW",compliance:["SOC2","PCI-DSS"],fix:'Add logging: logger.info("AI call", { userId }); before the call',documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow missing logging in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(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&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isInsideTryBlock(node){let parent=node.parent;while(parent){if(parent.type==="TryStatement"){return true}parent=parent.parent}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const parent=node.parent;if(parent?.type==="AwaitExpression"){if(!isInsideTryBlock(parent)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}else if(!isInsideTryBlock(node)){context.report({node,messageId:"missingErrorHandling",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireErrorHandling=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireErrorHandling=(0,eslint_devkit_1.createRule)({name:"require-error-handling",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md",description:"Require error handling for AI SDK calls to prevent cascading failures",cwe:"CWE-755",cvss:5},messages:{missingErrorHandling:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Unhandled AI SDK Call",cwe:"CWE-755",owasp:"A05:2021",cvss:5,description:"{{function}} call is not wrapped in try-catch. AI API failures can cause cascading errors.",severity:"MEDIUM",compliance:["SOC2"],fix:"Wrap in try-catch: try { await {{function}}(...) } catch (e) { /* handle error */ }",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/error-handling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow unhandled AI calls in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(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&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];const timeoutProperties=new Set(["timeout","abortSignal","signal","timeoutMs","requestTimeout"]);return{CallExpression(node){const callee=sourceCode.getText(node.callee);const matchedFunction=aiSDKFunctions.find(fn=>callee.includes(fn));if(!matchedFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}});return}const hasTimeout=optionsArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName&&timeoutProperties.has(keyName)});if(!hasTimeout){context.report({node,messageId:"missingTimeout",data:{function:matchedFunction}})}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireRequestTimeout=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireRequestTimeout=(0,eslint_devkit_1.createRule)({name:"require-request-timeout",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md",description:"Require timeout configuration for AI SDK calls to prevent DoS",cwe:"CWE-400",cvss:5},messages:{missingTimeout:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Missing Request Timeout",cwe:"CWE-400",owasp:"A05:2021",cvss:5,description:"{{function}} call lacks timeout configuration. This can lead to denial of service.",severity:"MEDIUM",compliance:["SOC2"],fix:"Add timeout configuration or use AbortController with setTimeout",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Skip in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??true;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(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&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const functionsWithTools=["generateText","streamText","generateObject","streamObject"];return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=functionsWithTools.some(fn=>callee.includes(fn));const isToolHelper=callee==="tool"||callee.endsWith(".tool");if(isToolHelper){const toolArg=node.arguments[0];if(toolArg&&toolArg.type==="ObjectExpression"){const hasInputSchema=toolArg.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolArg,messageId:"missingInputSchema",data:{toolName:"unnamed tool"}})}}return}if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const toolsProp=optionsArg.properties.find(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="tools"});if(!toolsProp)return;if(toolsProp.value.type==="ObjectExpression"){for(const toolDef of toolsProp.value.properties){if(toolDef.type!=="Property")continue;const toolName=toolDef.key.type==="Identifier"?toolDef.key.name:toolDef.key.type==="Literal"?String(toolDef.key.value):"unknown";if(toolDef.value.type==="ObjectExpression"){const hasInputSchema=toolDef.value.properties.some(prop=>{if(prop.type!=="Property")return false;const keyName=prop.key.type==="Identifier"?prop.key.name:null;return keyName==="inputSchema"||keyName==="parameters"});if(!hasInputSchema){context.report({node:toolDef.value,messageId:"missingInputSchema",data:{toolName}})}}}}}}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireToolSchema=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireToolSchema=(0,eslint_devkit_1.createRule)({name:"require-tool-schema",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md",description:"Require inputSchema (Zod schema) for all AI SDK tools",cwe:"CWE-20",cvss:7.5},messages:{missingInputSchema:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Tool Missing Input Schema",cwe:"CWE-20",owasp:"A03:2021",cvss:7.5,description:'Tool "{{toolName}}" is missing inputSchema. Unvalidated tool parameters can lead to injection attacks.',severity:"HIGH",compliance:["SOC2"],fix:"Add inputSchema using Zod: tool({ inputSchema: z.object({ ... }), execute: ... })",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"}),emptyToolsObject:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.WARNING,issueName:"Empty Tools Object",cwe:"CWE-20",owasp:"A03:2021",description:"Tools object is empty or has no tool definitions",severity:"LOW",fix:"Define tools with proper schemas or remove tools property",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",description:"Allow tools without inputSchema in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(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&&/\.(test|spec)\.[jt]sx?$/.test(filename)){return{}}const vercelAIFunctions=["generateText","streamText","generateObject","streamObject"];function isValidatedCall(node){if(node.type!=="CallExpression")return false;const callee=sourceCode.getText(node.callee);return validatorFunctions.some(v=>callee.includes(v))}function isUserInput(node){const text=sourceCode.getText(node);return userInputPatterns.some(pattern=>text.includes(pattern))}function hasUnsafeUserInput(node){if(isValidatedCall(node)){return{unsafe:false}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const result=hasUnsafeUserInput(expr);if(result.unsafe)return result}}if(node.type==="Identifier"){if(isUserInput(node)){return{unsafe:true,input:node.name}}}if(node.type==="MemberExpression"){const text=sourceCode.getText(node);if(isUserInput(node)){return{unsafe:true,input:text}}}if(node.type==="BinaryExpression"){const leftResult=hasUnsafeUserInput(node.left);if(leftResult.unsafe)return leftResult;const rightResult=hasUnsafeUserInput(node.right);if(rightResult.unsafe)return rightResult}return{unsafe:false}}function checkAICall(node){const callee=sourceCode.getText(node.callee);const matchedFunction=vercelAIFunctions.find(fn=>callee.includes(fn));if(!matchedFunction){return}const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression"){return}for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(keyName==="prompt"){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafePrompt",data:{input:result.input||"user input",function:matchedFunction}})}}if((0,prompt_props_1.isSystemPromptProp)(keyName)){const result=hasUnsafeUserInput(prop.value);if(result.unsafe){context.report({node:prop.value,messageId:"unsafeSystemPrompt"})}}}}return{CallExpression:checkAICall}}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.requireValidatedPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");const vercel_ai_evidence_1=require("../../utils/vercel-ai-evidence");exports.requireValidatedPrompt=(0,eslint_devkit_1.createRule)({name:"require-validated-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md",description:"Require validated/sanitized prompts in generateText and streamText calls",cwe:"CWE-74",cvss:9},messages:{unsafePrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Prompt in Vercel AI SDK",cwe:"CWE-74",owasp:"A03:2021",cvss:9,description:'User input "{{input}}" passed directly to {{function}} prompt without validation',severity:"CRITICAL",compliance:["SOC2","GDPR"],fix:"Validate input before use: generateText({ prompt: validateInput(userInput) })",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"}),unsafeSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8.5,description:"Dynamic value in system prompt can lead to prompt injection",severity:"HIGH",fix:"Use static system prompts or validate dynamic content",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{validatorFunctions:{type:"array",items:{type:"string"},description:"Function names considered as input validators"},userInputPatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest user input (regex)"},allowInTests:{type:"boolean",description:"Allow unsafe patterns in test files"}},additionalProperties:false}]},defaultOptions:[{validatorFunctions:["validateInput","sanitizeInput","validatePrompt","sanitizePrompt","escapeInput","cleanInput"],userInputPatterns:["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"],allowInTests:false}],create(context){if(!(0,vercel_ai_evidence_1.fileUsesVercelAi)(context.sourceCode.ast))return{};const[options={}]=context.options;const validatorFunctions=options.validatorFunctions??["validateInput","sanitizeInput","validatePrompt","sanitizePrompt"];const userInputPatterns=options.userInputPatterns??["userInput","userPrompt","userMessage","userQuery","userContent","input","query","message","req.body","request.body"];const allowInTests=options.allowInTests??false;const sourceCode=context.sourceCode;const filename=context.filename;if(allowInTests&&(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}}});