eslint-plugin-vercel-ai-security 1.4.0 → 1.4.2
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/package.json +5 -20
- package/src/index.d.ts +17 -74
- package/src/index.js +1 -99
- package/src/oxlint.d.ts +17 -20
- package/src/oxlint.js +0 -18
- package/src/rules/no-dynamic-system-prompt/index.js +1 -23
- package/src/rules/no-hardcoded-api-keys/index.js +5 -28
- package/src/rules/no-sensitive-in-prompt/index.js +1 -21
- package/src/rules/no-system-prompt-leak/index.js +0 -25
- package/src/rules/no-training-data-exposure/index.js +0 -16
- package/src/rules/no-unsafe-output-handling/index.js +0 -35
- package/src/rules/require-abort-signal/index.js +0 -14
- package/src/rules/require-audit-logging/index.js +0 -24
- package/src/rules/require-embedding-validation/index.js +0 -23
- package/src/rules/require-error-handling/index.js +0 -19
- package/src/rules/require-max-steps/index.js +0 -18
- package/src/rules/require-max-tokens/index.js +0 -14
- package/src/rules/require-output-filtering/index.js +0 -23
- package/src/rules/require-output-validation/index.js +0 -23
- package/src/rules/require-rag-content-validation/index.js +1 -29
- package/src/rules/require-request-timeout/index.js +0 -16
- package/src/rules/require-tool-confirmation/index.js +1 -25
- package/src/rules/require-tool-schema/index.js +0 -19
- package/src/rules/require-validated-prompt/index.js +1 -34
- package/src/types/index.js +0 -5
- package/src/utils/prompt-props.js +8 -28
- package/AGENTS.md +0 -131
- package/CHANGELOG.md +0 -255
- package/src/rules/no-dynamic-system-prompt/index.d.ts +0 -14
- package/src/rules/no-hardcoded-api-keys/index.d.ts +0 -14
- package/src/rules/no-sensitive-in-prompt/index.d.ts +0 -14
- package/src/rules/no-system-prompt-leak/index.d.ts +0 -14
- package/src/rules/no-training-data-exposure/index.d.ts +0 -14
- package/src/rules/no-unsafe-output-handling/index.d.ts +0 -15
- package/src/rules/require-abort-signal/index.d.ts +0 -14
- package/src/rules/require-audit-logging/index.d.ts +0 -14
- package/src/rules/require-embedding-validation/index.d.ts +0 -16
- package/src/rules/require-error-handling/index.d.ts +0 -14
- package/src/rules/require-max-steps/index.d.ts +0 -14
- package/src/rules/require-max-tokens/index.d.ts +0 -16
- package/src/rules/require-output-filtering/index.d.ts +0 -16
- package/src/rules/require-output-validation/index.d.ts +0 -16
- package/src/rules/require-rag-content-validation/index.d.ts +0 -16
- package/src/rules/require-request-timeout/index.d.ts +0 -14
- package/src/rules/require-tool-confirmation/index.d.ts +0 -14
- package/src/rules/require-tool-schema/index.d.ts +0 -15
- package/src/rules/require-validated-prompt/index.d.ts +0 -19
- package/src/utils/prompt-props.d.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vercel-ai-security",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
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",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
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",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/ofri-peretz/eslint",
|
|
27
|
+
"url": "git+https://github.com/ofri-peretz/eslint.git",
|
|
28
28
|
"directory": "packages/eslint-plugin-vercel-ai-security"
|
|
29
29
|
},
|
|
30
30
|
"bugs": {
|
|
@@ -35,11 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"src/",
|
|
38
|
-
"dist/",
|
|
39
38
|
"README.md",
|
|
40
|
-
"LICENSE"
|
|
41
|
-
"CHANGELOG.md",
|
|
42
|
-
"AGENTS.md"
|
|
39
|
+
"LICENSE"
|
|
43
40
|
],
|
|
44
41
|
"keywords": [
|
|
45
42
|
"eslint",
|
|
@@ -86,22 +83,10 @@
|
|
|
86
83
|
},
|
|
87
84
|
"dependencies": {
|
|
88
85
|
"tslib": "^2.3.0",
|
|
89
|
-
"@interlace/eslint-devkit": "^1.
|
|
90
|
-
},
|
|
91
|
-
"devDependencies": {
|
|
92
|
-
"@ai-sdk/openai": "^4.0.16",
|
|
93
|
-
"@typescript-eslint/parser": "^8.46.2",
|
|
94
|
-
"@typescript-eslint/rule-tester": "^8.46.2",
|
|
95
|
-
"ai": "^7.0.31"
|
|
96
|
-
},
|
|
97
|
-
"scripts": {
|
|
98
|
-
"build": "tsx ../../scripts/build-package.ts",
|
|
99
|
-
"test": "vitest run",
|
|
100
|
-
"test:coverage": "vitest run --coverage",
|
|
101
|
-
"typecheck": "tsc -p tsconfig.lib.json --noEmit"
|
|
86
|
+
"@interlace/eslint-devkit": "^1.6.1"
|
|
102
87
|
},
|
|
103
88
|
"funding": {
|
|
104
89
|
"type": "github",
|
|
105
|
-
"url": "https://github.com/ofri-peretz/eslint"
|
|
90
|
+
"url": "git+https://github.com/ofri-peretz/eslint.git"
|
|
106
91
|
}
|
|
107
92
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,74 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export { requireValidatedPrompt } from './rules/require-validated-prompt';
|
|
19
|
-
export { noSensitiveInPrompt } from './rules/no-sensitive-in-prompt';
|
|
20
|
-
export { noTrainingDataExposure } from './rules/no-training-data-exposure';
|
|
21
|
-
export { requireRequestTimeout } from './rules/require-request-timeout';
|
|
22
|
-
export { noUnsafeOutputHandling } from './rules/no-unsafe-output-handling';
|
|
23
|
-
export { requireToolConfirmation } from './rules/require-tool-confirmation';
|
|
24
|
-
export { noSystemPromptLeak } from './rules/no-system-prompt-leak';
|
|
25
|
-
export { requireEmbeddingValidation } from './rules/require-embedding-validation';
|
|
26
|
-
export { requireOutputValidation } from './rules/require-output-validation';
|
|
27
|
-
export { requireMaxTokens } from './rules/require-max-tokens';
|
|
28
|
-
export { requireMaxSteps } from './rules/require-max-steps';
|
|
29
|
-
export { requireAbortSignal } from './rules/require-abort-signal';
|
|
30
|
-
export { noDynamicSystemPrompt } from './rules/no-dynamic-system-prompt';
|
|
31
|
-
export { requireToolSchema } from './rules/require-tool-schema';
|
|
32
|
-
export { noHardcodedApiKeys } from './rules/no-hardcoded-api-keys';
|
|
33
|
-
export { requireOutputFiltering } from './rules/require-output-filtering';
|
|
34
|
-
export { requireRagContentValidation } from './rules/require-rag-content-validation';
|
|
35
|
-
export { requireErrorHandling } from './rules/require-error-handling';
|
|
36
|
-
export { requireAuditLogging } from './rules/require-audit-logging';
|
|
37
|
-
/**
|
|
38
|
-
* Collection of all Vercel AI SDK security ESLint rules (19 total)
|
|
39
|
-
*
|
|
40
|
-
* OWASP Top 10 for LLM Applications 2025 (10/10):
|
|
41
|
-
* - LLM01: Prompt Injection → require-validated-prompt
|
|
42
|
-
* - LLM02: Sensitive Info Disclosure → no-sensitive-in-prompt
|
|
43
|
-
* - LLM03: Training Data Poisoning → no-training-data-exposure
|
|
44
|
-
* - LLM04: Model Denial of Service → require-request-timeout
|
|
45
|
-
* - LLM05: Improper Output Handling → no-unsafe-output-handling
|
|
46
|
-
* - LLM06: Excessive Agency → require-tool-confirmation
|
|
47
|
-
* - LLM07: System Prompt Leakage → no-system-prompt-leak
|
|
48
|
-
* - LLM08: Vector & Embedding Weaknesses → require-embedding-validation
|
|
49
|
-
* - LLM09: Misinformation → require-output-validation
|
|
50
|
-
* - LLM10: Unbounded Consumption → require-max-tokens, require-max-steps, require-abort-signal
|
|
51
|
-
*
|
|
52
|
-
* OWASP Agentic Top 10 2026 (9/10 - ASI06 N/A for TypeScript):
|
|
53
|
-
* - ASI01: Agent Confusion → no-dynamic-system-prompt
|
|
54
|
-
* - ASI02: Tool Misuse → require-tool-schema
|
|
55
|
-
* - ASI03: Identity Abuse → no-hardcoded-api-keys
|
|
56
|
-
* - ASI04: Data Exfiltration → require-output-filtering
|
|
57
|
-
* - ASI05: Unexpected Code Execution → no-unsafe-output-handling
|
|
58
|
-
* - ASI06: Memory Corruption → N/A (TypeScript is memory-safe)
|
|
59
|
-
* - ASI07: Poisoned RAG → require-rag-content-validation
|
|
60
|
-
* - ASI08: Cascading Failures → require-error-handling
|
|
61
|
-
* - ASI09: Human-Agent Trust → require-tool-confirmation
|
|
62
|
-
* - ASI10: Logging & Monitoring → require-audit-logging
|
|
63
|
-
*/
|
|
64
|
-
export declare const rules: Record<string, TSESLint.RuleModule<string, readonly unknown[]>>;
|
|
65
|
-
/**
|
|
66
|
-
* ESLint plugin object
|
|
67
|
-
*/
|
|
68
|
-
export declare const plugin: TSESLint.FlatConfig.Plugin;
|
|
69
|
-
/**
|
|
70
|
-
* Available configurations (3 options)
|
|
71
|
-
*/
|
|
72
|
-
export declare const configs: Record<string, TSESLint.FlatConfig.Config>;
|
|
73
|
-
export default plugin;
|
|
74
|
-
export type * from './types/index';
|
|
1
|
+
// Minimal declaration written by scripts/build-package.ts.
|
|
2
|
+
//
|
|
3
|
+
// tsc's generated entry declaration inlined every inferred rule-option type
|
|
4
|
+
// (up to 166 kB per plugin) for an API no consumer calls directly. This types
|
|
5
|
+
// what a flat config actually touches. Per-rule option types remain available
|
|
6
|
+
// from the "./types" subpath export where a plugin provides one.
|
|
7
|
+
import type { Linter, Rule } from 'eslint';
|
|
8
|
+
|
|
9
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
10
|
+
export declare const configs: Record<string, Linter.Config>;
|
|
11
|
+
export declare const plugin: {
|
|
12
|
+
meta: { name: string; version: string };
|
|
13
|
+
rules: Record<string, Rule.RuleModule>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare const _default: typeof plugin;
|
|
17
|
+
export default _default;
|
package/src/index.js
CHANGED
|
@@ -1,48 +1,25 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
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;
|
|
9
|
-
// Import rules - LLM01: Prompt Injection
|
|
10
4
|
const require_validated_prompt_1 = require("./rules/require-validated-prompt");
|
|
11
|
-
// LLM02: Sensitive Information Disclosure
|
|
12
5
|
const no_sensitive_in_prompt_1 = require("./rules/no-sensitive-in-prompt");
|
|
13
|
-
// LLM03: Training Data Poisoning
|
|
14
6
|
const no_training_data_exposure_1 = require("./rules/no-training-data-exposure");
|
|
15
|
-
// LLM04: Model Denial of Service
|
|
16
7
|
const require_request_timeout_1 = require("./rules/require-request-timeout");
|
|
17
|
-
// LLM05/ASI05: Improper Output Handling / Unexpected Code Execution
|
|
18
8
|
const no_unsafe_output_handling_1 = require("./rules/no-unsafe-output-handling");
|
|
19
|
-
// LLM06/ASI09: Excessive Agency / Human-Agent Trust
|
|
20
9
|
const require_tool_confirmation_1 = require("./rules/require-tool-confirmation");
|
|
21
|
-
// LLM07: System Prompt Leakage
|
|
22
10
|
const no_system_prompt_leak_1 = require("./rules/no-system-prompt-leak");
|
|
23
|
-
// LLM08: Vector & Embedding Weaknesses
|
|
24
11
|
const require_embedding_validation_1 = require("./rules/require-embedding-validation");
|
|
25
|
-
// LLM09: Misinformation
|
|
26
12
|
const require_output_validation_1 = require("./rules/require-output-validation");
|
|
27
|
-
// LLM10: Unbounded Consumption
|
|
28
13
|
const require_max_tokens_1 = require("./rules/require-max-tokens");
|
|
29
14
|
const require_max_steps_1 = require("./rules/require-max-steps");
|
|
30
15
|
const require_abort_signal_1 = require("./rules/require-abort-signal");
|
|
31
|
-
// ASI01: Agent Confusion
|
|
32
16
|
const no_dynamic_system_prompt_1 = require("./rules/no-dynamic-system-prompt");
|
|
33
|
-
// ASI02: Tool Misuse & Exploitation
|
|
34
17
|
const require_tool_schema_1 = require("./rules/require-tool-schema");
|
|
35
|
-
// ASI03: Identity & Privilege Abuse
|
|
36
18
|
const no_hardcoded_api_keys_1 = require("./rules/no-hardcoded-api-keys");
|
|
37
|
-
// ASI04: Data Exfiltration
|
|
38
19
|
const require_output_filtering_1 = require("./rules/require-output-filtering");
|
|
39
|
-
// ASI07: Poisoned RAG Pipeline
|
|
40
20
|
const require_rag_content_validation_1 = require("./rules/require-rag-content-validation");
|
|
41
|
-
// ASI08: Cascading Failures
|
|
42
21
|
const require_error_handling_1 = require("./rules/require-error-handling");
|
|
43
|
-
// ASI10: Logging & Monitoring
|
|
44
22
|
const require_audit_logging_1 = require("./rules/require-audit-logging");
|
|
45
|
-
// Re-export all rules
|
|
46
23
|
var require_validated_prompt_2 = require("./rules/require-validated-prompt");
|
|
47
24
|
Object.defineProperty(exports, "requireValidatedPrompt", { enumerable: true, get: function () { return require_validated_prompt_2.requireValidatedPrompt; } });
|
|
48
25
|
var no_sensitive_in_prompt_2 = require("./rules/no-sensitive-in-prompt");
|
|
@@ -81,85 +58,34 @@ var require_error_handling_2 = require("./rules/require-error-handling");
|
|
|
81
58
|
Object.defineProperty(exports, "requireErrorHandling", { enumerable: true, get: function () { return require_error_handling_2.requireErrorHandling; } });
|
|
82
59
|
var require_audit_logging_2 = require("./rules/require-audit-logging");
|
|
83
60
|
Object.defineProperty(exports, "requireAuditLogging", { enumerable: true, get: function () { return require_audit_logging_2.requireAuditLogging; } });
|
|
84
|
-
/**
|
|
85
|
-
* Collection of all Vercel AI SDK security ESLint rules (19 total)
|
|
86
|
-
*
|
|
87
|
-
* OWASP Top 10 for LLM Applications 2025 (10/10):
|
|
88
|
-
* - LLM01: Prompt Injection → require-validated-prompt
|
|
89
|
-
* - LLM02: Sensitive Info Disclosure → no-sensitive-in-prompt
|
|
90
|
-
* - LLM03: Training Data Poisoning → no-training-data-exposure
|
|
91
|
-
* - LLM04: Model Denial of Service → require-request-timeout
|
|
92
|
-
* - LLM05: Improper Output Handling → no-unsafe-output-handling
|
|
93
|
-
* - LLM06: Excessive Agency → require-tool-confirmation
|
|
94
|
-
* - LLM07: System Prompt Leakage → no-system-prompt-leak
|
|
95
|
-
* - LLM08: Vector & Embedding Weaknesses → require-embedding-validation
|
|
96
|
-
* - LLM09: Misinformation → require-output-validation
|
|
97
|
-
* - LLM10: Unbounded Consumption → require-max-tokens, require-max-steps, require-abort-signal
|
|
98
|
-
*
|
|
99
|
-
* OWASP Agentic Top 10 2026 (9/10 - ASI06 N/A for TypeScript):
|
|
100
|
-
* - ASI01: Agent Confusion → no-dynamic-system-prompt
|
|
101
|
-
* - ASI02: Tool Misuse → require-tool-schema
|
|
102
|
-
* - ASI03: Identity Abuse → no-hardcoded-api-keys
|
|
103
|
-
* - ASI04: Data Exfiltration → require-output-filtering
|
|
104
|
-
* - ASI05: Unexpected Code Execution → no-unsafe-output-handling
|
|
105
|
-
* - ASI06: Memory Corruption → N/A (TypeScript is memory-safe)
|
|
106
|
-
* - ASI07: Poisoned RAG → require-rag-content-validation
|
|
107
|
-
* - ASI08: Cascading Failures → require-error-handling
|
|
108
|
-
* - ASI09: Human-Agent Trust → require-tool-confirmation
|
|
109
|
-
* - ASI10: Logging & Monitoring → require-audit-logging
|
|
110
|
-
*/
|
|
111
61
|
exports.rules = {
|
|
112
|
-
// OWASP LLM01: Prompt Injection
|
|
113
62
|
'require-validated-prompt': require_validated_prompt_1.requireValidatedPrompt,
|
|
114
|
-
// OWASP LLM02: Sensitive Information Disclosure
|
|
115
63
|
'no-sensitive-in-prompt': no_sensitive_in_prompt_1.noSensitiveInPrompt,
|
|
116
|
-
// OWASP LLM03: Training Data Poisoning
|
|
117
64
|
'no-training-data-exposure': no_training_data_exposure_1.noTrainingDataExposure,
|
|
118
|
-
// OWASP LLM04: Model Denial of Service
|
|
119
65
|
'require-request-timeout': require_request_timeout_1.requireRequestTimeout,
|
|
120
|
-
// OWASP LLM05: Improper Output Handling / ASI05: Code Execution
|
|
121
66
|
'no-unsafe-output-handling': no_unsafe_output_handling_1.noUnsafeOutputHandling,
|
|
122
|
-
// OWASP LLM06: Excessive Agency / ASI09: Human-Agent Trust
|
|
123
67
|
'require-tool-confirmation': require_tool_confirmation_1.requireToolConfirmation,
|
|
124
|
-
// OWASP LLM07: System Prompt Leakage
|
|
125
68
|
'no-system-prompt-leak': no_system_prompt_leak_1.noSystemPromptLeak,
|
|
126
|
-
// OWASP LLM08: Vector & Embedding Weaknesses
|
|
127
69
|
'require-embedding-validation': require_embedding_validation_1.requireEmbeddingValidation,
|
|
128
|
-
// OWASP LLM09: Misinformation
|
|
129
70
|
'require-output-validation': require_output_validation_1.requireOutputValidation,
|
|
130
|
-
// OWASP LLM10: Unbounded Consumption
|
|
131
71
|
'require-max-tokens': require_max_tokens_1.requireMaxTokens,
|
|
132
72
|
'require-max-steps': require_max_steps_1.requireMaxSteps,
|
|
133
73
|
'require-abort-signal': require_abort_signal_1.requireAbortSignal,
|
|
134
|
-
// OWASP ASI01: Agent Confusion
|
|
135
74
|
'no-dynamic-system-prompt': no_dynamic_system_prompt_1.noDynamicSystemPrompt,
|
|
136
|
-
// OWASP ASI02: Tool Misuse & Exploitation
|
|
137
75
|
'require-tool-schema': require_tool_schema_1.requireToolSchema,
|
|
138
|
-
// OWASP ASI03: Identity & Privilege Abuse
|
|
139
76
|
'no-hardcoded-api-keys': no_hardcoded_api_keys_1.noHardcodedApiKeys,
|
|
140
|
-
// OWASP ASI04: Data Exfiltration
|
|
141
77
|
'require-output-filtering': require_output_filtering_1.requireOutputFiltering,
|
|
142
|
-
// OWASP ASI07: Poisoned RAG Pipeline
|
|
143
78
|
'require-rag-content-validation': require_rag_content_validation_1.requireRagContentValidation,
|
|
144
|
-
// OWASP ASI08: Cascading Failures
|
|
145
79
|
'require-error-handling': require_error_handling_1.requireErrorHandling,
|
|
146
|
-
// OWASP ASI10: Logging & Monitoring
|
|
147
80
|
'require-audit-logging': require_audit_logging_1.requireAuditLogging,
|
|
148
81
|
};
|
|
149
|
-
/**
|
|
150
|
-
* ESLint plugin object
|
|
151
|
-
*/
|
|
152
82
|
exports.plugin = {
|
|
153
83
|
meta: {
|
|
154
84
|
name: 'eslint-plugin-vercel-ai-security',
|
|
155
|
-
version: '1.4.
|
|
85
|
+
version: '1.4.2',
|
|
156
86
|
},
|
|
157
87
|
rules: exports.rules,
|
|
158
88
|
};
|
|
159
|
-
/**
|
|
160
|
-
* Minimal configuration - for gradual adoption
|
|
161
|
-
* Only the 2 most critical rules
|
|
162
|
-
*/
|
|
163
89
|
const minimalConfig = {
|
|
164
90
|
plugins: {
|
|
165
91
|
'vercel-ai-security': exports.plugin,
|
|
@@ -169,16 +95,11 @@ const minimalConfig = {
|
|
|
169
95
|
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
170
96
|
},
|
|
171
97
|
};
|
|
172
|
-
/**
|
|
173
|
-
* Recommended configuration - balanced security
|
|
174
|
-
* Critical rules as errors, high-priority as warnings
|
|
175
|
-
*/
|
|
176
98
|
const recommendedConfig = {
|
|
177
99
|
plugins: {
|
|
178
100
|
'vercel-ai-security': exports.plugin,
|
|
179
101
|
},
|
|
180
102
|
rules: {
|
|
181
|
-
// Critical - always enabled as errors (LLM01-02, LLM05-07)
|
|
182
103
|
'vercel-ai-security/require-validated-prompt': 'error',
|
|
183
104
|
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
184
105
|
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
@@ -186,7 +107,6 @@ const recommendedConfig = {
|
|
|
186
107
|
'vercel-ai-security/no-system-prompt-leak': 'error',
|
|
187
108
|
'vercel-ai-security/no-dynamic-system-prompt': 'error',
|
|
188
109
|
'vercel-ai-security/require-tool-confirmation': 'error',
|
|
189
|
-
// High - enabled as warnings
|
|
190
110
|
'vercel-ai-security/require-tool-schema': 'error',
|
|
191
111
|
'vercel-ai-security/require-max-tokens': 'error',
|
|
192
112
|
'vercel-ai-security/require-max-steps': 'error',
|
|
@@ -194,7 +114,6 @@ const recommendedConfig = {
|
|
|
194
114
|
'vercel-ai-security/require-rag-content-validation': 'warn',
|
|
195
115
|
'vercel-ai-security/no-training-data-exposure': 'warn',
|
|
196
116
|
'vercel-ai-security/require-request-timeout': 'warn',
|
|
197
|
-
// Medium - disabled by default
|
|
198
117
|
'vercel-ai-security/require-error-handling': 'off',
|
|
199
118
|
'vercel-ai-security/require-abort-signal': 'error',
|
|
200
119
|
'vercel-ai-security/require-audit-logging': 'off',
|
|
@@ -202,16 +121,11 @@ const recommendedConfig = {
|
|
|
202
121
|
'vercel-ai-security/require-output-validation': 'off',
|
|
203
122
|
},
|
|
204
123
|
};
|
|
205
|
-
/**
|
|
206
|
-
* Strict configuration - maximum security
|
|
207
|
-
* All rules enabled for production deployments
|
|
208
|
-
*/
|
|
209
124
|
const strictConfig = {
|
|
210
125
|
plugins: {
|
|
211
126
|
'vercel-ai-security': exports.plugin,
|
|
212
127
|
},
|
|
213
128
|
rules: {
|
|
214
|
-
// All critical and high as errors
|
|
215
129
|
'vercel-ai-security/require-validated-prompt': 'error',
|
|
216
130
|
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
217
131
|
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
@@ -233,15 +147,7 @@ const strictConfig = {
|
|
|
233
147
|
'vercel-ai-security/require-audit-logging': 'warn',
|
|
234
148
|
},
|
|
235
149
|
};
|
|
236
|
-
/**
|
|
237
|
-
* Available configurations (3 options)
|
|
238
|
-
*/
|
|
239
150
|
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
151
|
flagship: {
|
|
246
152
|
plugins: {
|
|
247
153
|
'vercel-ai-security': exports.plugin,
|
|
@@ -250,12 +156,8 @@ exports.configs = {
|
|
|
250
156
|
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
251
157
|
},
|
|
252
158
|
},
|
|
253
|
-
/** Minimal: 2 critical rules for gradual adoption */
|
|
254
159
|
minimal: minimalConfig,
|
|
255
|
-
/** Recommended: Balanced security (7 errors, 7 warnings) */
|
|
256
160
|
recommended: recommendedConfig,
|
|
257
|
-
/** Strict: Maximum security (17 errors, 2 warnings) */
|
|
258
161
|
strict: strictConfig,
|
|
259
162
|
};
|
|
260
|
-
// Default export
|
|
261
163
|
exports.default = exports.plugin;
|
package/src/oxlint.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
import { plugin } from './index';
|
|
20
|
-
export = plugin;
|
|
1
|
+
// Minimal declaration written by scripts/build-package.ts.
|
|
2
|
+
//
|
|
3
|
+
// tsc's generated entry declaration inlined every inferred rule-option type
|
|
4
|
+
// (up to 166 kB per plugin) for an API no consumer calls directly. This types
|
|
5
|
+
// what a flat config actually touches. Per-rule option types remain available
|
|
6
|
+
// from the "./types" subpath export where a plugin provides one.
|
|
7
|
+
import type { Linter, Rule } from 'eslint';
|
|
8
|
+
|
|
9
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
10
|
+
export declare const configs: Record<string, Linter.Config>;
|
|
11
|
+
export declare const plugin: {
|
|
12
|
+
meta: { name: string; version: string };
|
|
13
|
+
rules: Record<string, Rule.RuleModule>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare const _default: typeof plugin;
|
|
17
|
+
export default _default;
|
package/src/oxlint.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
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
2
|
const index_1 = require("./index");
|
|
21
3
|
module.exports = index_1.plugin;
|
|
@@ -1,16 +1,6 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noDynamicSystemPrompt = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Prevent dynamic content in system prompts
|
|
11
|
-
* @description Detects when system prompts contain dynamic/user-controlled content
|
|
12
|
-
* @see OWASP ASI01: Agent Confusion
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
const prompt_props_1 = require("../../utils/prompt-props");
|
|
16
6
|
exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
@@ -59,28 +49,20 @@ exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
59
49
|
const [options = {}] = context.options;
|
|
60
50
|
const allowStaticTemplates = options.allowStaticTemplates ?? true;
|
|
61
51
|
const sourceCode = context.sourceCode;
|
|
62
|
-
// Vercel AI SDK functions
|
|
63
52
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
64
|
-
/**
|
|
65
|
-
* Check if a node represents dynamic content
|
|
66
|
-
*/
|
|
67
53
|
function isDynamicContent(node) {
|
|
68
|
-
// Template literal with expressions
|
|
69
54
|
if (node.type === 'TemplateLiteral') {
|
|
70
55
|
if (node.expressions.length > 0) {
|
|
71
56
|
return true;
|
|
72
57
|
}
|
|
73
58
|
return !allowStaticTemplates;
|
|
74
59
|
}
|
|
75
|
-
// Binary expression (concatenation)
|
|
76
60
|
if (node.type === 'BinaryExpression' && node.operator === '+') {
|
|
77
61
|
return true;
|
|
78
62
|
}
|
|
79
|
-
// Call expression (function result)
|
|
80
63
|
if (node.type === 'CallExpression') {
|
|
81
64
|
return true;
|
|
82
65
|
}
|
|
83
|
-
// Await expression
|
|
84
66
|
if (node.type === 'AwaitExpression') {
|
|
85
67
|
return true;
|
|
86
68
|
}
|
|
@@ -89,22 +71,18 @@ exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
89
71
|
return {
|
|
90
72
|
CallExpression(node) {
|
|
91
73
|
const callee = sourceCode.getText(node.callee);
|
|
92
|
-
// Check if this is an AI SDK function
|
|
93
74
|
const isAIFunction = aiSDKFunctions.some(fn => callee.includes(fn));
|
|
94
75
|
if (!isAIFunction)
|
|
95
76
|
return;
|
|
96
|
-
// Check first argument (options object)
|
|
97
77
|
const optionsArg = node.arguments[0];
|
|
98
78
|
if (!optionsArg || optionsArg.type !== 'ObjectExpression')
|
|
99
79
|
return;
|
|
100
|
-
// Find the system-prompt property (`instructions` in AI SDK v7+, `system` before it)
|
|
101
80
|
for (const prop of optionsArg.properties) {
|
|
102
81
|
if (prop.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
|
|
103
82
|
continue;
|
|
104
|
-
const keyName = (0, prompt_props_1.getStaticPropName)(prop
|
|
83
|
+
const keyName = (0, prompt_props_1.getStaticPropName)(prop);
|
|
105
84
|
if (!(0, prompt_props_1.isSystemPromptProp)(keyName))
|
|
106
85
|
continue;
|
|
107
|
-
// Check if system prompt is dynamic
|
|
108
86
|
if (isDynamicContent(prop.value)) {
|
|
109
87
|
context.report({
|
|
110
88
|
node: prop.value,
|
|
@@ -1,16 +1,6 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noHardcodedApiKeys = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Prevent hardcoded API keys in AI SDK calls
|
|
11
|
-
* @description Detects hardcoded API keys/secrets in model configuration
|
|
12
|
-
* @see OWASP ASI03: Identity & Privilege Abuse
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'no-hardcoded-api-keys',
|
|
@@ -61,23 +51,17 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
61
51
|
'apiKey', 'api_key', 'token', 'secret', 'credentials',
|
|
62
52
|
];
|
|
63
53
|
const sourceCode = context.sourceCode;
|
|
64
|
-
// Known model provider functions
|
|
65
54
|
const providerFunctions = [
|
|
66
55
|
'openai', 'anthropic', 'google', 'cohere', 'mistral',
|
|
67
56
|
'createOpenAI', 'createAnthropic', 'createGoogle', 'createMistral',
|
|
68
57
|
];
|
|
69
|
-
/**
|
|
70
|
-
* Check if a string looks like an API key
|
|
71
|
-
*/
|
|
72
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
73
58
|
function looksLikeApiKey(value) {
|
|
74
|
-
// Common API key patterns
|
|
75
59
|
const patterns = [
|
|
76
|
-
/^sk-[a-zA-Z0-9]{20,}$/,
|
|
77
|
-
/^sk-ant-[a-zA-Z0-9-]+$/,
|
|
78
|
-
/^[a-zA-Z0-9]{32,}$/,
|
|
79
|
-
/^[A-Z0-9]{20,}$/,
|
|
80
|
-
/^AIza[a-zA-Z0-9-_]{35}$/,
|
|
60
|
+
/^sk-[a-zA-Z0-9]{20,}$/,
|
|
61
|
+
/^sk-ant-[a-zA-Z0-9-]+$/,
|
|
62
|
+
/^[a-zA-Z0-9]{32,}$/,
|
|
63
|
+
/^[A-Z0-9]{20,}$/,
|
|
64
|
+
/^AIza[a-zA-Z0-9-_]{35}$/,
|
|
81
65
|
];
|
|
82
66
|
return patterns.some(p => p.test(value));
|
|
83
67
|
}
|
|
@@ -90,18 +74,14 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
90
74
|
: null;
|
|
91
75
|
if (!keyName)
|
|
92
76
|
return;
|
|
93
|
-
// Check if this is an API key property
|
|
94
77
|
const isApiKeyProp = apiKeyPatterns.some((pattern) => keyName.toLowerCase().includes(pattern.toLowerCase()));
|
|
95
78
|
if (!isApiKeyProp)
|
|
96
79
|
return;
|
|
97
|
-
// Check if value is a hardcoded string
|
|
98
80
|
if (node.value.type === 'Literal' && typeof node.value.value === 'string') {
|
|
99
81
|
const value = node.value.value;
|
|
100
|
-
// Skip placeholder values
|
|
101
82
|
if (value === '' || value === 'YOUR_API_KEY' || value.startsWith('$')) {
|
|
102
83
|
return;
|
|
103
84
|
}
|
|
104
|
-
// Report if it looks like a real API key
|
|
105
85
|
if (looksLikeApiKey(value) || value.length > 20) {
|
|
106
86
|
context.report({
|
|
107
87
|
node: node.value,
|
|
@@ -111,14 +91,11 @@ exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
|
111
91
|
}
|
|
112
92
|
}
|
|
113
93
|
},
|
|
114
|
-
// Also check model provider function calls
|
|
115
94
|
CallExpression(node) {
|
|
116
95
|
const callee = sourceCode.getText(node.callee);
|
|
117
|
-
// Check if this is a model provider function
|
|
118
96
|
const isProvider = providerFunctions.some(fn => callee.includes(fn));
|
|
119
97
|
if (!isProvider)
|
|
120
98
|
return;
|
|
121
|
-
// Check if second argument is options with apiKey
|
|
122
99
|
const optionsArg = node.arguments[1];
|
|
123
100
|
if (optionsArg && optionsArg.type === 'ObjectExpression') {
|
|
124
101
|
for (const prop of optionsArg.properties) {
|
|
@@ -1,16 +1,6 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noSensitiveInPrompt = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Prevent sensitive data from being passed to LLM prompts
|
|
11
|
-
* @description Detects secrets, credentials, PII in prompts
|
|
12
|
-
* @see OWASP LLM02: Sensitive Information Disclosure
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
const prompt_props_1 = require("../../utils/prompt-props");
|
|
16
6
|
exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
@@ -69,18 +59,11 @@ exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
69
59
|
'ssn', 'creditCard', 'privateKey', 'accessToken',
|
|
70
60
|
];
|
|
71
61
|
const sourceCode = context.sourceCode;
|
|
72
|
-
// Vercel AI SDK functions
|
|
73
62
|
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
74
|
-
/**
|
|
75
|
-
* Check if an identifier name matches sensitive patterns
|
|
76
|
-
*/
|
|
77
63
|
function isSensitiveIdentifier(name) {
|
|
78
64
|
const lowerName = name.toLowerCase();
|
|
79
65
|
return sensitivePatterns.some((pattern) => lowerName.includes(pattern.toLowerCase()));
|
|
80
66
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Check if a node contains sensitive data
|
|
83
|
-
*/
|
|
84
67
|
function findSensitiveData(node) {
|
|
85
68
|
if (node.type === 'Identifier' && isSensitiveIdentifier(node.name)) {
|
|
86
69
|
return node.name;
|
|
@@ -110,20 +93,17 @@ exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
|
110
93
|
return {
|
|
111
94
|
CallExpression(node) {
|
|
112
95
|
const callee = sourceCode.getText(node.callee);
|
|
113
|
-
// Check if this is an AI SDK function
|
|
114
96
|
const isAIFunction = aiSDKFunctions.some(fn => callee.includes(fn));
|
|
115
97
|
if (!isAIFunction)
|
|
116
98
|
return;
|
|
117
|
-
// Check first argument (options object)
|
|
118
99
|
const optionsArg = node.arguments[0];
|
|
119
100
|
if (!optionsArg || optionsArg.type !== 'ObjectExpression')
|
|
120
101
|
return;
|
|
121
|
-
// Check prompt, the system-prompt props, and messages
|
|
122
102
|
const propsToCheck = new Set(['prompt', 'messages', ...prompt_props_1.SYSTEM_PROMPT_PROPS]);
|
|
123
103
|
for (const prop of optionsArg.properties) {
|
|
124
104
|
if (prop.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
|
|
125
105
|
continue;
|
|
126
|
-
const keyName = (0, prompt_props_1.getStaticPropName)(prop
|
|
106
|
+
const keyName = (0, prompt_props_1.getStaticPropName)(prop);
|
|
127
107
|
if (!keyName || !propsToCheck.has(keyName))
|
|
128
108
|
continue;
|
|
129
109
|
const sensitiveVar = findSensitiveData(prop.value);
|