/**
* Custom Prompt Validation & Security
*
* Validates user-provided custom prompts to prevent:
* - Prompt injection attacks
* - Token/cost abuse (length limits)
* - Jailbreak attempts
* - Impersonation attempts
*/
export interface ValidationResult {
valid: boolean;
reason?: string;
sanitized?: string;
}
// Maximum allowed length for custom prompts
export const MAX_PROMPT_LENGTH = 2000;
// Minimum time between prompt updates (in milliseconds)
export const PROMPT_UPDATE_COOLDOWN_MS = 60 * 1000; // 1 minute
/**
* Patterns that indicate potential prompt injection attacks
* These are case-insensitive and use word boundaries where appropriate
*/
const BLOCKED_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
// Direct instruction override attempts
{
pattern: /ignore\s+(?:all\s+)?(?:previous|above|prior|earlier|system|safety)\s+(?:instructions?|rules?|guidelines?|prompts?)/i,
reason: 'Attempting to override system instructions is not allowed',
},
{
pattern: /disregard\s+(?:all\s+)?(?:previous|above|prior|earlier|system|safety)/i,
reason: 'Attempting to override system instructions is not allowed',
},
{
pattern: /forget\s+(?:all\s+)?(?:previous|above|prior|earlier|your)\s+(?:instructions?|rules?|training|guidelines?)/i,
reason: 'Attempting to override system instructions is not allowed',
},
// Persona/identity manipulation
{
pattern: /you\s+are\s+now\s+(?:a|an|the)?\s*(?!helping|assisting|here to)/i,
reason: 'Attempting to change AI persona is not allowed',
},
{
pattern: /(?:new|different|changed?)\s+(?:persona|identity|character|role)/i,
reason: 'Attempting to change AI persona is not allowed',
},
{
pattern: /(?:act|behave|pretend|roleplay)\s+(?:as|like)\s+(?:a|an|the)?\s*(?:different|new|another)/i,
reason: 'Attempting to change AI persona is not allowed',
},
// Known jailbreak terms
{
pattern: /\b(?:jailbreak|DAN\s*mode|developer\s*mode|god\s*mode|unrestricted\s*mode)\b/i,
reason: 'Known jailbreak attempt detected',
},
{
pattern: /\b(?:do\s+anything\s+now|bypass\s+(?:filters?|safety|restrictions?))\b/i,
reason: 'Known jailbreak attempt detected',
},
// System prompt extraction
{
pattern: /(?:reveal|show|display|output|print|repeat)\s+(?:your|the|system)\s+(?:system\s+)?(?:prompt|instructions?|rules?)/i,
reason: 'Attempting to extract system instructions is not allowed',
},
{
pattern: /what\s+(?:are|is)\s+your\s+(?:system\s+)?(?:prompt|instructions?|rules?|guidelines?)/i,
reason: 'Attempting to extract system instructions is not allowed',
},
// Impersonation attempts
{
pattern: /(?:you\s+are|i\s+am|this\s+is)\s+(?:the\s+)?(?:official|government|prime\s+minister|minister|mp\b|member\s+of\s+parliament)/i,
reason: 'Impersonating government officials is not allowed',
},
{
pattern: /speak(?:ing)?\s+(?:on\s+behalf\s+of|for)\s+(?:the\s+)?(?:government|parliament|canada)/i,
reason: 'Impersonating government officials is not allowed',
},
// Data exfiltration attempts
{
pattern: /(?:tell|show|give)\s+me\s+(?:about\s+)?other\s+users?/i,
reason: 'Attempting to access other users\' data is not allowed',
},
{
pattern: /(?:api|secret|private)\s*keys?/i,
reason: 'Attempting to extract sensitive information is not allowed',
},
// Instruction to always/never do something dangerous
{
pattern: /always\s+(?:say|claim|state|assert)\s+(?:that\s+)?(?:everything|all\s+information)\s+is\s+(?:true|verified|accurate|fact)/i,
reason: 'Forcing false verification claims is not allowed',
},
{
pattern: /never\s+(?:say|admit|acknowledge)\s+(?:you\s+(?:are|were)\s+)?(?:wrong|uncertain|unsure|an?\s+ai)/i,
reason: 'Suppressing AI transparency is not allowed',
},
];
/**
* Characters/strings that should be stripped or escaped
*/
const SANITIZE_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
// Remove excessive whitespace
{ pattern: /\s{10,}/g, replacement: ' ' },
// Remove null bytes
{ pattern: /\0/g, replacement: '' },
// Remove control characters (except newlines and tabs)
{ pattern: /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, replacement: '' },
];
/**
* Validate and sanitize a custom prompt
*/
export function validateCustomPrompt(prompt: string | null | undefined): ValidationResult {
// Handle null/undefined/empty
if (!prompt || prompt.trim().length === 0) {
return { valid: true, sanitized: '' };
}
// Sanitize first
let sanitized = prompt;
for (const { pattern, replacement } of SANITIZE_PATTERNS) {
sanitized = sanitized.replace(pattern, replacement);
}
sanitized = sanitized.trim();
// Check length
if (sanitized.length > MAX_PROMPT_LENGTH) {
return {
valid: false,
reason: `Prompt too long. Maximum ${MAX_PROMPT_LENGTH} characters allowed (yours: ${sanitized.length})`,
};
}
// Check for blocked patterns
for (const { pattern, reason } of BLOCKED_PATTERNS) {
if (pattern.test(sanitized)) {
return { valid: false, reason };
}
}
return { valid: true, sanitized };
}
/**
* Check if user is within rate limit for prompt updates
* Returns true if allowed, false if rate limited
*/
export async function checkPromptUpdateRateLimit(
supabase: any,
userId: string
): Promise<{ allowed: boolean; retryAfterMs?: number }> {
// Check last prompt update time from audit log
const { data } = await supabase
.from('prompt_audit_log')
.select('created_at')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.limit(1)
.maybeSingle();
if (!data) {
return { allowed: true };
}
const lastUpdate = new Date(data.created_at).getTime();
const now = Date.now();
const elapsed = now - lastUpdate;
if (elapsed < PROMPT_UPDATE_COOLDOWN_MS) {
return {
allowed: false,
retryAfterMs: PROMPT_UPDATE_COOLDOWN_MS - elapsed,
};
}
return { allowed: true };
}
/**
* Log a prompt update for audit purposes
*/
export async function logPromptUpdate(
supabase: any,
userId: string,
oldPrompt: string | null,
newPrompt: string,
ipAddress?: string,
userAgent?: string
): Promise<void> {
try {
await supabase.from('prompt_audit_log').insert({
user_id: userId,
action: oldPrompt ? 'update' : 'create',
old_value: oldPrompt,
new_value: newPrompt,
ip_address: ipAddress,
user_agent: userAgent,
});
} catch (error) {
// Log but don't fail the request if audit logging fails
console.error('Failed to log prompt update:', error);
}
}
/**
* Get validation error message suitable for display to users
*/
export function getUserFriendlyError(reason: string): string {
// Make technical reasons more user-friendly
if (reason.includes('override system instructions')) {
return 'Your custom instructions cannot override the assistant\'s core behavior. Please rephrase your instructions.';
}
if (reason.includes('change AI persona')) {
return 'Custom instructions cannot change Gordie\'s identity. You can adjust how Gordie responds, but not who Gordie is.';
}
if (reason.includes('jailbreak')) {
return 'This type of instruction is not allowed. Please use normal, constructive instructions.';
}
if (reason.includes('extract system instructions')) {
return 'Instructions asking the assistant to reveal its configuration are not allowed.';
}
if (reason.includes('Impersonating')) {
return 'Instructions that could make responses appear to come from official government sources are not allowed.';
}
if (reason.includes('other users')) {
return 'Instructions related to other users\' information are not allowed.';
}
return reason;
}