eslint-plugin-vercel-ai-security 1.3.5 → 1.3.6
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/CHANGELOG.md +42 -0
- package/README.md +78 -70
- package/package.json +23 -7
- package/src/index.js +19 -6
- package/src/oxlint.d.ts +20 -0
- package/src/oxlint.js +21 -0
- package/src/rules/no-dynamic-system-prompt/index.js +4 -1
- package/src/rules/no-hardcoded-api-keys/index.js +5 -1
- package/src/rules/no-sensitive-in-prompt/index.js +6 -3
- package/src/rules/no-system-prompt-leak/index.js +4 -1
- package/src/rules/no-training-data-exposure/index.js +3 -0
- package/src/rules/no-unsafe-output-handling/index.js +56 -1
- package/src/rules/require-abort-signal/index.js +5 -2
- package/src/rules/require-audit-logging/index.js +5 -2
- package/src/rules/require-embedding-validation/index.js +4 -1
- package/src/rules/require-error-handling/index.js +6 -2
- package/src/rules/require-max-steps/index.js +4 -1
- package/src/rules/require-max-tokens/index.js +4 -1
- package/src/rules/require-output-filtering/index.js +5 -1
- package/src/rules/require-output-validation/index.js +4 -1
- package/src/rules/require-rag-content-validation/index.js +4 -1
- package/src/rules/require-request-timeout/index.js +7 -4
- package/src/rules/require-tool-confirmation/index.js +7 -3
- package/src/rules/require-tool-schema/index.js +5 -2
- package/src/rules/require-validated-prompt/index.js +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
## 1.3.6
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- [#144](https://github.com/ofri-peretz/eslint/pull/144) [`8843ce7`](https://github.com/ofri-peretz/eslint/commit/8843ce7fbb465caad161b97032467b3a37a49319) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix: ILB-Wild FP reduction + doc examples + doc-test-alignment scanner fixes
|
|
6
|
+
|
|
7
|
+
**`no-unlimited-resource-allocation` — FP reduction (430 Edge FPs)**
|
|
8
|
+
- Skip loop-allocation reporting when the first argument is a numeric literal (e.g. `Buffer.alloc(1024)` inside a loop is statically bounded, not a risk)
|
|
9
|
+
- Skip `Array.isArray`, `Array.from`, `Array.of` calls in the `alloc/Array` pattern check (these don't allocate unbounded memory)
|
|
10
|
+
|
|
11
|
+
**`no-hardcoded-credentials` — FP reduction (~280 Edge FPs)**
|
|
12
|
+
- Extended test-file skip to cover `.fixture.`, `.mock.`, `__mocks__/`, `/tests/`, `/fixtures/`, `/mocks/` paths
|
|
13
|
+
- Skip string literals that are fallback values in `process.env.X || 'fallback'` expressions — the secret lives in the environment, the string is only a dev-mode default
|
|
14
|
+
|
|
15
|
+
**Doc examples — 4 rules now have ❌ Incorrect examples**
|
|
16
|
+
- `lambda-security/no-missing-authorization-check`
|
|
17
|
+
- `lambda-security/no-overly-permissive-iam-policy`
|
|
18
|
+
- `node-security/prefer-native-crypto` (renamed non-standard `### ❌ Third-Party (Flagged)` to `### ❌ Incorrect`)
|
|
19
|
+
- `vercel-ai-security/require-tool-confirmation` (replaced placeholder with a real tested example)
|
|
20
|
+
|
|
21
|
+
**`ilb-doc-test-alignment` scanner fixes**
|
|
22
|
+
- Accept both `## ❌` and `### ❌` headings (docs use H3 under an H2 `## Examples` section; was only finding H2)
|
|
23
|
+
- Slice from end-of-line rather than end-of-regex-match (prevents `## ❌ Incorrect Code` from leaving a partial heading in the parsed section)
|
|
24
|
+
|
|
25
|
+
Result: `ilb:doc-test-alignment` → 206 ok, 0 doc has no ❌ examples (was 165 missing).
|
|
26
|
+
|
|
27
|
+
- [#143](https://github.com/ofri-peretz/eslint/pull/143) [`213cde1`](https://github.com/ofri-peretz/eslint/commit/213cde190ff2aea49ca7c1b533170940f879d9b4) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - fix(no-missing-null-checks): eliminate 53 false positives via three new narrowing patterns
|
|
28
|
+
|
|
29
|
+
Rules that were recognized as null guards are now correctly identified as safe:
|
|
30
|
+
1. **Truthy if guard** — `if (obj) { obj.prop }` — direct truthy check proves non-null. Also covers chains: `if (response)` protects `response.data.items`.
|
|
31
|
+
2. **Short-circuit AND** — `obj && obj.prop` — right side of `&&` only runs when left is truthy.
|
|
32
|
+
3. **Ternary consequent** — `obj ? obj.prop : fallback` — truthy test guards the consequent.
|
|
33
|
+
|
|
34
|
+
Also: bumped `beforeAll` timeout to 30 seconds in 7 compatibility test files (`__compatibility__/*.spec.ts`). Native-addon packages routinely exceed the previous 10-second default on a cold ESM load.
|
|
35
|
+
|
|
36
|
+
- Updated dependencies [[`736a5fe`](https://github.com/ofri-peretz/eslint/commit/736a5fed47e673f6157ea900b29fe2a54e4bc7df)]:
|
|
37
|
+
- @interlace/eslint-devkit@1.4.1
|
|
38
|
+
|
|
39
|
+
## 1.3.5 (2026-02-09)
|
|
40
|
+
|
|
41
|
+
This was a version bump only for eslint-plugin-vercel-ai-security to align it with other projects, there were no code changes.
|
|
42
|
+
|
|
1
43
|
## [1.3.3] - 2026-02-08
|
|
2
44
|
|
|
3
45
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://eslint.interlace.tools" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
|
|
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/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
<a href="https://www.npmjs.com/package/eslint-plugin-vercel-ai-security" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-vercel-ai-security.svg" alt="NPM Version" /></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/eslint-plugin-vercel-ai-security" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-vercel-ai-security.svg" alt="NPM Downloads" /></a>
|
|
12
12
|
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
|
|
13
|
-
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=
|
|
13
|
+
<a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=vercel-ai-security" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=vercel-ai-security" alt="Codecov" /></a>
|
|
14
14
|
<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>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
|
+
> ⭐ 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.
|
|
18
|
+
|
|
17
19
|
## Description
|
|
18
20
|
|
|
19
21
|
This plugin provides Security rules for Vercel AI SDK usage (prompt injection, data handling).
|
|
20
|
-
By using this plugin, you can proactively identify and mitigate security risks across your entire codebase.
|
|
21
22
|
|
|
22
23
|
## Philosophy
|
|
23
24
|
|
|
@@ -25,29 +26,17 @@ By using this plugin, you can proactively identify and mitigate security risks a
|
|
|
25
26
|
|
|
26
27
|
## Getting Started
|
|
27
28
|
|
|
28
|
-
- To check out the [guide](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
29
|
-
- 要查看中文 [指南](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security), 请访问 [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
30
|
-
- [가이드](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security) 문서는 [eslint.interlace.tools](https://eslint.interlace.tools)에서 확인하실 수 있습니다. 📚
|
|
31
|
-
- [ガイド](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security)は [eslint.interlace.tools](https://eslint.interlace.tools)でご確認ください。 📚
|
|
32
|
-
- Para ver la [guía](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security), visita [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
33
|
-
- للاطلاع على [الدليل](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security)، قم بزيارة [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
29
|
+
- 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). 📚
|
|
30
|
+
- 要查看中文 [指南](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). 📚
|
|
31
|
+
- [가이드](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)에서 확인하실 수 있습니다. 📚
|
|
32
|
+
- [ガイド](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)でご確認ください。 📚
|
|
33
|
+
- 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). 📚
|
|
34
|
+
- للاطلاع على [الدليل](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). 📚
|
|
34
35
|
|
|
35
36
|
```bash
|
|
36
37
|
npm install eslint-plugin-vercel-ai-security --save-dev
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
## 🔧 Supported AI SDK Functions
|
|
40
|
-
| Function | Full Coverage |
|
|
41
|
-
| ---------------------- | ------------------------------ |
|
|
42
|
-
| `generateText` | ✅ All 19 rules |
|
|
43
|
-
| `streamText` | ✅ All 19 rules + abort signal |
|
|
44
|
-
| `generateObject` | ✅ All 19 rules |
|
|
45
|
-
| `streamObject` | ✅ All 19 rules + abort signal |
|
|
46
|
-
| `tool()` helper | ✅ Schema validation |
|
|
47
|
-
| `embed()` / embeddings | ✅ Embedding validation |
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
40
|
## ⚙️ Configuration Presets
|
|
52
41
|
| Preset | Description |
|
|
53
42
|
| :------------ | :--------------------------------------- |
|
|
@@ -55,17 +44,10 @@ npm install eslint-plugin-vercel-ai-security --save-dev
|
|
|
55
44
|
| `strict` | Maximum security (17 errors, 2 warnings) |
|
|
56
45
|
| `minimal` | Minimal config |
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
| ------------- | -------- |
|
|
63
|
-
| **Rules** | 19 |
|
|
64
|
-
| **Tests** | 200 |
|
|
65
|
-
| **Lines** | 98%+ |
|
|
66
|
-
| **Functions** | 100% |
|
|
67
|
-
|
|
68
|
-
---
|
|
47
|
+
## 📚 Supported Libraries
|
|
48
|
+
| Library | npm | Downloads | Detection |
|
|
49
|
+
| -------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
|
50
|
+
| `ai` (Vercel AI SDK) | [](https://www.npmjs.com/package/ai) | [](https://www.npmjs.com/package/ai) | Prompt Injection, Data Leakage |
|
|
69
51
|
|
|
70
52
|
## 🤖 AI-Agent Optimized Messages
|
|
71
53
|
All rule messages follow a structured format optimized for AI coding assistants:
|
|
@@ -77,14 +59,23 @@ All rule messages follow a structured format optimized for AI coding assistants:
|
|
|
77
59
|
|
|
78
60
|
By providing this structured context (CWE, OWASP, Fix), we enable AI tools to **reason** about the security flaw rather than hallucinating. This allows Copilot/Cursor to suggest the _exact_ correct fix immediately.
|
|
79
61
|
|
|
80
|
-
##
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
| `
|
|
84
|
-
|
|
|
85
|
-
|
|
|
62
|
+
## 🔧 Supported AI SDK Functions
|
|
63
|
+
| Function | Full Coverage |
|
|
64
|
+
| ---------------------- | ------------------------------ |
|
|
65
|
+
| `generateText` | ✅ All 19 rules |
|
|
66
|
+
| `streamText` | ✅ All 19 rules + abort signal |
|
|
67
|
+
| `generateObject` | ✅ All 19 rules |
|
|
68
|
+
| `streamObject` | ✅ All 19 rules + abort signal |
|
|
69
|
+
| `tool()` helper | ✅ Schema validation |
|
|
70
|
+
| `embed()` / embeddings | ✅ Embedding validation |
|
|
86
71
|
|
|
87
|
-
|
|
72
|
+
## 📊 Test Coverage
|
|
73
|
+
| Metric | Coverage |
|
|
74
|
+
| ------------- | -------- |
|
|
75
|
+
| **Rules** | 19 |
|
|
76
|
+
| **Tests** | 200 |
|
|
77
|
+
| **Lines** | 98%+ |
|
|
78
|
+
| **Functions** | 100% |
|
|
88
79
|
|
|
89
80
|
## 🙋 FAQ
|
|
90
81
|
### What's the difference between this and generic AI security linters?
|
|
@@ -93,7 +84,7 @@ Generic linters guess at patterns. This plugin knows the **exact** Vercel AI SDK
|
|
|
93
84
|
|
|
94
85
|
### Does this work with ESLint 9 Flat Config?
|
|
95
86
|
|
|
96
|
-
Yes! Designed specifically for ESLint Flat Config.
|
|
87
|
+
Yes! Designed specifically for ESLint Flat Config — works on ESLint 8 (with flat config), 9, and 10. See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) for the full matrix.
|
|
97
88
|
|
|
98
89
|
### How do I suppress a rule for a specific line?
|
|
99
90
|
|
|
@@ -106,10 +97,14 @@ await generateText({ prompt: internalPrompt });
|
|
|
106
97
|
|
|
107
98
|
TypeScript/JavaScript are memory-safe languages. Memory corruption vulnerabilities (buffer overflows, use-after-free, etc.) are not possible in these environments.
|
|
108
99
|
|
|
109
|
-
##
|
|
110
|
-
|
|
|
111
|
-
| -------------------- |
|
|
112
|
-
| `ai` (Vercel AI SDK) | [](https://www.npmjs.com/package/ai)
|
|
100
|
+
## 📦 Compatibility
|
|
101
|
+
| Package | Version |
|
|
102
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `ai` (Vercel AI SDK) | [](https://www.npmjs.com/package/ai) |
|
|
104
|
+
| ESLint | [](https://www.npmjs.com/package/eslint) |
|
|
105
|
+
| Node.js | [](https://nodejs.org/) |
|
|
106
|
+
|
|
107
|
+
See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) for the full matrix.
|
|
113
108
|
|
|
114
109
|
## Rules
|
|
115
110
|
|
|
@@ -118,33 +113,37 @@ TypeScript/JavaScript are memory-safe languages. Memory corruption vulnerabiliti
|
|
|
118
113
|
| Icon | Description |
|
|
119
114
|
| :---: | :--- |
|
|
120
115
|
| 💼 | **Recommended**: Included in the recommended preset. |
|
|
121
|
-
| ⚠️ | **Warns**: Set
|
|
116
|
+
| ⚠️ | **Warns**: Set to warn in recommended preset. |
|
|
122
117
|
| 🔧 | **Auto-fixable**: Automatically fixable by the `--fix` CLI option. |
|
|
123
118
|
| 💡 | **Suggestions**: Providing code suggestions in IDE. |
|
|
124
119
|
| 🚫 | **Deprecated**: This rule is deprecated. |
|
|
125
|
-
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
| [no-
|
|
133
|
-
| [no-
|
|
134
|
-
| [
|
|
135
|
-
| [
|
|
136
|
-
| [
|
|
137
|
-
| [
|
|
138
|
-
| [require-
|
|
139
|
-
| [require-
|
|
140
|
-
| [require-
|
|
141
|
-
| [require-
|
|
142
|
-
| [require-
|
|
143
|
-
| [require-
|
|
144
|
-
| [require-
|
|
145
|
-
| [require-
|
|
146
|
-
| [require-
|
|
147
|
-
|
|
120
|
+
| 🟢 | **Type-unaware**: AST-only, runs in oxlint JS-plugin tier. |
|
|
121
|
+
| 🟡 | **Type-aware (refining)**: pure-AST primary path; types refine precision. |
|
|
122
|
+
| 🟠 | **Type-aware (graceful)**: requires TS program; silent without it. |
|
|
123
|
+
|
|
124
|
+
<!-- AUTO-GENERATED:RULES_TABLE:START - Do not edit manually -->
|
|
125
|
+
| Rule | CWE | OWASP | CVSS | Description | 🧠 | 💼 | ⚠️ | 🔧 | 💡 | 🚫 |
|
|
126
|
+
| :--- | :---: | :---: | :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
|
127
|
+
| [no-dynamic-system-prompt](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-dynamic-system-prompt?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-74 | | | This rule identifies code patterns where system prompts contain dynamic or user-controlled content | 🟢 | | | | | |
|
|
128
|
+
| [no-hardcoded-api-keys](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-hardcoded-api-keys?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-798 | | | This rule identifies hardcoded API keys, tokens, and secrets in your codebase that are used with AI SDK pro… | 🟢 | | | | | |
|
|
129
|
+
| [no-sensitive-in-prompt](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-sensitive-in-prompt?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-200 | | | This rule identifies code patterns where sensitive data like passwords, API keys, tokens, or personally ide… | 🟢 | | | | | |
|
|
130
|
+
| [no-system-prompt-leak](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-system-prompt-leak?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-200 | | | This rule identifies code patterns where system prompts or AI instructions are returned in API responses, l… | 🟢 | | | | | |
|
|
131
|
+
| [no-training-data-exposure](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-training-data-exposure?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-359 | | | This rule identifies code patterns where user data might be sent to LLM training endpoints or when training… | 🟢 | | | | | |
|
|
132
|
+
| [no-unsafe-output-handling](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/no-unsafe-output-handling?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-94 | | | This rule identifies code patterns where AI-generated output is passed directly to dangerous functions that… | 🟢 | | | | | |
|
|
133
|
+
| [require-abort-signal](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-abort-signal?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-404 | | | This rule identifies streaming AI SDK calls (streamText, streamObject) that don't include an AbortSignal fo… | 🟢 | | | | | |
|
|
134
|
+
| [require-audit-logging](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-audit-logging?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-778 | | | This rule identifies AI SDK calls that aren't preceded by logging statements | 🟢 | | | | | |
|
|
135
|
+
| [require-embedding-validation](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-embedding-validation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-20 | | | This rule identifies code patterns where embeddings are stored in vector databases without validation. | 🟢 | | | | | |
|
|
136
|
+
| [require-error-handling](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-error-handling?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-755 | | | This rule identifies AI SDK calls that aren't wrapped in try-catch blocks | 🟢 | | | | | |
|
|
137
|
+
| [require-max-steps](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-max-steps?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-834 | | | This rule identifies AI SDK calls that use tools but don't specify a maxSteps limit | 🟢 | | | | | |
|
|
138
|
+
| [require-max-tokens](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-max-tokens?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-770 | | | This rule identifies AI SDK calls that don't specify a maxTokens limit | 🟢 | | | | | |
|
|
139
|
+
| [require-output-filtering](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-output-filtering?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-200 | | | This rule identifies tool execute functions that return raw data from data sources (databases, APIs, file s… | 🟢 | | | | | |
|
|
140
|
+
| [require-output-validation](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-output-validation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-707 | | | This rule identifies code patterns where AI-generated output is displayed to users without validation or fa… | 🟢 | | | | | |
|
|
141
|
+
| [require-rag-content-validation](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-rag-content-validation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-74 | | | This rule identifies code patterns where content retrieved from vector stores or document retrieval systems… | 🟢 | | | | | |
|
|
142
|
+
| [require-request-timeout](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-request-timeout?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-400 | | | This rule identifies AI SDK calls that don't have timeout or abort signal configuration. | 🟢 | | | | | |
|
|
143
|
+
| [require-tool-confirmation](https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security/rules/require-tool-confirmation?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security) | CWE-862 | | | This rule identifies destructive tools (delete, transfer, execute, etc.) that don't require human confirmat… | 🟢 | | | | | |
|
|
144
|
+
| [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 | 🟢 | | | | | |
|
|
145
|
+
| [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… | 🟢 | | | | | |
|
|
146
|
+
<!-- AUTO-GENERATED:RULES_TABLE:END -->
|
|
148
147
|
## 🔗 Related ESLint Plugins
|
|
149
148
|
|
|
150
149
|
Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with LLM-optimized error messages:
|
|
@@ -153,7 +152,7 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
|
|
|
153
152
|
| :--- | :---: | :--- |
|
|
154
153
|
| [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [](https://www.npmjs.com/package/eslint-plugin-secure-coding) | General security rules & OWASP guidelines. |
|
|
155
154
|
| [`eslint-plugin-pg`](https://www.npmjs.com/package/eslint-plugin-pg) | [](https://www.npmjs.com/package/eslint-plugin-pg) | PostgreSQL security & best practices. |
|
|
156
|
-
| [`eslint-plugin-
|
|
155
|
+
| [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [](https://www.npmjs.com/package/eslint-plugin-node-security) | Node.js core-module security (fs, child_process, vm, crypto, Buffer). |
|
|
157
156
|
| [`eslint-plugin-jwt`](https://www.npmjs.com/package/eslint-plugin-jwt) | [](https://www.npmjs.com/package/eslint-plugin-jwt) | JWT security & best practices. |
|
|
158
157
|
| [`eslint-plugin-browser-security`](https://www.npmjs.com/package/eslint-plugin-browser-security) | [](https://www.npmjs.com/package/eslint-plugin-browser-security) | Browser-specific security & XSS prevention. |
|
|
159
158
|
| [`eslint-plugin-express-security`](https://www.npmjs.com/package/eslint-plugin-express-security) | [](https://www.npmjs.com/package/eslint-plugin-express-security) | Express.js security hardening rules. |
|
|
@@ -163,10 +162,19 @@ Part of the **Interlace ESLint Ecosystem** — AI-native security plugins with L
|
|
|
163
162
|
| [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | Vercel AI SDK security hardening. |
|
|
164
163
|
| [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [](https://www.npmjs.com/package/eslint-plugin-import-next) | Next-gen import sorting & architecture. |
|
|
165
164
|
|
|
165
|
+
<!-- INTERLACE:STAR_CTA:START -->
|
|
166
|
+
## ⭐ Support & follow
|
|
167
|
+
|
|
168
|
+
If this plugin caught a real bug for you, **[star the repo](https://github.com/ofri-peretz/eslint)** — stars are the signal that keeps the Interlace ESLint ecosystem maintained — and **[follow the writeups on Dev.to](https://dev.to/ofri-peretz)** for the benchmarks and security research behind these rules.
|
|
169
|
+
|
|
170
|
+
[](https://github.com/ofri-peretz/eslint)
|
|
171
|
+
|
|
172
|
+
<!-- INTERLACE:STAR_CTA:END -->
|
|
173
|
+
|
|
166
174
|
## 📄 License
|
|
167
175
|
|
|
168
176
|
MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
|
169
177
|
|
|
170
178
|
<p align="center">
|
|
171
|
-
<a href="https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security"><img src="https://eslint.interlace.tools/images/og-vercel-ai-security.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
179
|
+
<a href="https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security"><img src="https://eslint.interlace.tools/images/og-vercel-ai-security.png" alt="ESLint Interlace Plugin" width="100%" /></a>
|
|
172
180
|
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vercel-ai-security",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Security-focused ESLint plugin for Vercel AI SDK. SDK-aware rules for generateText, streamText, tools, and streaming patterns with full type knowledge.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
"./types": {
|
|
14
14
|
"types": "./src/types/index.d.ts",
|
|
15
15
|
"default": "./src/types/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./oxlint": {
|
|
18
|
+
"types": "./src/oxlint.d.ts",
|
|
19
|
+
"default": "./src/oxlint.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"author": "Ofri Peretz <ofriperetzdev@gmail.com>",
|
|
19
23
|
"license": "MIT",
|
|
20
|
-
"homepage": "https://
|
|
24
|
+
"homepage": "https://eslint.interlace.tools/docs/security/plugin-vercel-ai-security?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-vercel-ai-security",
|
|
21
25
|
"repository": {
|
|
22
26
|
"type": "git",
|
|
23
27
|
"url": "https://github.com/ofri-peretz/eslint",
|
|
@@ -64,7 +68,9 @@
|
|
|
64
68
|
"engines": {
|
|
65
69
|
"node": ">=18.0.0"
|
|
66
70
|
},
|
|
67
|
-
"peerDependencies": {
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
|
73
|
+
},
|
|
68
74
|
"peerDependenciesMeta": {
|
|
69
75
|
"ai": {
|
|
70
76
|
"optional": true
|
|
@@ -72,12 +78,22 @@
|
|
|
72
78
|
},
|
|
73
79
|
"dependencies": {
|
|
74
80
|
"tslib": "^2.3.0",
|
|
75
|
-
"@interlace/eslint-devkit": "^1.
|
|
81
|
+
"@interlace/eslint-devkit": "^1.4.1"
|
|
76
82
|
},
|
|
77
83
|
"devDependencies": {
|
|
78
|
-
"@ai-sdk/openai": "^
|
|
84
|
+
"@ai-sdk/openai": "^3.0.67",
|
|
79
85
|
"@typescript-eslint/parser": "^8.46.2",
|
|
80
86
|
"@typescript-eslint/rule-tester": "^8.46.2",
|
|
81
|
-
"ai": "^
|
|
87
|
+
"ai": "^6.0.193"
|
|
88
|
+
},
|
|
89
|
+
"scripts": {
|
|
90
|
+
"build": "tsx ../../scripts/build-package.ts",
|
|
91
|
+
"test": "vitest run",
|
|
92
|
+
"test:coverage": "vitest run --coverage",
|
|
93
|
+
"typecheck": "tsc -p tsconfig.lib.json --noEmit"
|
|
94
|
+
},
|
|
95
|
+
"funding": {
|
|
96
|
+
"type": "github",
|
|
97
|
+
"url": "https://github.com/ofri-peretz/eslint"
|
|
82
98
|
}
|
|
83
|
-
}
|
|
99
|
+
}
|
package/src/index.js
CHANGED
|
@@ -152,7 +152,7 @@ exports.rules = {
|
|
|
152
152
|
exports.plugin = {
|
|
153
153
|
meta: {
|
|
154
154
|
name: 'eslint-plugin-vercel-ai-security',
|
|
155
|
-
version: '
|
|
155
|
+
version: '1.3.6',
|
|
156
156
|
},
|
|
157
157
|
rules: exports.rules,
|
|
158
158
|
};
|
|
@@ -187,16 +187,16 @@ const recommendedConfig = {
|
|
|
187
187
|
'vercel-ai-security/no-dynamic-system-prompt': 'error',
|
|
188
188
|
'vercel-ai-security/require-tool-confirmation': 'error',
|
|
189
189
|
// High - enabled as warnings
|
|
190
|
-
'vercel-ai-security/require-tool-schema': '
|
|
191
|
-
'vercel-ai-security/require-max-tokens': '
|
|
192
|
-
'vercel-ai-security/require-max-steps': '
|
|
190
|
+
'vercel-ai-security/require-tool-schema': 'error',
|
|
191
|
+
'vercel-ai-security/require-max-tokens': 'error',
|
|
192
|
+
'vercel-ai-security/require-max-steps': 'error',
|
|
193
193
|
'vercel-ai-security/require-output-filtering': 'warn',
|
|
194
194
|
'vercel-ai-security/require-rag-content-validation': 'warn',
|
|
195
195
|
'vercel-ai-security/no-training-data-exposure': 'warn',
|
|
196
196
|
'vercel-ai-security/require-request-timeout': 'warn',
|
|
197
197
|
// Medium - disabled by default
|
|
198
198
|
'vercel-ai-security/require-error-handling': 'off',
|
|
199
|
-
'vercel-ai-security/require-abort-signal': '
|
|
199
|
+
'vercel-ai-security/require-abort-signal': 'error',
|
|
200
200
|
'vercel-ai-security/require-audit-logging': 'off',
|
|
201
201
|
'vercel-ai-security/require-embedding-validation': 'off',
|
|
202
202
|
'vercel-ai-security/require-output-validation': 'off',
|
|
@@ -229,7 +229,7 @@ const strictConfig = {
|
|
|
229
229
|
'vercel-ai-security/require-embedding-validation': 'error',
|
|
230
230
|
'vercel-ai-security/require-output-validation': 'error',
|
|
231
231
|
'vercel-ai-security/require-error-handling': 'error',
|
|
232
|
-
'vercel-ai-security/require-abort-signal': '
|
|
232
|
+
'vercel-ai-security/require-abort-signal': 'error',
|
|
233
233
|
'vercel-ai-security/require-audit-logging': 'warn',
|
|
234
234
|
},
|
|
235
235
|
};
|
|
@@ -237,6 +237,19 @@ const strictConfig = {
|
|
|
237
237
|
* Available configurations (3 options)
|
|
238
238
|
*/
|
|
239
239
|
exports.configs = {
|
|
240
|
+
/**
|
|
241
|
+
* Flagship preset — the rule from this plugin in the ecosystem-wide
|
|
242
|
+
* flagship list (`.agent/flagship-rules.md`). Anchors the OWASP LLM02
|
|
243
|
+
* niche — green-field, no competitor.
|
|
244
|
+
*/
|
|
245
|
+
flagship: {
|
|
246
|
+
plugins: {
|
|
247
|
+
'vercel-ai-security': exports.plugin,
|
|
248
|
+
},
|
|
249
|
+
rules: {
|
|
250
|
+
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
240
253
|
/** Minimal: 2 critical rules for gradual adoption */
|
|
241
254
|
minimal: minimalConfig,
|
|
242
255
|
/** Recommended: Balanced security (7 errors, 7 warnings) */
|
package/src/oxlint.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* oxlint sub-export.
|
|
8
|
+
*
|
|
9
|
+
* Consumers wire oxlint to this entry via:
|
|
10
|
+
* { "jsPlugins": ["eslint-plugin-vercel-ai-security/oxlint"] }
|
|
11
|
+
*
|
|
12
|
+
* oxlint's JS plugin loader does `require('eslint-plugin-vercel-ai-security/oxlint')` and reads
|
|
13
|
+
* `.rules` off the result. The compiled output of `export = plugin` is
|
|
14
|
+
* `module.exports = plugin`, which gives oxlint the plugin object directly
|
|
15
|
+
* (`{ meta, rules }`) — no wrapper, no `.default` indirection.
|
|
16
|
+
*
|
|
17
|
+
* Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
|
|
18
|
+
*/
|
|
19
|
+
import { plugin } from './index';
|
|
20
|
+
export = plugin;
|
package/src/oxlint.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* oxlint sub-export.
|
|
9
|
+
*
|
|
10
|
+
* Consumers wire oxlint to this entry via:
|
|
11
|
+
* { "jsPlugins": ["eslint-plugin-vercel-ai-security/oxlint"] }
|
|
12
|
+
*
|
|
13
|
+
* oxlint's JS plugin loader does `require('eslint-plugin-vercel-ai-security/oxlint')` and reads
|
|
14
|
+
* `.rules` off the result. The compiled output of `export = plugin` is
|
|
15
|
+
* `module.exports = plugin`, which gives oxlint the plugin object directly
|
|
16
|
+
* (`{ meta, rules }`) — no wrapper, no `.default` indirection.
|
|
17
|
+
*
|
|
18
|
+
* Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
|
|
19
|
+
*/
|
|
20
|
+
const index_1 = require("./index");
|
|
21
|
+
module.exports = index_1.plugin;
|
|
@@ -17,7 +17,10 @@ exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'problem',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md',
|
|
20
21
|
description: 'Prevent dynamic content in system prompts to avoid agent confusion attacks',
|
|
22
|
+
cwe: 'CWE-74',
|
|
23
|
+
cvss: 8,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
dynamicSystemPrompt: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -54,7 +57,7 @@ exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
54
57
|
create(context) {
|
|
55
58
|
const [options = {}] = context.options;
|
|
56
59
|
const allowStaticTemplates = options.allowStaticTemplates ?? true;
|
|
57
|
-
const sourceCode = context.sourceCode
|
|
60
|
+
const sourceCode = context.sourceCode;
|
|
58
61
|
// Vercel AI SDK functions
|
|
59
62
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
60
63
|
/**
|
|
@@ -17,7 +17,10 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'problem',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md',
|
|
20
21
|
description: 'Prevent hardcoded API keys in AI SDK model configuration',
|
|
22
|
+
cwe: 'CWE-798',
|
|
23
|
+
cvss: 8.5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
hardcodedApiKey: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -57,7 +60,7 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
57
60
|
const apiKeyPatterns = options.apiKeyPatterns ?? [
|
|
58
61
|
'apiKey', 'api_key', 'token', 'secret', 'credentials',
|
|
59
62
|
];
|
|
60
|
-
const sourceCode = context.sourceCode
|
|
63
|
+
const sourceCode = context.sourceCode;
|
|
61
64
|
// Known model provider functions
|
|
62
65
|
const providerFunctions = [
|
|
63
66
|
'openai', 'anthropic', 'google', 'cohere', 'mistral',
|
|
@@ -66,6 +69,7 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
66
69
|
/**
|
|
67
70
|
* Check if a string looks like an API key
|
|
68
71
|
*/
|
|
72
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
69
73
|
function looksLikeApiKey(value) {
|
|
70
74
|
// Common API key patterns
|
|
71
75
|
const patterns = [
|
|
@@ -17,7 +17,10 @@ exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'problem',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md',
|
|
20
21
|
description: 'Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts',
|
|
22
|
+
cwe: 'CWE-200',
|
|
23
|
+
cvss: 8,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
sensitiveInPrompt: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -64,7 +67,7 @@ exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
64
67
|
'password', 'secret', 'apiKey', 'api_key', 'token', 'credential',
|
|
65
68
|
'ssn', 'creditCard', 'privateKey', 'accessToken',
|
|
66
69
|
];
|
|
67
|
-
const sourceCode = context.sourceCode
|
|
70
|
+
const sourceCode = context.sourceCode;
|
|
68
71
|
// Vercel AI SDK functions
|
|
69
72
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
70
73
|
/**
|
|
@@ -115,12 +118,12 @@ exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
115
118
|
if (!optionsArg || optionsArg.type !== 'ObjectExpression')
|
|
116
119
|
return;
|
|
117
120
|
// Check prompt, system, and messages properties
|
|
118
|
-
const propsToCheck = ['prompt', 'system', 'messages'];
|
|
121
|
+
const propsToCheck = new Set(['prompt', 'system', 'messages']);
|
|
119
122
|
for (const prop of optionsArg.properties) {
|
|
120
123
|
if (prop.type !== 'Property')
|
|
121
124
|
continue;
|
|
122
125
|
const keyName = prop.key.type === 'Identifier' ? prop.key.name : null;
|
|
123
|
-
if (!keyName || !propsToCheck.
|
|
126
|
+
if (!keyName || !propsToCheck.has(keyName))
|
|
124
127
|
continue;
|
|
125
128
|
const sensitiveVar = findSensitiveData(prop.value);
|
|
126
129
|
if (sensitiveVar) {
|
|
@@ -17,7 +17,10 @@ exports.noSystemPromptLeak = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'problem',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-system-prompt-leak.md',
|
|
20
21
|
description: 'Prevent system prompts from being exposed in API responses or client code',
|
|
22
|
+
cwe: 'CWE-200',
|
|
23
|
+
cvss: 7.5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
systemPromptLeak: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -63,7 +66,7 @@ exports.noSystemPromptLeak = (0, eslint_devkit_1.createRule)({
|
|
|
63
66
|
'systemPrompt', 'system_prompt', 'SYSTEM_PROMPT',
|
|
64
67
|
'systemMessage', 'instructions', 'agentPrompt',
|
|
65
68
|
];
|
|
66
|
-
const sourceCode = context.sourceCode
|
|
69
|
+
const sourceCode = context.sourceCode;
|
|
67
70
|
/**
|
|
68
71
|
* Check if identifier matches system prompt pattern
|
|
69
72
|
*/
|
|
@@ -17,7 +17,10 @@ exports.noTrainingDataExposure = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'problem',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-training-data-exposure.md',
|
|
20
21
|
description: 'Prevent user data from being sent to LLM training endpoints',
|
|
22
|
+
cwe: 'CWE-359',
|
|
23
|
+
cvss: 7,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
trainingDataExposure: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -18,7 +18,11 @@ exports.noUnsafeOutputHandling = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-unsafe-output-handling.md',
|
|
21
22
|
description: 'Prevent using AI output directly in dangerous operations (eval, SQL, HTML)',
|
|
23
|
+
cwe: 'CWE-94',
|
|
24
|
+
cvss: 9.8,
|
|
25
|
+
confidence: 'medium',
|
|
22
26
|
},
|
|
23
27
|
messages: {
|
|
24
28
|
unsafeOutputExecution: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -95,19 +99,70 @@ exports.noUnsafeOutputHandling = (0, eslint_devkit_1.createRule)({
|
|
|
95
99
|
'result.text', 'response.text', 'completion', 'generated',
|
|
96
100
|
'aiOutput', 'aiResponse', 'llmOutput', '.text',
|
|
97
101
|
];
|
|
98
|
-
const sourceCode = context.sourceCode
|
|
102
|
+
const sourceCode = context.sourceCode;
|
|
99
103
|
// Dangerous execution functions
|
|
100
104
|
const dangerousFunctions = ['eval', 'Function', 'execSync', 'exec', 'spawn', 'execFile'];
|
|
101
105
|
// SQL execution patterns
|
|
102
106
|
const sqlPatterns = ['query', 'execute', 'run', 'raw'];
|
|
107
|
+
/**
|
|
108
|
+
* Names of variables locally bound to the result of a known AI SDK call.
|
|
109
|
+
* Tracks the idiomatic `const { text } = await generateText(...)` and
|
|
110
|
+
* `const result = await streamText(...)` patterns. Without this, the
|
|
111
|
+
* heuristic pattern match (`result.text`, `aiOutput`, …) missed every
|
|
112
|
+
* destructured-`text` case (real FN found by the OWASP-LLM02 corpus).
|
|
113
|
+
*/
|
|
114
|
+
const aiBoundNames = new Set();
|
|
115
|
+
const AI_SDK_CALLS = new Set(['generateText', 'streamText', 'generateObject', 'streamObject']);
|
|
116
|
+
function isAISDKCall(node) {
|
|
117
|
+
// generateText(...) | ai.generateText(...) | sdk.generateText(...)
|
|
118
|
+
let target = node;
|
|
119
|
+
if (target.type === 'AwaitExpression')
|
|
120
|
+
target = target.argument;
|
|
121
|
+
if (target.type !== 'CallExpression')
|
|
122
|
+
return false;
|
|
123
|
+
const callee = target.callee;
|
|
124
|
+
if (callee.type === 'Identifier' && AI_SDK_CALLS.has(callee.name))
|
|
125
|
+
return true;
|
|
126
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier' &&
|
|
127
|
+
AI_SDK_CALLS.has(callee.property.name))
|
|
128
|
+
return true;
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
103
131
|
/**
|
|
104
132
|
* Check if a node likely contains AI output
|
|
105
133
|
*/
|
|
106
134
|
function isLikelyAIOutput(node) {
|
|
135
|
+
// Direct member access into a tracked variable: `result.text`, `out.text`
|
|
136
|
+
if (node.type === 'MemberExpression' && node.object.type === 'Identifier' &&
|
|
137
|
+
aiBoundNames.has(node.object.name)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// Bare reference to a tracked variable (covers destructured `text` from
|
|
141
|
+
// `const { text } = await generateText(...)`).
|
|
142
|
+
if (node.type === 'Identifier' && aiBoundNames.has(node.name)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Original heuristic — still useful for `result.text`-shaped source even
|
|
146
|
+
// when scope tracking missed the binding.
|
|
107
147
|
const text = sourceCode.getText(node);
|
|
108
148
|
return aiOutputPatterns.some((pattern) => text.includes(pattern));
|
|
109
149
|
}
|
|
110
150
|
return {
|
|
151
|
+
// Track `const r = await generateText(...)` / `const { text } = ...` shapes
|
|
152
|
+
VariableDeclarator(node) {
|
|
153
|
+
if (!node.init || !isAISDKCall(node.init))
|
|
154
|
+
return;
|
|
155
|
+
if (node.id.type === 'Identifier') {
|
|
156
|
+
aiBoundNames.add(node.id.name);
|
|
157
|
+
}
|
|
158
|
+
else if (node.id.type === 'ObjectPattern') {
|
|
159
|
+
for (const prop of node.id.properties) {
|
|
160
|
+
if (prop.type === 'Property' && prop.value.type === 'Identifier') {
|
|
161
|
+
aiBoundNames.add(prop.value.name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
111
166
|
// Check for eval() and similar with AI output
|
|
112
167
|
CallExpression(node) {
|
|
113
168
|
const callee = sourceCode.getText(node.callee);
|
|
@@ -16,9 +16,12 @@ const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
|
16
16
|
exports.requireAbortSignal = (0, eslint_devkit_1.createRule)({
|
|
17
17
|
name: 'require-abort-signal',
|
|
18
18
|
meta: {
|
|
19
|
-
type: '
|
|
19
|
+
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-abort-signal.md',
|
|
21
22
|
description: 'Require AbortSignal for streaming AI calls to enable proper cleanup',
|
|
23
|
+
cwe: 'CWE-404',
|
|
24
|
+
cvss: 4,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingAbortSignal: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -55,7 +58,7 @@ exports.requireAbortSignal = (0, eslint_devkit_1.createRule)({
|
|
|
55
58
|
create(context) {
|
|
56
59
|
const [options = {}] = context.options;
|
|
57
60
|
const targetFunctions = options.targetFunctions ?? ['streamText', 'streamObject'];
|
|
58
|
-
const sourceCode = context.sourceCode
|
|
61
|
+
const sourceCode = context.sourceCode;
|
|
59
62
|
return {
|
|
60
63
|
CallExpression(node) {
|
|
61
64
|
const callee = sourceCode.getText(node.callee);
|
|
@@ -17,7 +17,10 @@ exports.requireAuditLogging = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-audit-logging.md',
|
|
20
21
|
description: 'Suggest audit logging for AI SDK operations',
|
|
22
|
+
cwe: 'CWE-778',
|
|
23
|
+
cvss: 4,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
missingAuditLogging: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -54,8 +57,8 @@ exports.requireAuditLogging = (0, eslint_devkit_1.createRule)({
|
|
|
54
57
|
create(context) {
|
|
55
58
|
const [options = {}] = context.options;
|
|
56
59
|
const allowInTests = options.allowInTests ?? true;
|
|
57
|
-
const sourceCode = context.sourceCode
|
|
58
|
-
const filename = context.filename
|
|
60
|
+
const sourceCode = context.sourceCode;
|
|
61
|
+
const filename = context.filename;
|
|
59
62
|
// Skip test files if allowed
|
|
60
63
|
if (allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
61
64
|
return {};
|
|
@@ -17,7 +17,10 @@ exports.requireEmbeddingValidation = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-embedding-validation.md',
|
|
20
21
|
description: 'Require validation of embeddings before storage or similarity search',
|
|
22
|
+
cwe: 'CWE-20',
|
|
23
|
+
cvss: 5.5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
unvalidatedEmbedding: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -72,7 +75,7 @@ exports.requireEmbeddingValidation = (0, eslint_devkit_1.createRule)({
|
|
|
72
75
|
const validatorFunctions = options.validatorFunctions ?? [
|
|
73
76
|
'validate', 'verify', 'check', 'sanitize',
|
|
74
77
|
];
|
|
75
|
-
const sourceCode = context.sourceCode
|
|
78
|
+
const sourceCode = context.sourceCode;
|
|
76
79
|
// Vector store operations
|
|
77
80
|
const vectorStoreOps = ['upsert', 'insert', 'add', 'store', 'index', 'save'];
|
|
78
81
|
/**
|
|
@@ -18,7 +18,10 @@ exports.requireErrorHandling = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-error-handling.md',
|
|
21
22
|
description: 'Require error handling for AI SDK calls to prevent cascading failures',
|
|
23
|
+
cwe: 'CWE-755',
|
|
24
|
+
cvss: 5,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingErrorHandling: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -55,8 +58,8 @@ exports.requireErrorHandling = (0, eslint_devkit_1.createRule)({
|
|
|
55
58
|
create(context) {
|
|
56
59
|
const [options = {}] = context.options;
|
|
57
60
|
const allowInTests = options.allowInTests ?? true;
|
|
58
|
-
const sourceCode = context.sourceCode
|
|
59
|
-
const filename = context.filename
|
|
61
|
+
const sourceCode = context.sourceCode;
|
|
62
|
+
const filename = context.filename;
|
|
60
63
|
// Skip test files if allowed
|
|
61
64
|
if (allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
62
65
|
return {};
|
|
@@ -66,6 +69,7 @@ exports.requireErrorHandling = (0, eslint_devkit_1.createRule)({
|
|
|
66
69
|
/**
|
|
67
70
|
* Check if node is inside a try block
|
|
68
71
|
*/
|
|
72
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
69
73
|
function isInsideTryBlock(node) {
|
|
70
74
|
let parent = node.parent;
|
|
71
75
|
while (parent) {
|
|
@@ -18,7 +18,10 @@ exports.requireMaxSteps = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-steps.md',
|
|
21
22
|
description: 'Require maxSteps limit for multi-step tool calling to prevent infinite loops',
|
|
23
|
+
cwe: 'CWE-834',
|
|
24
|
+
cvss: 6.5,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingMaxSteps: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -54,7 +57,7 @@ exports.requireMaxSteps = (0, eslint_devkit_1.createRule)({
|
|
|
54
57
|
},
|
|
55
58
|
],
|
|
56
59
|
create(context) {
|
|
57
|
-
const sourceCode = context.sourceCode
|
|
60
|
+
const sourceCode = context.sourceCode;
|
|
58
61
|
// Vercel AI SDK functions that support tools
|
|
59
62
|
const functionsWithTools = ['generateText', 'streamText'];
|
|
60
63
|
return {
|
|
@@ -18,7 +18,10 @@ exports.requireMaxTokens = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-max-tokens.md',
|
|
21
22
|
description: 'Require maxTokens limit in generateText and streamText calls',
|
|
23
|
+
cwe: 'CWE-770',
|
|
24
|
+
cvss: 6.5,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingMaxTokens: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -64,7 +67,7 @@ exports.requireMaxTokens = (0, eslint_devkit_1.createRule)({
|
|
|
64
67
|
const targetFunctions = options.targetFunctions ?? [
|
|
65
68
|
'generateText', 'streamText', 'generateObject', 'streamObject',
|
|
66
69
|
];
|
|
67
|
-
const sourceCode = context.sourceCode
|
|
70
|
+
const sourceCode = context.sourceCode;
|
|
68
71
|
return {
|
|
69
72
|
CallExpression(node) {
|
|
70
73
|
const callee = sourceCode.getText(node.callee);
|
|
@@ -17,7 +17,10 @@ exports.requireOutputFiltering = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-filtering.md',
|
|
20
21
|
description: 'Require filtering of sensitive data returned by AI tools',
|
|
22
|
+
cwe: 'CWE-200',
|
|
23
|
+
cvss: 6.5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
missingOutputFilter: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -72,7 +75,7 @@ exports.requireOutputFiltering = (0, eslint_devkit_1.createRule)({
|
|
|
72
75
|
const filterFunctions = options.filterFunctions ?? [
|
|
73
76
|
'filter', 'sanitize', 'redact', 'mask',
|
|
74
77
|
];
|
|
75
|
-
const sourceCode = context.sourceCode
|
|
78
|
+
const sourceCode = context.sourceCode;
|
|
76
79
|
/**
|
|
77
80
|
* Check if expression is a data source call
|
|
78
81
|
*/
|
|
@@ -99,6 +102,7 @@ exports.requireOutputFiltering = (0, eslint_devkit_1.createRule)({
|
|
|
99
102
|
/**
|
|
100
103
|
* Get tool name from context
|
|
101
104
|
*/
|
|
105
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
102
106
|
function getToolName(node) {
|
|
103
107
|
let current = node.parent;
|
|
104
108
|
while (current) {
|
|
@@ -17,7 +17,10 @@ exports.requireOutputValidation = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-output-validation.md',
|
|
20
21
|
description: 'Require validation of AI output before displaying to users',
|
|
22
|
+
cwe: 'CWE-707',
|
|
23
|
+
cvss: 5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
unvalidatedOutput: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -72,7 +75,7 @@ exports.requireOutputValidation = (0, eslint_devkit_1.createRule)({
|
|
|
72
75
|
const validatorFunctions = options.validatorFunctions ?? [
|
|
73
76
|
'validate', 'verify', 'check', 'sanitize',
|
|
74
77
|
];
|
|
75
|
-
const sourceCode = context.sourceCode
|
|
78
|
+
const sourceCode = context.sourceCode;
|
|
76
79
|
// AI output property patterns
|
|
77
80
|
const aiOutputPatterns = ['.text', '.content', '.message', '.response', '.output'];
|
|
78
81
|
// Track variables that hold AI results
|
|
@@ -17,7 +17,10 @@ exports.requireRagContentValidation = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-rag-content-validation.md',
|
|
20
21
|
description: 'Require validation of RAG content before including in AI prompts',
|
|
22
|
+
cwe: 'CWE-74',
|
|
23
|
+
cvss: 6,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
unsanitizedRagContent: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -73,7 +76,7 @@ exports.requireRagContentValidation = (0, eslint_devkit_1.createRule)({
|
|
|
73
76
|
const validatorFunctions = options.validatorFunctions ?? [
|
|
74
77
|
'validate', 'sanitize', 'filter', 'clean',
|
|
75
78
|
];
|
|
76
|
-
const sourceCode = context.sourceCode
|
|
79
|
+
const sourceCode = context.sourceCode;
|
|
77
80
|
// Vercel AI SDK functions
|
|
78
81
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
79
82
|
// Track variables that hold RAG content
|
|
@@ -17,7 +17,10 @@ exports.requireRequestTimeout = (0, eslint_devkit_1.createRule)({
|
|
|
17
17
|
meta: {
|
|
18
18
|
type: 'suggestion',
|
|
19
19
|
docs: {
|
|
20
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-request-timeout.md',
|
|
20
21
|
description: 'Require timeout configuration for AI SDK calls to prevent DoS',
|
|
22
|
+
cwe: 'CWE-400',
|
|
23
|
+
cvss: 5,
|
|
21
24
|
},
|
|
22
25
|
messages: {
|
|
23
26
|
missingTimeout: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -54,8 +57,8 @@ exports.requireRequestTimeout = (0, eslint_devkit_1.createRule)({
|
|
|
54
57
|
create(context) {
|
|
55
58
|
const [options = {}] = context.options;
|
|
56
59
|
const allowInTests = options.allowInTests ?? true;
|
|
57
|
-
const sourceCode = context.sourceCode
|
|
58
|
-
const filename = context.filename
|
|
60
|
+
const sourceCode = context.sourceCode;
|
|
61
|
+
const filename = context.filename;
|
|
59
62
|
// Skip test files if allowed
|
|
60
63
|
if (allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
61
64
|
return {};
|
|
@@ -63,7 +66,7 @@ exports.requireRequestTimeout = (0, eslint_devkit_1.createRule)({
|
|
|
63
66
|
// Vercel AI SDK functions
|
|
64
67
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
65
68
|
// Timeout-related property names
|
|
66
|
-
const timeoutProperties = ['timeout', 'abortSignal', 'signal', 'timeoutMs', 'requestTimeout'];
|
|
69
|
+
const timeoutProperties = new Set(['timeout', 'abortSignal', 'signal', 'timeoutMs', 'requestTimeout']);
|
|
67
70
|
return {
|
|
68
71
|
CallExpression(node) {
|
|
69
72
|
const callee = sourceCode.getText(node.callee);
|
|
@@ -86,7 +89,7 @@ exports.requireRequestTimeout = (0, eslint_devkit_1.createRule)({
|
|
|
86
89
|
if (prop.type !== 'Property')
|
|
87
90
|
return false;
|
|
88
91
|
const keyName = prop.key.type === 'Identifier' ? prop.key.name : null;
|
|
89
|
-
return keyName && timeoutProperties.
|
|
92
|
+
return keyName && timeoutProperties.has(keyName);
|
|
90
93
|
});
|
|
91
94
|
if (!hasTimeout) {
|
|
92
95
|
context.report({
|
|
@@ -18,7 +18,10 @@ exports.requireToolConfirmation = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'suggestion',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-confirmation.md',
|
|
21
22
|
description: 'Require human confirmation for destructive tool operations (delete, transfer, execute)',
|
|
23
|
+
cwe: 'CWE-862',
|
|
24
|
+
cvss: 7,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingConfirmation: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -79,17 +82,18 @@ exports.requireToolConfirmation = (0, eslint_devkit_1.createRule)({
|
|
|
79
82
|
/**
|
|
80
83
|
* Check if tool has confirmation requirement
|
|
81
84
|
*/
|
|
85
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
82
86
|
function hasConfirmationFlag(toolDef) {
|
|
83
|
-
const confirmationProps = [
|
|
87
|
+
const confirmationProps = new Set([
|
|
84
88
|
'requiresConfirmation', 'requireConfirmation', 'confirmation',
|
|
85
89
|
'requiresApproval', 'requireApproval', 'approval',
|
|
86
90
|
'dangerouslyAllowBrowser', // If explicitly acknowledged
|
|
87
|
-
];
|
|
91
|
+
]);
|
|
88
92
|
return toolDef.properties.some(prop => {
|
|
89
93
|
if (prop.type !== 'Property')
|
|
90
94
|
return false;
|
|
91
95
|
const keyName = prop.key.type === 'Identifier' ? prop.key.name : null;
|
|
92
|
-
return keyName && confirmationProps.
|
|
96
|
+
return keyName && confirmationProps.has(keyName);
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
return {
|
|
@@ -18,7 +18,10 @@ exports.requireToolSchema = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-tool-schema.md',
|
|
21
22
|
description: 'Require inputSchema (Zod schema) for all AI SDK tools',
|
|
23
|
+
cwe: 'CWE-20',
|
|
24
|
+
cvss: 7.5,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
missingInputSchema: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -65,8 +68,8 @@ exports.requireToolSchema = (0, eslint_devkit_1.createRule)({
|
|
|
65
68
|
create(context) {
|
|
66
69
|
const [options = {}] = context.options;
|
|
67
70
|
const allowInTests = options.allowInTests ?? false;
|
|
68
|
-
const sourceCode = context.sourceCode
|
|
69
|
-
const filename = context.filename
|
|
71
|
+
const sourceCode = context.sourceCode;
|
|
72
|
+
const filename = context.filename;
|
|
70
73
|
// Skip test files if allowed
|
|
71
74
|
if (allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
72
75
|
return {};
|
|
@@ -18,7 +18,10 @@ exports.requireValidatedPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
18
18
|
meta: {
|
|
19
19
|
type: 'problem',
|
|
20
20
|
docs: {
|
|
21
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/require-validated-prompt.md',
|
|
21
22
|
description: 'Require validated/sanitized prompts in generateText and streamText calls',
|
|
23
|
+
cwe: 'CWE-74',
|
|
24
|
+
cvss: 9,
|
|
22
25
|
},
|
|
23
26
|
messages: {
|
|
24
27
|
unsafePrompt: (0, eslint_devkit_1.formatLLMMessage)({
|
|
@@ -103,8 +106,8 @@ exports.requireValidatedPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
103
106
|
'input', 'query', 'message', 'req.body', 'request.body',
|
|
104
107
|
];
|
|
105
108
|
const allowInTests = options.allowInTests ?? false;
|
|
106
|
-
const sourceCode = context.sourceCode
|
|
107
|
-
const filename = context.filename
|
|
109
|
+
const sourceCode = context.sourceCode;
|
|
110
|
+
const filename = context.filename;
|
|
108
111
|
// Skip test files if allowed
|
|
109
112
|
if (allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
110
113
|
return {};
|