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 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.6",
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": "^3.0.67",
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": "^6.0.193"
87
+ "ai": "^7.0.31"
88
88
  },
89
89
  "scripts": {
90
90
  "build": "tsx ../../scripts/build-package.ts",
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: '1.3.6',
155
+ version: '1.3.7',
156
156
  },
157
157
  rules: exports.rules,
158
158
  };
@@ -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
- const embeddingSource = isEmbeddingCall(valueNode);
130
- if (embeddingSource && !isValidated(valueNode)) {
131
- context.report({
132
- node: prop.value,
133
- messageId: 'unvalidatedEmbedding',
134
- data: { source: embeddingSource },
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
- if (isAIOutput(arg) && !isValidated(arg)) {
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) && !isValidated(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
  }