eslint-plugin-vercel-ai-security 1.5.2 → 1.5.4
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 +3 -3
- package/src/index.js +1 -145
- package/src/oxlint.js +1 -3
- package/src/rules/no-dynamic-system-prompt/index.js +1 -96
- package/src/rules/no-hardcoded-api-keys/index.js +1 -122
- package/src/rules/no-sensitive-in-prompt/index.js +1 -121
- package/src/rules/no-system-prompt-leak/index.js +1 -116
- package/src/rules/no-training-data-exposure/index.js +1 -96
- package/src/rules/no-unsafe-output-handling/index.js +1 -193
- package/src/rules/require-abort-signal/index.js +1 -80
- package/src/rules/require-audit-logging/index.js +1 -110
- package/src/rules/require-embedding-validation/index.js +1 -119
- package/src/rules/require-error-handling/index.js +1 -92
- package/src/rules/require-max-steps/index.js +1 -88
- package/src/rules/require-max-tokens/index.js +1 -94
- package/src/rules/require-output-filtering/index.js +1 -123
- package/src/rules/require-output-validation/index.js +1 -121
- package/src/rules/require-rag-content-validation/index.js +1 -150
- package/src/rules/require-request-timeout/index.js +1 -88
- package/src/rules/require-tool-confirmation/index.js +1 -118
- package/src/rules/require-tool-schema/index.js +1 -132
- package/src/rules/require-validated-prompt/index.js +1 -194
- package/src/types/index.js +1 -2
- package/src/utils/prompt-props.js +1 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-vercel-ai-security",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
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",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"node": ">=18.0.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"eslint": "^8.
|
|
77
|
+
"eslint": "^8.40.0 || ^9.0.0 || ^10.0.0",
|
|
78
78
|
"ai": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@interlace/eslint-devkit": "^1.
|
|
86
|
+
"@interlace/eslint-devkit": "^1.11.0"
|
|
87
87
|
},
|
|
88
88
|
"funding": {
|
|
89
89
|
"type": "github",
|
package/src/index.js
CHANGED
|
@@ -1,145 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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;
|
|
4
|
-
var require_validated_prompt_2 = require("./rules/require-validated-prompt");
|
|
5
|
-
Object.defineProperty(exports, "requireValidatedPrompt", { enumerable: true, get: function () { return require_validated_prompt_2.requireValidatedPrompt; } });
|
|
6
|
-
var no_sensitive_in_prompt_2 = require("./rules/no-sensitive-in-prompt");
|
|
7
|
-
Object.defineProperty(exports, "noSensitiveInPrompt", { enumerable: true, get: function () { return no_sensitive_in_prompt_2.noSensitiveInPrompt; } });
|
|
8
|
-
var no_training_data_exposure_2 = require("./rules/no-training-data-exposure");
|
|
9
|
-
Object.defineProperty(exports, "noTrainingDataExposure", { enumerable: true, get: function () { return no_training_data_exposure_2.noTrainingDataExposure; } });
|
|
10
|
-
var require_request_timeout_2 = require("./rules/require-request-timeout");
|
|
11
|
-
Object.defineProperty(exports, "requireRequestTimeout", { enumerable: true, get: function () { return require_request_timeout_2.requireRequestTimeout; } });
|
|
12
|
-
var no_unsafe_output_handling_2 = require("./rules/no-unsafe-output-handling");
|
|
13
|
-
Object.defineProperty(exports, "noUnsafeOutputHandling", { enumerable: true, get: function () { return no_unsafe_output_handling_2.noUnsafeOutputHandling; } });
|
|
14
|
-
var require_tool_confirmation_2 = require("./rules/require-tool-confirmation");
|
|
15
|
-
Object.defineProperty(exports, "requireToolConfirmation", { enumerable: true, get: function () { return require_tool_confirmation_2.requireToolConfirmation; } });
|
|
16
|
-
var no_system_prompt_leak_2 = require("./rules/no-system-prompt-leak");
|
|
17
|
-
Object.defineProperty(exports, "noSystemPromptLeak", { enumerable: true, get: function () { return no_system_prompt_leak_2.noSystemPromptLeak; } });
|
|
18
|
-
var require_embedding_validation_2 = require("./rules/require-embedding-validation");
|
|
19
|
-
Object.defineProperty(exports, "requireEmbeddingValidation", { enumerable: true, get: function () { return require_embedding_validation_2.requireEmbeddingValidation; } });
|
|
20
|
-
var require_output_validation_2 = require("./rules/require-output-validation");
|
|
21
|
-
Object.defineProperty(exports, "requireOutputValidation", { enumerable: true, get: function () { return require_output_validation_2.requireOutputValidation; } });
|
|
22
|
-
var require_max_tokens_2 = require("./rules/require-max-tokens");
|
|
23
|
-
Object.defineProperty(exports, "requireMaxTokens", { enumerable: true, get: function () { return require_max_tokens_2.requireMaxTokens; } });
|
|
24
|
-
var require_max_steps_2 = require("./rules/require-max-steps");
|
|
25
|
-
Object.defineProperty(exports, "requireMaxSteps", { enumerable: true, get: function () { return require_max_steps_2.requireMaxSteps; } });
|
|
26
|
-
var require_abort_signal_2 = require("./rules/require-abort-signal");
|
|
27
|
-
Object.defineProperty(exports, "requireAbortSignal", { enumerable: true, get: function () { return require_abort_signal_2.requireAbortSignal; } });
|
|
28
|
-
var no_dynamic_system_prompt_2 = require("./rules/no-dynamic-system-prompt");
|
|
29
|
-
Object.defineProperty(exports, "noDynamicSystemPrompt", { enumerable: true, get: function () { return no_dynamic_system_prompt_2.noDynamicSystemPrompt; } });
|
|
30
|
-
var require_tool_schema_2 = require("./rules/require-tool-schema");
|
|
31
|
-
Object.defineProperty(exports, "requireToolSchema", { enumerable: true, get: function () { return require_tool_schema_2.requireToolSchema; } });
|
|
32
|
-
var no_hardcoded_api_keys_2 = require("./rules/no-hardcoded-api-keys");
|
|
33
|
-
Object.defineProperty(exports, "noHardcodedApiKeys", { enumerable: true, get: function () { return no_hardcoded_api_keys_2.noHardcodedApiKeys; } });
|
|
34
|
-
var require_output_filtering_2 = require("./rules/require-output-filtering");
|
|
35
|
-
Object.defineProperty(exports, "requireOutputFiltering", { enumerable: true, get: function () { return require_output_filtering_2.requireOutputFiltering; } });
|
|
36
|
-
var require_rag_content_validation_2 = require("./rules/require-rag-content-validation");
|
|
37
|
-
Object.defineProperty(exports, "requireRagContentValidation", { enumerable: true, get: function () { return require_rag_content_validation_2.requireRagContentValidation; } });
|
|
38
|
-
var require_error_handling_2 = require("./rules/require-error-handling");
|
|
39
|
-
Object.defineProperty(exports, "requireErrorHandling", { enumerable: true, get: function () { return require_error_handling_2.requireErrorHandling; } });
|
|
40
|
-
var require_audit_logging_2 = require("./rules/require-audit-logging");
|
|
41
|
-
Object.defineProperty(exports, "requireAuditLogging", { enumerable: true, get: function () { return require_audit_logging_2.requireAuditLogging; } });
|
|
42
|
-
exports.rules = {
|
|
43
|
-
get 'require-validated-prompt'() { return require("./rules/require-validated-prompt").requireValidatedPrompt; },
|
|
44
|
-
get 'no-sensitive-in-prompt'() { return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt; },
|
|
45
|
-
get 'no-training-data-exposure'() { return require("./rules/no-training-data-exposure").noTrainingDataExposure; },
|
|
46
|
-
get 'require-request-timeout'() { return require("./rules/require-request-timeout").requireRequestTimeout; },
|
|
47
|
-
get 'no-unsafe-output-handling'() { return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling; },
|
|
48
|
-
get 'require-tool-confirmation'() { return require("./rules/require-tool-confirmation").requireToolConfirmation; },
|
|
49
|
-
get 'no-system-prompt-leak'() { return require("./rules/no-system-prompt-leak").noSystemPromptLeak; },
|
|
50
|
-
get 'require-embedding-validation'() { return require("./rules/require-embedding-validation").requireEmbeddingValidation; },
|
|
51
|
-
get 'require-output-validation'() { return require("./rules/require-output-validation").requireOutputValidation; },
|
|
52
|
-
get 'require-max-tokens'() { return require("./rules/require-max-tokens").requireMaxTokens; },
|
|
53
|
-
get 'require-max-steps'() { return require("./rules/require-max-steps").requireMaxSteps; },
|
|
54
|
-
get 'require-abort-signal'() { return require("./rules/require-abort-signal").requireAbortSignal; },
|
|
55
|
-
get 'no-dynamic-system-prompt'() { return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt; },
|
|
56
|
-
get 'require-tool-schema'() { return require("./rules/require-tool-schema").requireToolSchema; },
|
|
57
|
-
get 'no-hardcoded-api-keys'() { return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys; },
|
|
58
|
-
get 'require-output-filtering'() { return require("./rules/require-output-filtering").requireOutputFiltering; },
|
|
59
|
-
get 'require-rag-content-validation'() { return require("./rules/require-rag-content-validation").requireRagContentValidation; },
|
|
60
|
-
get 'require-error-handling'() { return require("./rules/require-error-handling").requireErrorHandling; },
|
|
61
|
-
get 'require-audit-logging'() { return require("./rules/require-audit-logging").requireAuditLogging; },
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
exports.plugin = {
|
|
65
|
-
meta: {
|
|
66
|
-
name: 'eslint-plugin-vercel-ai-security',
|
|
67
|
-
version: '1.5.2',
|
|
68
|
-
},
|
|
69
|
-
rules: exports.rules,
|
|
70
|
-
};
|
|
71
|
-
const minimalConfig = {
|
|
72
|
-
plugins: {
|
|
73
|
-
'vercel-ai-security': exports.plugin,
|
|
74
|
-
},
|
|
75
|
-
rules: {
|
|
76
|
-
'vercel-ai-security/require-validated-prompt': 'error',
|
|
77
|
-
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
const recommendedConfig = {
|
|
81
|
-
plugins: {
|
|
82
|
-
'vercel-ai-security': exports.plugin,
|
|
83
|
-
},
|
|
84
|
-
rules: {
|
|
85
|
-
'vercel-ai-security/require-validated-prompt': 'error',
|
|
86
|
-
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
87
|
-
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
88
|
-
'vercel-ai-security/no-sensitive-in-prompt': 'error',
|
|
89
|
-
'vercel-ai-security/no-system-prompt-leak': 'error',
|
|
90
|
-
'vercel-ai-security/no-dynamic-system-prompt': 'error',
|
|
91
|
-
'vercel-ai-security/require-tool-confirmation': 'error',
|
|
92
|
-
'vercel-ai-security/require-tool-schema': 'error',
|
|
93
|
-
'vercel-ai-security/require-max-tokens': 'error',
|
|
94
|
-
'vercel-ai-security/require-max-steps': 'error',
|
|
95
|
-
'vercel-ai-security/require-output-filtering': 'warn',
|
|
96
|
-
'vercel-ai-security/require-rag-content-validation': 'warn',
|
|
97
|
-
'vercel-ai-security/no-training-data-exposure': 'warn',
|
|
98
|
-
'vercel-ai-security/require-request-timeout': 'warn',
|
|
99
|
-
'vercel-ai-security/require-error-handling': 'off',
|
|
100
|
-
'vercel-ai-security/require-abort-signal': 'error',
|
|
101
|
-
'vercel-ai-security/require-audit-logging': 'off',
|
|
102
|
-
'vercel-ai-security/require-embedding-validation': 'off',
|
|
103
|
-
'vercel-ai-security/require-output-validation': 'off',
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
const strictConfig = {
|
|
107
|
-
plugins: {
|
|
108
|
-
'vercel-ai-security': exports.plugin,
|
|
109
|
-
},
|
|
110
|
-
rules: {
|
|
111
|
-
'vercel-ai-security/require-validated-prompt': 'error',
|
|
112
|
-
'vercel-ai-security/no-hardcoded-api-keys': 'error',
|
|
113
|
-
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
114
|
-
'vercel-ai-security/no-sensitive-in-prompt': 'error',
|
|
115
|
-
'vercel-ai-security/no-system-prompt-leak': 'error',
|
|
116
|
-
'vercel-ai-security/no-dynamic-system-prompt': 'error',
|
|
117
|
-
'vercel-ai-security/require-tool-confirmation': 'error',
|
|
118
|
-
'vercel-ai-security/require-tool-schema': 'error',
|
|
119
|
-
'vercel-ai-security/require-max-tokens': 'error',
|
|
120
|
-
'vercel-ai-security/require-max-steps': 'error',
|
|
121
|
-
'vercel-ai-security/require-output-filtering': 'error',
|
|
122
|
-
'vercel-ai-security/require-rag-content-validation': 'error',
|
|
123
|
-
'vercel-ai-security/no-training-data-exposure': 'error',
|
|
124
|
-
'vercel-ai-security/require-request-timeout': 'error',
|
|
125
|
-
'vercel-ai-security/require-embedding-validation': 'error',
|
|
126
|
-
'vercel-ai-security/require-output-validation': 'error',
|
|
127
|
-
'vercel-ai-security/require-error-handling': 'error',
|
|
128
|
-
'vercel-ai-security/require-abort-signal': 'error',
|
|
129
|
-
'vercel-ai-security/require-audit-logging': 'warn',
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
exports.configs = {
|
|
133
|
-
flagship: {
|
|
134
|
-
plugins: {
|
|
135
|
-
'vercel-ai-security': exports.plugin,
|
|
136
|
-
},
|
|
137
|
-
rules: {
|
|
138
|
-
'vercel-ai-security/no-unsafe-output-handling': 'error',
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
minimal: minimalConfig,
|
|
142
|
-
recommended: recommendedConfig,
|
|
143
|
-
strict: strictConfig,
|
|
144
|
-
};
|
|
145
|
-
exports.default = exports.plugin;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.configs=exports.plugin=exports.rules=exports.requireAuditLogging=exports.requireErrorHandling=exports.requireRagContentValidation=exports.requireOutputFiltering=exports.noHardcodedApiKeys=exports.requireToolSchema=exports.noDynamicSystemPrompt=exports.requireAbortSignal=exports.requireMaxSteps=exports.requireMaxTokens=exports.requireOutputValidation=exports.requireEmbeddingValidation=exports.noSystemPromptLeak=exports.requireToolConfirmation=exports.noUnsafeOutputHandling=exports.requireRequestTimeout=exports.noTrainingDataExposure=exports.noSensitiveInPrompt=exports.requireValidatedPrompt=void 0;var require_validated_prompt_2=require("./rules/require-validated-prompt");Object.defineProperty(exports,"requireValidatedPrompt",{enumerable:true,get:function(){return require_validated_prompt_2.requireValidatedPrompt}});var no_sensitive_in_prompt_2=require("./rules/no-sensitive-in-prompt");Object.defineProperty(exports,"noSensitiveInPrompt",{enumerable:true,get:function(){return no_sensitive_in_prompt_2.noSensitiveInPrompt}});var no_training_data_exposure_2=require("./rules/no-training-data-exposure");Object.defineProperty(exports,"noTrainingDataExposure",{enumerable:true,get:function(){return no_training_data_exposure_2.noTrainingDataExposure}});var require_request_timeout_2=require("./rules/require-request-timeout");Object.defineProperty(exports,"requireRequestTimeout",{enumerable:true,get:function(){return require_request_timeout_2.requireRequestTimeout}});var no_unsafe_output_handling_2=require("./rules/no-unsafe-output-handling");Object.defineProperty(exports,"noUnsafeOutputHandling",{enumerable:true,get:function(){return no_unsafe_output_handling_2.noUnsafeOutputHandling}});var require_tool_confirmation_2=require("./rules/require-tool-confirmation");Object.defineProperty(exports,"requireToolConfirmation",{enumerable:true,get:function(){return require_tool_confirmation_2.requireToolConfirmation}});var no_system_prompt_leak_2=require("./rules/no-system-prompt-leak");Object.defineProperty(exports,"noSystemPromptLeak",{enumerable:true,get:function(){return no_system_prompt_leak_2.noSystemPromptLeak}});var require_embedding_validation_2=require("./rules/require-embedding-validation");Object.defineProperty(exports,"requireEmbeddingValidation",{enumerable:true,get:function(){return require_embedding_validation_2.requireEmbeddingValidation}});var require_output_validation_2=require("./rules/require-output-validation");Object.defineProperty(exports,"requireOutputValidation",{enumerable:true,get:function(){return require_output_validation_2.requireOutputValidation}});var require_max_tokens_2=require("./rules/require-max-tokens");Object.defineProperty(exports,"requireMaxTokens",{enumerable:true,get:function(){return require_max_tokens_2.requireMaxTokens}});var require_max_steps_2=require("./rules/require-max-steps");Object.defineProperty(exports,"requireMaxSteps",{enumerable:true,get:function(){return require_max_steps_2.requireMaxSteps}});var require_abort_signal_2=require("./rules/require-abort-signal");Object.defineProperty(exports,"requireAbortSignal",{enumerable:true,get:function(){return require_abort_signal_2.requireAbortSignal}});var no_dynamic_system_prompt_2=require("./rules/no-dynamic-system-prompt");Object.defineProperty(exports,"noDynamicSystemPrompt",{enumerable:true,get:function(){return no_dynamic_system_prompt_2.noDynamicSystemPrompt}});var require_tool_schema_2=require("./rules/require-tool-schema");Object.defineProperty(exports,"requireToolSchema",{enumerable:true,get:function(){return require_tool_schema_2.requireToolSchema}});var no_hardcoded_api_keys_2=require("./rules/no-hardcoded-api-keys");Object.defineProperty(exports,"noHardcodedApiKeys",{enumerable:true,get:function(){return no_hardcoded_api_keys_2.noHardcodedApiKeys}});var require_output_filtering_2=require("./rules/require-output-filtering");Object.defineProperty(exports,"requireOutputFiltering",{enumerable:true,get:function(){return require_output_filtering_2.requireOutputFiltering}});var require_rag_content_validation_2=require("./rules/require-rag-content-validation");Object.defineProperty(exports,"requireRagContentValidation",{enumerable:true,get:function(){return require_rag_content_validation_2.requireRagContentValidation}});var require_error_handling_2=require("./rules/require-error-handling");Object.defineProperty(exports,"requireErrorHandling",{enumerable:true,get:function(){return require_error_handling_2.requireErrorHandling}});var require_audit_logging_2=require("./rules/require-audit-logging");Object.defineProperty(exports,"requireAuditLogging",{enumerable:true,get:function(){return require_audit_logging_2.requireAuditLogging}});exports.rules={get"require-validated-prompt"(){return require("./rules/require-validated-prompt").requireValidatedPrompt},get"no-sensitive-in-prompt"(){return require("./rules/no-sensitive-in-prompt").noSensitiveInPrompt},get"no-training-data-exposure"(){return require("./rules/no-training-data-exposure").noTrainingDataExposure},get"require-request-timeout"(){return require("./rules/require-request-timeout").requireRequestTimeout},get"no-unsafe-output-handling"(){return require("./rules/no-unsafe-output-handling").noUnsafeOutputHandling},get"require-tool-confirmation"(){return require("./rules/require-tool-confirmation").requireToolConfirmation},get"no-system-prompt-leak"(){return require("./rules/no-system-prompt-leak").noSystemPromptLeak},get"require-embedding-validation"(){return require("./rules/require-embedding-validation").requireEmbeddingValidation},get"require-output-validation"(){return require("./rules/require-output-validation").requireOutputValidation},get"require-max-tokens"(){return require("./rules/require-max-tokens").requireMaxTokens},get"require-max-steps"(){return require("./rules/require-max-steps").requireMaxSteps},get"require-abort-signal"(){return require("./rules/require-abort-signal").requireAbortSignal},get"no-dynamic-system-prompt"(){return require("./rules/no-dynamic-system-prompt").noDynamicSystemPrompt},get"require-tool-schema"(){return require("./rules/require-tool-schema").requireToolSchema},get"no-hardcoded-api-keys"(){return require("./rules/no-hardcoded-api-keys").noHardcodedApiKeys},get"require-output-filtering"(){return require("./rules/require-output-filtering").requireOutputFiltering},get"require-rag-content-validation"(){return require("./rules/require-rag-content-validation").requireRagContentValidation},get"require-error-handling"(){return require("./rules/require-error-handling").requireErrorHandling},get"require-audit-logging"(){return require("./rules/require-audit-logging").requireAuditLogging}};exports.plugin={meta:{name:"eslint-plugin-vercel-ai-security",version:"1.5.4"},rules:exports.rules};const minimalConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error"}};const recommendedConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"warn","vercel-ai-security/require-rag-content-validation":"warn","vercel-ai-security/no-training-data-exposure":"warn","vercel-ai-security/require-request-timeout":"warn","vercel-ai-security/require-error-handling":"off","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"off","vercel-ai-security/require-embedding-validation":"off","vercel-ai-security/require-output-validation":"off"}};const strictConfig={plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/require-validated-prompt":"error","vercel-ai-security/no-hardcoded-api-keys":"error","vercel-ai-security/no-unsafe-output-handling":"error","vercel-ai-security/no-sensitive-in-prompt":"error","vercel-ai-security/no-system-prompt-leak":"error","vercel-ai-security/no-dynamic-system-prompt":"error","vercel-ai-security/require-tool-confirmation":"error","vercel-ai-security/require-tool-schema":"error","vercel-ai-security/require-max-tokens":"error","vercel-ai-security/require-max-steps":"error","vercel-ai-security/require-output-filtering":"error","vercel-ai-security/require-rag-content-validation":"error","vercel-ai-security/no-training-data-exposure":"error","vercel-ai-security/require-request-timeout":"error","vercel-ai-security/require-embedding-validation":"error","vercel-ai-security/require-output-validation":"error","vercel-ai-security/require-error-handling":"error","vercel-ai-security/require-abort-signal":"error","vercel-ai-security/require-audit-logging":"warn"}};exports.configs={flagship:{plugins:{"vercel-ai-security":exports.plugin},rules:{"vercel-ai-security/no-unsafe-output-handling":"error"}},minimal:minimalConfig,recommended:recommendedConfig,strict:strictConfig};exports.default=exports.plugin;
|
package/src/oxlint.js
CHANGED
|
@@ -1,96 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noDynamicSystemPrompt = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const prompt_props_1 = require("../../utils/prompt-props");
|
|
6
|
-
exports.noDynamicSystemPrompt = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-dynamic-system-prompt',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md',
|
|
12
|
-
description: 'Prevent dynamic content in system prompts to avoid agent confusion attacks',
|
|
13
|
-
cwe: 'CWE-74',
|
|
14
|
-
cvss: 8,
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
dynamicSystemPrompt: (0, eslint_devkit_1.formatLLMMessage)({
|
|
18
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
19
|
-
issueName: 'Dynamic System Prompt',
|
|
20
|
-
cwe: 'CWE-74',
|
|
21
|
-
owasp: 'A03:2021',
|
|
22
|
-
cvss: 8.0,
|
|
23
|
-
description: 'System prompt contains dynamic content. This can lead to agent confusion attacks.',
|
|
24
|
-
severity: 'HIGH',
|
|
25
|
-
compliance: ['SOC2'],
|
|
26
|
-
fix: 'Use a static system prompt defined as a constant. Avoid template literals or concatenation.',
|
|
27
|
-
documentationLink: 'https://owasp.org/www-project-top-10-for-large-language-model-applications/',
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
schema: [
|
|
31
|
-
{
|
|
32
|
-
type: 'object',
|
|
33
|
-
properties: {
|
|
34
|
-
allowStaticTemplates: {
|
|
35
|
-
type: 'boolean',
|
|
36
|
-
description: 'Allow template literals without expressions',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
additionalProperties: false,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
defaultOptions: [
|
|
44
|
-
{
|
|
45
|
-
allowStaticTemplates: true,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
create(context) {
|
|
49
|
-
const [options = {}] = context.options;
|
|
50
|
-
const allowStaticTemplates = options.allowStaticTemplates ?? true;
|
|
51
|
-
const sourceCode = context.sourceCode;
|
|
52
|
-
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
53
|
-
function isDynamicContent(node) {
|
|
54
|
-
if (node.type === 'TemplateLiteral') {
|
|
55
|
-
if (node.expressions.length > 0) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
return !allowStaticTemplates;
|
|
59
|
-
}
|
|
60
|
-
if (node.type === 'BinaryExpression' && node.operator === '+') {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
if (node.type === 'CallExpression') {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
if (node.type === 'AwaitExpression') {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
CallExpression(node) {
|
|
73
|
-
const callee = sourceCode.getText(node.callee);
|
|
74
|
-
const isAIFunction = aiSDKFunctions.some(fn => callee.includes(fn));
|
|
75
|
-
if (!isAIFunction)
|
|
76
|
-
return;
|
|
77
|
-
const optionsArg = node.arguments[0];
|
|
78
|
-
if (!optionsArg || optionsArg.type !== 'ObjectExpression')
|
|
79
|
-
return;
|
|
80
|
-
for (const prop of optionsArg.properties) {
|
|
81
|
-
if (prop.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
|
|
82
|
-
continue;
|
|
83
|
-
const keyName = (0, prompt_props_1.getStaticPropName)(prop);
|
|
84
|
-
if (!(0, prompt_props_1.isSystemPromptProp)(keyName))
|
|
85
|
-
continue;
|
|
86
|
-
if (isDynamicContent(prop.value)) {
|
|
87
|
-
context.report({
|
|
88
|
-
node: prop.value,
|
|
89
|
-
messageId: 'dynamicSystemPrompt',
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicSystemPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");exports.noDynamicSystemPrompt=(0,eslint_devkit_1.createRule)({name:"no-dynamic-system-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-dynamic-system-prompt.md",description:"Prevent dynamic content in system prompts to avoid agent confusion attacks",cwe:"CWE-74",cvss:8},messages:{dynamicSystemPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Dynamic System Prompt",cwe:"CWE-74",owasp:"A03:2021",cvss:8,description:"System prompt contains dynamic content. This can lead to agent confusion attacks.",severity:"HIGH",compliance:["SOC2"],fix:"Use a static system prompt defined as a constant. Avoid template literals or concatenation.",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{allowStaticTemplates:{type:"boolean",description:"Allow template literals without expressions"}},additionalProperties:false}]},defaultOptions:[{allowStaticTemplates:true}],create(context){const[options={}]=context.options;const allowStaticTemplates=options.allowStaticTemplates??true;const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isDynamicContent(node){if(node.type==="TemplateLiteral"){if(node.expressions.length>0){return true}return!allowStaticTemplates}if(node.type==="BinaryExpression"&&node.operator==="+"){return true}if(node.type==="CallExpression"){return true}if(node.type==="AwaitExpression"){return true}return false}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!(0,prompt_props_1.isSystemPromptProp)(keyName))continue;if(isDynamicContent(prop.value)){context.report({node:prop.value,messageId:"dynamicSystemPrompt"})}}}}}});
|
|
@@ -1,122 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noHardcodedApiKeys = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
exports.noHardcodedApiKeys = (0, eslint_devkit_1.createRule)({
|
|
6
|
-
name: 'no-hardcoded-api-keys',
|
|
7
|
-
meta: {
|
|
8
|
-
type: 'problem',
|
|
9
|
-
docs: {
|
|
10
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md',
|
|
11
|
-
description: 'Prevent hardcoded API keys in AI SDK model configuration',
|
|
12
|
-
cwe: 'CWE-798',
|
|
13
|
-
cvss: 8.5,
|
|
14
|
-
},
|
|
15
|
-
messages: {
|
|
16
|
-
hardcodedApiKey: (0, eslint_devkit_1.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
18
|
-
issueName: 'Hardcoded API Key Detected',
|
|
19
|
-
cwe: 'CWE-798',
|
|
20
|
-
owasp: 'A02:2021',
|
|
21
|
-
cvss: 8.5,
|
|
22
|
-
description: 'Hardcoded API key "{{key}}" detected. Use environment variables instead.',
|
|
23
|
-
severity: 'CRITICAL',
|
|
24
|
-
compliance: ['SOC2', 'PCI-DSS', 'GDPR'],
|
|
25
|
-
fix: 'Use environment variable: apiKey: process.env.OPENAI_API_KEY',
|
|
26
|
-
documentationLink: 'https://sdk.vercel.ai/docs/ai-sdk-core/settings',
|
|
27
|
-
}),
|
|
28
|
-
},
|
|
29
|
-
schema: [
|
|
30
|
-
{
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: {
|
|
33
|
-
apiKeyPatterns: {
|
|
34
|
-
type: 'array',
|
|
35
|
-
items: { type: 'string' },
|
|
36
|
-
description: 'Property names that contain API keys',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
additionalProperties: false,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
defaultOptions: [
|
|
44
|
-
{
|
|
45
|
-
apiKeyPatterns: ['apiKey', 'api_key', 'token', 'secret', 'credentials'],
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
create(context) {
|
|
49
|
-
const [options = {}] = context.options;
|
|
50
|
-
const apiKeyPatterns = options.apiKeyPatterns ?? [
|
|
51
|
-
'apiKey', 'api_key', 'token', 'secret', 'credentials',
|
|
52
|
-
];
|
|
53
|
-
const sourceCode = context.sourceCode;
|
|
54
|
-
const providerFunctions = [
|
|
55
|
-
'openai', 'anthropic', 'google', 'cohere', 'mistral',
|
|
56
|
-
'createOpenAI', 'createAnthropic', 'createGoogle', 'createMistral',
|
|
57
|
-
];
|
|
58
|
-
function looksLikeApiKey(value) {
|
|
59
|
-
const patterns = [
|
|
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}$/,
|
|
65
|
-
];
|
|
66
|
-
return patterns.some(p => p.test(value));
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
Property(node) {
|
|
70
|
-
const keyName = node.key.type === 'Identifier'
|
|
71
|
-
? node.key.name
|
|
72
|
-
: node.key.type === 'Literal'
|
|
73
|
-
? String(node.key.value)
|
|
74
|
-
: null;
|
|
75
|
-
if (!keyName)
|
|
76
|
-
return;
|
|
77
|
-
const isApiKeyProp = apiKeyPatterns.some((pattern) => keyName.toLowerCase().includes(pattern.toLowerCase()));
|
|
78
|
-
if (!isApiKeyProp)
|
|
79
|
-
return;
|
|
80
|
-
if (node.value.type === 'Literal' && typeof node.value.value === 'string') {
|
|
81
|
-
const value = node.value.value;
|
|
82
|
-
if (value === '' || value === 'YOUR_API_KEY' || value.startsWith('$')) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (looksLikeApiKey(value) || value.length > 20) {
|
|
86
|
-
context.report({
|
|
87
|
-
node: node.value,
|
|
88
|
-
messageId: 'hardcodedApiKey',
|
|
89
|
-
data: { key: value.substring(0, 10) + '...' },
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
CallExpression(node) {
|
|
95
|
-
const callee = sourceCode.getText(node.callee);
|
|
96
|
-
const isProvider = providerFunctions.some(fn => callee.includes(fn));
|
|
97
|
-
if (!isProvider)
|
|
98
|
-
return;
|
|
99
|
-
const optionsArg = node.arguments[1];
|
|
100
|
-
if (optionsArg && optionsArg.type === 'ObjectExpression') {
|
|
101
|
-
for (const prop of optionsArg.properties) {
|
|
102
|
-
if (prop.type !== 'Property')
|
|
103
|
-
continue;
|
|
104
|
-
const keyName = prop.key.type === 'Identifier' ? prop.key.name : null;
|
|
105
|
-
if (keyName === 'apiKey' || keyName === 'api_key') {
|
|
106
|
-
if (prop.value.type === 'Literal' && typeof prop.value.value === 'string') {
|
|
107
|
-
const value = prop.value.value;
|
|
108
|
-
if (looksLikeApiKey(value) || value.length > 20) {
|
|
109
|
-
context.report({
|
|
110
|
-
node: prop.value,
|
|
111
|
-
messageId: 'hardcodedApiKey',
|
|
112
|
-
data: { key: value.substring(0, 10) + '...' },
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
},
|
|
122
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noHardcodedApiKeys=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noHardcodedApiKeys=(0,eslint_devkit_1.createRule)({name:"no-hardcoded-api-keys",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-hardcoded-api-keys.md",description:"Prevent hardcoded API keys in AI SDK model configuration",cwe:"CWE-798",cvss:8.5},messages:{hardcodedApiKey:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Hardcoded API Key Detected",cwe:"CWE-798",owasp:"A02:2021",cvss:8.5,description:'Hardcoded API key "{{key}}" detected. Use environment variables instead.',severity:"CRITICAL",compliance:["SOC2","PCI-DSS","GDPR"],fix:"Use environment variable: apiKey: process.env.OPENAI_API_KEY",documentationLink:"https://sdk.vercel.ai/docs/ai-sdk-core/settings"})},schema:[{type:"object",properties:{apiKeyPatterns:{type:"array",items:{type:"string"},description:"Property names that contain API keys"}},additionalProperties:false}]},defaultOptions:[{apiKeyPatterns:["apiKey","api_key","token","secret","credentials"]}],create(context){const[options={}]=context.options;const apiKeyPatterns=options.apiKeyPatterns??["apiKey","api_key","token","secret","credentials"];const sourceCode=context.sourceCode;const providerFunctions=["openai","anthropic","google","cohere","mistral","createOpenAI","createAnthropic","createGoogle","createMistral"];function looksLikeApiKey(value){const patterns=[/^sk-[a-zA-Z0-9]{20,}$/,/^sk-ant-[a-zA-Z0-9-]+$/,/^[a-zA-Z0-9]{32,}$/,/^[A-Z0-9]{20,}$/,/^AIza[a-zA-Z0-9-_]{35}$/];return patterns.some(p=>p.test(value))}return{Property(node){const keyName=node.key.type==="Identifier"?node.key.name:node.key.type==="Literal"?String(node.key.value):null;if(!keyName)return;const isApiKeyProp=apiKeyPatterns.some(pattern=>keyName.toLowerCase().includes(pattern.toLowerCase()));if(!isApiKeyProp)return;if(node.value.type==="Literal"&&typeof node.value.value==="string"){const value=node.value.value;if(value===""||value==="YOUR_API_KEY"||value.startsWith("$")){return}if(looksLikeApiKey(value)||value.length>20){context.report({node:node.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}},CallExpression(node){const callee=sourceCode.getText(node.callee);const isProvider=providerFunctions.some(fn=>callee.includes(fn));if(!isProvider)return;const optionsArg=node.arguments[1];if(optionsArg&&optionsArg.type==="ObjectExpression"){for(const prop of optionsArg.properties){if(prop.type!=="Property")continue;const keyName=prop.key.type==="Identifier"?prop.key.name:null;if(keyName==="apiKey"||keyName==="api_key"){if(prop.value.type==="Literal"&&typeof prop.value.value==="string"){const value=prop.value.value;if(looksLikeApiKey(value)||value.length>20){context.report({node:prop.value,messageId:"hardcodedApiKey",data:{key:value.substring(0,10)+"..."}})}}}}}}}}});
|
|
@@ -1,121 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noSensitiveInPrompt = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
const prompt_props_1 = require("../../utils/prompt-props");
|
|
6
|
-
exports.noSensitiveInPrompt = (0, eslint_devkit_1.createRule)({
|
|
7
|
-
name: 'no-sensitive-in-prompt',
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'problem',
|
|
10
|
-
docs: {
|
|
11
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md',
|
|
12
|
-
description: 'Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts',
|
|
13
|
-
cwe: 'CWE-200',
|
|
14
|
-
cvss: 8,
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
sensitiveInPrompt: (0, eslint_devkit_1.formatLLMMessage)({
|
|
18
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
19
|
-
issueName: 'Sensitive Data in LLM Prompt',
|
|
20
|
-
cwe: 'CWE-200',
|
|
21
|
-
owasp: 'A01:2021',
|
|
22
|
-
cvss: 8.0,
|
|
23
|
-
description: 'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',
|
|
24
|
-
severity: 'CRITICAL',
|
|
25
|
-
compliance: ['SOC2', 'GDPR', 'HIPAA', 'PCI-DSS'],
|
|
26
|
-
fix: 'Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)',
|
|
27
|
-
documentationLink: 'https://owasp.org/www-project-top-10-for-large-language-model-applications/',
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
schema: [
|
|
31
|
-
{
|
|
32
|
-
type: 'object',
|
|
33
|
-
properties: {
|
|
34
|
-
sensitivePatterns: {
|
|
35
|
-
type: 'array',
|
|
36
|
-
items: { type: 'string' },
|
|
37
|
-
description: 'Variable patterns that suggest sensitive data',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
additionalProperties: false,
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
defaultOptions: [
|
|
45
|
-
{
|
|
46
|
-
sensitivePatterns: [
|
|
47
|
-
'password', 'secret', 'apiKey', 'api_key', 'token', 'credential',
|
|
48
|
-
'ssn', 'socialSecurity', 'creditCard', 'cardNumber', 'cvv',
|
|
49
|
-
'privateKey', 'private_key', 'accessToken', 'access_token',
|
|
50
|
-
'refreshToken', 'refresh_token', 'authToken', 'bearer',
|
|
51
|
-
'connectionString', 'dbPassword', 'dbUser',
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
create(context) {
|
|
56
|
-
const [options = {}] = context.options;
|
|
57
|
-
const sensitivePatterns = options.sensitivePatterns ?? [
|
|
58
|
-
'password', 'secret', 'apiKey', 'api_key', 'token', 'credential',
|
|
59
|
-
'ssn', 'creditCard', 'privateKey', 'accessToken',
|
|
60
|
-
];
|
|
61
|
-
const sourceCode = context.sourceCode;
|
|
62
|
-
const aiSDKFunctions = ['generateText', 'streamText', 'generateObject', 'streamObject'];
|
|
63
|
-
function isSensitiveIdentifier(name) {
|
|
64
|
-
const lowerName = name.toLowerCase();
|
|
65
|
-
return sensitivePatterns.some((pattern) => lowerName.includes(pattern.toLowerCase()));
|
|
66
|
-
}
|
|
67
|
-
function findSensitiveData(node) {
|
|
68
|
-
if (node.type === 'Identifier' && isSensitiveIdentifier(node.name)) {
|
|
69
|
-
return node.name;
|
|
70
|
-
}
|
|
71
|
-
if (node.type === 'MemberExpression' && node.property.type === 'Identifier') {
|
|
72
|
-
if (isSensitiveIdentifier(node.property.name)) {
|
|
73
|
-
return sourceCode.getText(node);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (node.type === 'TemplateLiteral') {
|
|
77
|
-
for (const expr of node.expressions) {
|
|
78
|
-
const sensitive = findSensitiveData(expr);
|
|
79
|
-
if (sensitive)
|
|
80
|
-
return sensitive;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (node.type === 'BinaryExpression') {
|
|
84
|
-
const leftSensitive = findSensitiveData(node.left);
|
|
85
|
-
if (leftSensitive)
|
|
86
|
-
return leftSensitive;
|
|
87
|
-
const rightSensitive = findSensitiveData(node.right);
|
|
88
|
-
if (rightSensitive)
|
|
89
|
-
return rightSensitive;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
CallExpression(node) {
|
|
95
|
-
const callee = sourceCode.getText(node.callee);
|
|
96
|
-
const isAIFunction = aiSDKFunctions.some(fn => callee.includes(fn));
|
|
97
|
-
if (!isAIFunction)
|
|
98
|
-
return;
|
|
99
|
-
const optionsArg = node.arguments[0];
|
|
100
|
-
if (!optionsArg || optionsArg.type !== 'ObjectExpression')
|
|
101
|
-
return;
|
|
102
|
-
const propsToCheck = new Set(['prompt', 'messages', ...prompt_props_1.SYSTEM_PROMPT_PROPS]);
|
|
103
|
-
for (const prop of optionsArg.properties) {
|
|
104
|
-
if (prop.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
|
|
105
|
-
continue;
|
|
106
|
-
const keyName = (0, prompt_props_1.getStaticPropName)(prop);
|
|
107
|
-
if (!keyName || !propsToCheck.has(keyName))
|
|
108
|
-
continue;
|
|
109
|
-
const sensitiveVar = findSensitiveData(prop.value);
|
|
110
|
-
if (sensitiveVar) {
|
|
111
|
-
context.report({
|
|
112
|
-
node: prop.value,
|
|
113
|
-
messageId: 'sensitiveInPrompt',
|
|
114
|
-
data: { variable: sensitiveVar },
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSensitiveInPrompt=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const prompt_props_1=require("../../utils/prompt-props");exports.noSensitiveInPrompt=(0,eslint_devkit_1.createRule)({name:"no-sensitive-in-prompt",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-vercel-ai-security/docs/rules/no-sensitive-in-prompt.md",description:"Prevent sensitive data (secrets, credentials, PII) from being passed to LLM prompts",cwe:"CWE-200",cvss:8},messages:{sensitiveInPrompt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Sensitive Data in LLM Prompt",cwe:"CWE-200",owasp:"A01:2021",cvss:8,description:'Sensitive data "{{variable}}" detected in prompt. Secrets and PII should never be sent to LLMs.',severity:"CRITICAL",compliance:["SOC2","GDPR","HIPAA","PCI-DSS"],fix:"Remove sensitive data from prompt or redact before sending: prompt: redact(sensitiveData)",documentationLink:"https://owasp.org/www-project-top-10-for-large-language-model-applications/"})},schema:[{type:"object",properties:{sensitivePatterns:{type:"array",items:{type:"string"},description:"Variable patterns that suggest sensitive data"}},additionalProperties:false}]},defaultOptions:[{sensitivePatterns:["password","secret","apiKey","api_key","token","credential","ssn","socialSecurity","creditCard","cardNumber","cvv","privateKey","private_key","accessToken","access_token","refreshToken","refresh_token","authToken","bearer","connectionString","dbPassword","dbUser"]}],create(context){const[options={}]=context.options;const sensitivePatterns=options.sensitivePatterns??["password","secret","apiKey","api_key","token","credential","ssn","creditCard","privateKey","accessToken"];const sourceCode=context.sourceCode;const aiSDKFunctions=["generateText","streamText","generateObject","streamObject"];function isSensitiveIdentifier(name){const lowerName=name.toLowerCase();return sensitivePatterns.some(pattern=>lowerName.includes(pattern.toLowerCase()))}function findSensitiveData(node){if(node.type==="Identifier"&&isSensitiveIdentifier(node.name)){return node.name}if(node.type==="MemberExpression"&&node.property.type==="Identifier"){if(isSensitiveIdentifier(node.property.name)){return sourceCode.getText(node)}}if(node.type==="TemplateLiteral"){for(const expr of node.expressions){const sensitive=findSensitiveData(expr);if(sensitive)return sensitive}}if(node.type==="BinaryExpression"){const leftSensitive=findSensitiveData(node.left);if(leftSensitive)return leftSensitive;const rightSensitive=findSensitiveData(node.right);if(rightSensitive)return rightSensitive}return null}return{CallExpression(node){const callee=sourceCode.getText(node.callee);const isAIFunction=aiSDKFunctions.some(fn=>callee.includes(fn));if(!isAIFunction)return;const optionsArg=node.arguments[0];if(!optionsArg||optionsArg.type!=="ObjectExpression")return;const propsToCheck=new Set(["prompt","messages",...prompt_props_1.SYSTEM_PROMPT_PROPS]);for(const prop of optionsArg.properties){if(prop.type!==eslint_devkit_1.AST_NODE_TYPES.Property)continue;const keyName=(0,prompt_props_1.getStaticPropName)(prop);if(!keyName||!propsToCheck.has(keyName))continue;const sensitiveVar=findSensitiveData(prop.value);if(sensitiveVar){context.report({node:prop.value,messageId:"sensitiveInPrompt",data:{variable:sensitiveVar}})}}}}}});
|