@purplesquirrel/guardrails-mcp-server 1.0.0
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/.github/dependabot.yml +21 -0
- package/.github/workflows/ci.yml +36 -0
- package/LICENSE +21 -0
- package/README.md +257 -0
- package/TROUBLESHOOTING.md +299 -0
- package/package.json +26 -0
- package/src/audit/AuditLogger.js +340 -0
- package/src/engine/GuardrailsEngine.js +305 -0
- package/src/filters/OutputFilter.js +296 -0
- package/src/policies/PolicyEngine.js +337 -0
- package/src/validators/InputValidator.js +291 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InputValidator - Validates and sanitizes incoming requests
|
|
3
|
+
*
|
|
4
|
+
* Checks for:
|
|
5
|
+
* - Prompt injection attempts
|
|
6
|
+
* - Malicious code patterns
|
|
7
|
+
* - PII/sensitive data
|
|
8
|
+
* - Content length limits
|
|
9
|
+
* - Blocked keywords/patterns
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class InputValidator {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
|
|
16
|
+
// Prompt injection patterns
|
|
17
|
+
this.injectionPatterns = [
|
|
18
|
+
/ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?|rules?)/i,
|
|
19
|
+
/disregard\s+(all\s+)?(previous|prior|above)/i,
|
|
20
|
+
/forget\s+(everything|all|your)\s+(you|instructions?|rules?)/i,
|
|
21
|
+
/you\s+are\s+now\s+(a|an|the)\s+/i,
|
|
22
|
+
/pretend\s+(you\s+are|to\s+be)/i,
|
|
23
|
+
/act\s+as\s+(if|though|a|an)/i,
|
|
24
|
+
/jailbreak/i,
|
|
25
|
+
/DAN\s+mode/i,
|
|
26
|
+
/developer\s+mode\s+(enabled|on|activated)/i,
|
|
27
|
+
/bypass\s+(safety|security|filters?|guardrails?)/i,
|
|
28
|
+
/override\s+(system|safety|security)/i,
|
|
29
|
+
/\[SYSTEM\]|\[ADMIN\]|\[ROOT\]/i,
|
|
30
|
+
/```system|```admin/i,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Malicious code patterns
|
|
34
|
+
this.codePatterns = [
|
|
35
|
+
/eval\s*\(/i,
|
|
36
|
+
/exec\s*\(/i,
|
|
37
|
+
/system\s*\(/i,
|
|
38
|
+
/subprocess/i,
|
|
39
|
+
/os\.system/i,
|
|
40
|
+
/child_process/i,
|
|
41
|
+
/spawn\s*\(/i,
|
|
42
|
+
/import\s+os\b/i,
|
|
43
|
+
/require\s*\(\s*['"]child_process['"]\s*\)/i,
|
|
44
|
+
/__import__\s*\(/i,
|
|
45
|
+
/\bsh\s+-c\b/i,
|
|
46
|
+
/\bbash\s+-c\b/i,
|
|
47
|
+
/;\s*rm\s+-rf/i,
|
|
48
|
+
/&&\s*rm\s+-rf/i,
|
|
49
|
+
/\|\s*sh\b/i,
|
|
50
|
+
/\$\(.*\)/,
|
|
51
|
+
/`.*`/,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// PII patterns
|
|
55
|
+
this.piiPatterns = [
|
|
56
|
+
{ name: 'SSN', pattern: /\b\d{3}-\d{2}-\d{4}\b/ },
|
|
57
|
+
{ name: 'CreditCard', pattern: /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/ },
|
|
58
|
+
{ name: 'Email', pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g },
|
|
59
|
+
{ name: 'Phone', pattern: /\b(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/ },
|
|
60
|
+
{ name: 'IPAddress', pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/ },
|
|
61
|
+
{ name: 'APIKey', pattern: /\b(?:sk-|pk-|api[_-]?key[_-]?)[a-zA-Z0-9]{20,}\b/i },
|
|
62
|
+
{ name: 'AWSKey', pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
63
|
+
{ name: 'Password', pattern: /\b(?:password|passwd|pwd)\s*[:=]\s*\S+/i },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Blocked keywords (configurable)
|
|
67
|
+
this.blockedKeywords = config.blockedKeywords || [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate an incoming request
|
|
72
|
+
* @param {Object} request - The request to validate
|
|
73
|
+
* @param {Object} context - Additional context
|
|
74
|
+
* @returns {Object} - Validation result
|
|
75
|
+
*/
|
|
76
|
+
async validate(request, context = {}) {
|
|
77
|
+
const violations = [];
|
|
78
|
+
let sanitizedRequest = JSON.parse(JSON.stringify(request));
|
|
79
|
+
|
|
80
|
+
// Extract text content from request
|
|
81
|
+
const textContent = this.extractTextContent(request);
|
|
82
|
+
|
|
83
|
+
// 1. Check content length
|
|
84
|
+
if (textContent.length > (this.config.maxInputLength || 100000)) {
|
|
85
|
+
violations.push({
|
|
86
|
+
type: 'LENGTH_EXCEEDED',
|
|
87
|
+
message: `Input exceeds maximum length of ${this.config.maxInputLength || 100000} characters`,
|
|
88
|
+
severity: 'high',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 2. Check for prompt injection
|
|
93
|
+
for (const pattern of this.injectionPatterns) {
|
|
94
|
+
if (pattern.test(textContent)) {
|
|
95
|
+
violations.push({
|
|
96
|
+
type: 'PROMPT_INJECTION',
|
|
97
|
+
message: 'Potential prompt injection detected',
|
|
98
|
+
pattern: pattern.toString(),
|
|
99
|
+
severity: 'critical',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 3. Check for malicious code patterns
|
|
105
|
+
for (const pattern of this.codePatterns) {
|
|
106
|
+
if (pattern.test(textContent)) {
|
|
107
|
+
violations.push({
|
|
108
|
+
type: 'MALICIOUS_CODE',
|
|
109
|
+
message: 'Potentially malicious code pattern detected',
|
|
110
|
+
pattern: pattern.toString(),
|
|
111
|
+
severity: 'high',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 4. Check for PII (if configured to block)
|
|
117
|
+
if (this.config.blockPII) {
|
|
118
|
+
for (const pii of this.piiPatterns) {
|
|
119
|
+
if (pii.pattern.test(textContent)) {
|
|
120
|
+
violations.push({
|
|
121
|
+
type: 'PII_DETECTED',
|
|
122
|
+
message: `${pii.name} pattern detected in input`,
|
|
123
|
+
severity: 'medium',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 5. Check blocked keywords
|
|
130
|
+
for (const keyword of this.blockedKeywords) {
|
|
131
|
+
const regex = typeof keyword === 'string'
|
|
132
|
+
? new RegExp(keyword, 'i')
|
|
133
|
+
: keyword;
|
|
134
|
+
if (regex.test(textContent)) {
|
|
135
|
+
violations.push({
|
|
136
|
+
type: 'BLOCKED_KEYWORD',
|
|
137
|
+
message: 'Blocked keyword detected',
|
|
138
|
+
severity: 'high',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 6. Check custom patterns from config
|
|
144
|
+
if (this.config.blockedPatterns) {
|
|
145
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
146
|
+
const regex = typeof pattern === 'string'
|
|
147
|
+
? new RegExp(pattern, 'i')
|
|
148
|
+
: pattern;
|
|
149
|
+
if (regex.test(textContent)) {
|
|
150
|
+
violations.push({
|
|
151
|
+
type: 'BLOCKED_PATTERN',
|
|
152
|
+
message: 'Blocked pattern detected',
|
|
153
|
+
severity: 'high',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 7. Sanitize if configured
|
|
160
|
+
if (this.config.sanitizeInput) {
|
|
161
|
+
sanitizedRequest = this.sanitize(sanitizedRequest);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Determine if valid based on violations
|
|
165
|
+
const criticalViolations = violations.filter(v => v.severity === 'critical');
|
|
166
|
+
const highViolations = violations.filter(v => v.severity === 'high');
|
|
167
|
+
|
|
168
|
+
const valid = criticalViolations.length === 0 &&
|
|
169
|
+
(this.config.allowHighSeverity || highViolations.length === 0);
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
valid,
|
|
173
|
+
violations,
|
|
174
|
+
sanitizedRequest: valid ? sanitizedRequest : null,
|
|
175
|
+
stats: {
|
|
176
|
+
criticalCount: criticalViolations.length,
|
|
177
|
+
highCount: highViolations.length,
|
|
178
|
+
mediumCount: violations.filter(v => v.severity === 'medium').length,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extract text content from various request formats
|
|
185
|
+
*/
|
|
186
|
+
extractTextContent(request) {
|
|
187
|
+
if (typeof request === 'string') {
|
|
188
|
+
return request;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const parts = [];
|
|
192
|
+
|
|
193
|
+
// Handle common request structures
|
|
194
|
+
if (request.prompt) parts.push(request.prompt);
|
|
195
|
+
if (request.input) parts.push(request.input);
|
|
196
|
+
if (request.text) parts.push(request.text);
|
|
197
|
+
if (request.content) parts.push(request.content);
|
|
198
|
+
if (request.message) parts.push(request.message);
|
|
199
|
+
if (request.query) parts.push(request.query);
|
|
200
|
+
|
|
201
|
+
// Handle messages array (chat format)
|
|
202
|
+
if (Array.isArray(request.messages)) {
|
|
203
|
+
for (const msg of request.messages) {
|
|
204
|
+
if (msg.content) parts.push(msg.content);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Handle arguments object
|
|
209
|
+
if (request.arguments) {
|
|
210
|
+
parts.push(JSON.stringify(request.arguments));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return parts.join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Sanitize request content
|
|
218
|
+
*/
|
|
219
|
+
sanitize(request) {
|
|
220
|
+
if (typeof request === 'string') {
|
|
221
|
+
return this.sanitizeString(request);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const sanitized = { ...request };
|
|
225
|
+
|
|
226
|
+
// Sanitize common fields
|
|
227
|
+
const fieldsToSanitize = ['prompt', 'input', 'text', 'content', 'message', 'query'];
|
|
228
|
+
for (const field of fieldsToSanitize) {
|
|
229
|
+
if (sanitized[field] && typeof sanitized[field] === 'string') {
|
|
230
|
+
sanitized[field] = this.sanitizeString(sanitized[field]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Handle messages array
|
|
235
|
+
if (Array.isArray(sanitized.messages)) {
|
|
236
|
+
sanitized.messages = sanitized.messages.map(msg => ({
|
|
237
|
+
...msg,
|
|
238
|
+
content: msg.content ? this.sanitizeString(msg.content) : msg.content,
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return sanitized;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sanitize a string
|
|
247
|
+
*/
|
|
248
|
+
sanitizeString(str) {
|
|
249
|
+
let result = str;
|
|
250
|
+
|
|
251
|
+
// Remove null bytes
|
|
252
|
+
result = result.replace(/\0/g, '');
|
|
253
|
+
|
|
254
|
+
// Normalize unicode
|
|
255
|
+
result = result.normalize('NFC');
|
|
256
|
+
|
|
257
|
+
// Remove control characters (except newlines and tabs)
|
|
258
|
+
result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update configuration
|
|
265
|
+
*/
|
|
266
|
+
updateConfig(config) {
|
|
267
|
+
this.config = { ...this.config, ...config };
|
|
268
|
+
if (config.blockedKeywords) {
|
|
269
|
+
this.blockedKeywords = config.blockedKeywords;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Add a custom injection pattern
|
|
275
|
+
*/
|
|
276
|
+
addInjectionPattern(pattern) {
|
|
277
|
+
if (typeof pattern === 'string') {
|
|
278
|
+
pattern = new RegExp(pattern, 'i');
|
|
279
|
+
}
|
|
280
|
+
this.injectionPatterns.push(pattern);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Add a custom PII pattern
|
|
285
|
+
*/
|
|
286
|
+
addPIIPattern(name, pattern) {
|
|
287
|
+
this.piiPatterns.push({ name, pattern });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default InputValidator;
|