eslint-plugin-vercel-ai-security 1.3.6 → 1.3.7
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 +6 -0
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/index.js +1 -1
- package/src/rules/require-embedding-validation/index.js +9 -11
- package/src/rules/require-output-filtering/index.js +0 -4
- package/src/rules/require-output-validation/index.js +5 -14
- package/src/rules/require-rag-content-validation/index.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 1.3.7
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- [#252](https://github.com/ofri-peretz/eslint/pull/252) [`d67e395`](https://github.com/ofri-peretz/eslint/commit/d67e3953c2748ad36e6aebe0f24b1d04e518b4d0) Thanks [@ofri-peretz](https://github.com/ofri-peretz)! - Fix Codecov badge showing "unknown" — switch from flag to component URL format
|
|
6
|
+
|
|
1
7
|
## 1.3.6
|
|
2
8
|
|
|
3
9
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
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=vercel-ai-security" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=vercel-ai-security" alt="Codecov" /></a>
|
|
13
|
+
<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>
|
|
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
|
|
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.7",
|
|
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",
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
"@interlace/eslint-devkit": "^1.4.1"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@ai-sdk/openai": "^
|
|
84
|
+
"@ai-sdk/openai": "^4.0.16",
|
|
85
85
|
"@typescript-eslint/parser": "^8.46.2",
|
|
86
86
|
"@typescript-eslint/rule-tester": "^8.46.2",
|
|
87
|
-
"ai": "^
|
|
87
|
+
"ai": "^7.0.31"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
90
|
"build": "tsx ../../scripts/build-package.ts",
|
package/src/index.js
CHANGED
|
@@ -82,8 +82,6 @@ exports.requireEmbeddingValidation = (0, eslint_devkit_1.createRule)({
|
|
|
82
82
|
* Check if expression is an embedding call
|
|
83
83
|
*/
|
|
84
84
|
function isEmbeddingCall(node) {
|
|
85
|
-
if (node.type !== 'CallExpression')
|
|
86
|
-
return null;
|
|
87
85
|
const callee = sourceCode.getText(node.callee);
|
|
88
86
|
for (const pattern of embeddingPatterns) {
|
|
89
87
|
if (callee.toLowerCase().includes(pattern.toLowerCase())) {
|
|
@@ -96,8 +94,6 @@ exports.requireEmbeddingValidation = (0, eslint_devkit_1.createRule)({
|
|
|
96
94
|
* Check if expression is validated
|
|
97
95
|
*/
|
|
98
96
|
function isValidated(node) {
|
|
99
|
-
if (node.type !== 'CallExpression')
|
|
100
|
-
return false;
|
|
101
97
|
const callee = sourceCode.getText(node.callee);
|
|
102
98
|
return validatorFunctions.some((fn) => callee.toLowerCase().includes(fn.toLowerCase()));
|
|
103
99
|
}
|
|
@@ -126,13 +122,15 @@ exports.requireEmbeddingValidation = (0, eslint_devkit_1.createRule)({
|
|
|
126
122
|
if (valueNode.type === 'AwaitExpression') {
|
|
127
123
|
valueNode = valueNode.argument;
|
|
128
124
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
if (valueNode.type === 'CallExpression') {
|
|
126
|
+
const embeddingSource = isEmbeddingCall(valueNode);
|
|
127
|
+
if (embeddingSource && !isValidated(valueNode)) {
|
|
128
|
+
context.report({
|
|
129
|
+
node: prop.value,
|
|
130
|
+
messageId: 'unvalidatedEmbedding',
|
|
131
|
+
data: { source: embeddingSource },
|
|
132
|
+
});
|
|
133
|
+
}
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
136
|
}
|
|
@@ -80,8 +80,6 @@ exports.requireOutputFiltering = (0, eslint_devkit_1.createRule)({
|
|
|
80
80
|
* Check if expression is a data source call
|
|
81
81
|
*/
|
|
82
82
|
function isDataSourceCall(node) {
|
|
83
|
-
if (node.type !== 'CallExpression')
|
|
84
|
-
return null;
|
|
85
83
|
const callee = sourceCode.getText(node.callee);
|
|
86
84
|
for (const pattern of dataSourcePatterns) {
|
|
87
85
|
if (callee.toLowerCase().includes(pattern.toLowerCase())) {
|
|
@@ -94,8 +92,6 @@ exports.requireOutputFiltering = (0, eslint_devkit_1.createRule)({
|
|
|
94
92
|
* Check if expression is wrapped in a filter function
|
|
95
93
|
*/
|
|
96
94
|
function isFilteredCall(node) {
|
|
97
|
-
if (node.type !== 'CallExpression')
|
|
98
|
-
return false;
|
|
99
95
|
const callee = sourceCode.getText(node.callee);
|
|
100
96
|
return filterFunctions.some((filter) => callee.toLowerCase().includes(filter.toLowerCase()));
|
|
101
97
|
}
|
|
@@ -72,23 +72,11 @@ exports.requireOutputValidation = (0, eslint_devkit_1.createRule)({
|
|
|
72
72
|
const displayPatterns = options.displayPatterns ?? [
|
|
73
73
|
'render', 'display', 'show', 'send', 'respond',
|
|
74
74
|
];
|
|
75
|
-
const validatorFunctions = options.validatorFunctions ?? [
|
|
76
|
-
'validate', 'verify', 'check', 'sanitize',
|
|
77
|
-
];
|
|
78
75
|
const sourceCode = context.sourceCode;
|
|
79
76
|
// AI output property patterns
|
|
80
77
|
const aiOutputPatterns = ['.text', '.content', '.message', '.response', '.output'];
|
|
81
78
|
// Track variables that hold AI results
|
|
82
79
|
const aiResultVariables = new Set();
|
|
83
|
-
/**
|
|
84
|
-
* Check if expression is validated
|
|
85
|
-
*/
|
|
86
|
-
function isValidated(node) {
|
|
87
|
-
if (node.type !== 'CallExpression')
|
|
88
|
-
return false;
|
|
89
|
-
const callee = sourceCode.getText(node.callee);
|
|
90
|
-
return validatorFunctions.some((fn) => callee.toLowerCase().includes(fn.toLowerCase()));
|
|
91
|
-
}
|
|
92
80
|
/**
|
|
93
81
|
* Check if expression accesses AI output
|
|
94
82
|
*/
|
|
@@ -125,7 +113,10 @@ exports.requireOutputValidation = (0, eslint_devkit_1.createRule)({
|
|
|
125
113
|
return;
|
|
126
114
|
// Check arguments for unvalidated AI output
|
|
127
115
|
for (const arg of node.arguments) {
|
|
128
|
-
|
|
116
|
+
// NOTE: `isAIOutput` only matches MemberExpression/Identifier nodes,
|
|
117
|
+
// so a wrapped call like `render(validate(result.text))` never
|
|
118
|
+
// reaches here — validated output is inherently not flagged.
|
|
119
|
+
if (isAIOutput(arg)) {
|
|
129
120
|
context.report({
|
|
130
121
|
node: arg,
|
|
131
122
|
messageId: 'unvalidatedOutput',
|
|
@@ -137,7 +128,7 @@ exports.requireOutputValidation = (0, eslint_devkit_1.createRule)({
|
|
|
137
128
|
for (const prop of arg.properties) {
|
|
138
129
|
if (prop.type !== 'Property')
|
|
139
130
|
continue;
|
|
140
|
-
if (isAIOutput(prop.value)
|
|
131
|
+
if (isAIOutput(prop.value)) {
|
|
141
132
|
context.report({
|
|
142
133
|
node: prop.value,
|
|
143
134
|
messageId: 'unvalidatedOutput',
|
|
@@ -99,8 +99,6 @@ exports.requireRagContentValidation = (0, eslint_devkit_1.createRule)({
|
|
|
99
99
|
* Check if expression is wrapped in validation
|
|
100
100
|
*/
|
|
101
101
|
function isValidated(node) {
|
|
102
|
-
if (node.type !== 'CallExpression')
|
|
103
|
-
return false;
|
|
104
102
|
const callee = sourceCode.getText(node.callee);
|
|
105
103
|
return validatorFunctions.some((fn) => callee.toLowerCase().includes(fn.toLowerCase()));
|
|
106
104
|
}
|