@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
267 lines • 9.7 kB
JavaScript
/**
* Security Input Validation Framework
*
* Provides comprehensive input validation and sanitization to prevent:
* - Tool Poisoning Attacks (TPAs)
* - Command Injection
* - Prompt Injection
* - SQL Injection
* - Path Traversal
*/
export const DEFAULT_SECURITY_CONFIG = {
maxInputLength: 10000,
maxPromptLength: 50000,
allowedCommands: [
'consensus', 'cons', 'capture', 'cap',
'list_profiles', 'profiles', 'lp',
'get_profile', 'profile', 'gp',
'update_profile', 'up',
'list_providers', 'providers', 'lprov',
'configure_provider', 'config_provider', 'cp',
'test_providers', 'test', 'tp',
'list_pipeline_profiles', 'pipelines', 'lpp',
'configure_pipeline', 'config_pipeline', 'cpp',
'set_default_profile', 'default_profile', 'sdp',
'update_model_registry', 'update_registry', 'umr',
'add_custom_model', 'add_model', 'acm',
'list_models', 'models', 'lm'
],
bannedPatterns: [
// Command injection patterns
/[;&|`$(){}[\]\\]/g,
/\$\(/g,
/\$\{/g,
/`[^`]*`/g,
// File system patterns
/\.\.\//g,
/~\//g,
/\/etc\//g,
/\/proc\//g,
/\/sys\//g,
// Network patterns
/curl\s+/i,
/wget\s+/i,
/nc\s+/i,
/netcat\s+/i,
// Script execution patterns
/eval\s*\(/i,
/exec\s*\(/i,
/system\s*\(/i,
/shell_exec\s*\(/i,
// Hidden instruction patterns (Tool Poisoning)
/<!--.*-->/gs,
/<script.*<\/script>/gis,
/\[HIDDEN\]/gi,
/\[SYSTEM\]/gi,
/\[ADMIN\]/gi,
/IGNORE PREVIOUS INSTRUCTIONS/gi,
/DISREGARD.*ABOVE/gi,
/EXECUTE.*COMMAND/gi,
// SQL injection patterns
/union\s+select/gi,
/drop\s+table/gi,
/delete\s+from/gi,
/insert\s+into/gi,
/update\s+set/gi,
// Environment variable access
/\$HOME/g,
/\$PATH/g,
/\$USER/g,
/process\.env/g,
// Credential patterns
/sk-[a-zA-Z0-9]{48}/g, // OpenAI API keys
/^xoxb-/g, // Slack tokens
/^ghp_/g, // GitHub tokens
/^ghs_/g, // GitHub tokens
/^gho_/g, // GitHub tokens
/^ghu_/g, // GitHub tokens
/aws_access_key_id/gi,
/aws_secret_access_key/gi,
],
urlPattern: /^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/,
filePathPattern: /^[a-zA-Z0-9\/_\-\.]+$/
};
export class SecurityValidationError extends Error {
code;
constructor(message, code) {
super(message);
this.code = code;
this.name = 'SecurityValidationError';
}
}
export class InputValidator {
config;
constructor(config = DEFAULT_SECURITY_CONFIG) {
this.config = config;
}
/**
* Validate and sanitize general text input
*/
validateTextInput(input, context = 'general') {
if (!input) {
throw new SecurityValidationError('Input cannot be empty', 'EMPTY_INPUT');
}
// Length validation
const maxLength = context === 'prompt' ? this.config.maxPromptLength : this.config.maxInputLength;
if (input.length > maxLength) {
throw new SecurityValidationError(`Input too long: ${input.length} > ${maxLength}`, 'INPUT_TOO_LONG');
}
// Check for banned patterns
for (const pattern of this.config.bannedPatterns) {
if (pattern.test(input)) {
throw new SecurityValidationError(`Suspicious pattern detected: ${pattern.source}`, 'SUSPICIOUS_PATTERN');
}
}
return this.sanitizeInput(input);
}
/**
* Validate command strings for shorthand commands
*/
validateCommand(command) {
const cleanCommand = command.trim().toLowerCase();
if (!this.config.allowedCommands.includes(cleanCommand)) {
throw new SecurityValidationError(`Command not allowed: ${command}`, 'INVALID_COMMAND');
}
return cleanCommand;
}
/**
* Validate and sanitize URLs
*/
validateUrl(url) {
if (!url) {
throw new SecurityValidationError('URL cannot be empty', 'EMPTY_URL');
}
// Basic format validation
if (!this.config.urlPattern.test(url)) {
throw new SecurityValidationError(`Invalid URL format: ${url}`, 'INVALID_URL_FORMAT');
}
// Check for dangerous protocols
const lowercaseUrl = url.toLowerCase();
if (lowercaseUrl.startsWith('file://') ||
lowercaseUrl.startsWith('ftp://') ||
lowercaseUrl.startsWith('javascript:') ||
lowercaseUrl.startsWith('data:')) {
throw new SecurityValidationError(`Dangerous URL protocol: ${url}`, 'DANGEROUS_PROTOCOL');
}
// Validate domain allowlist for API endpoints
const allowedDomains = [
'api.openai.com',
'api.anthropic.com',
'generativelanguage.googleapis.com',
'api.x.ai',
'hive-api.hivetechs.io'
];
try {
const urlObj = new URL(url);
const domain = urlObj.hostname.toLowerCase();
const isAllowed = allowedDomains.some(allowedDomain => domain === allowedDomain || domain.endsWith('.' + allowedDomain));
if (!isAllowed) {
throw new SecurityValidationError(`Domain not allowed: ${domain}`, 'DOMAIN_NOT_ALLOWED');
}
}
catch (error) {
if (error instanceof SecurityValidationError) {
throw error;
}
throw new SecurityValidationError(`Invalid URL: ${url}`, 'INVALID_URL');
}
return url;
}
/**
* Validate file paths to prevent path traversal
*/
validateFilePath(path) {
if (!path) {
throw new SecurityValidationError('Path cannot be empty', 'EMPTY_PATH');
}
// Check for path traversal patterns
if (path.includes('..') || path.includes('~') || path.startsWith('/')) {
throw new SecurityValidationError(`Dangerous path pattern: ${path}`, 'DANGEROUS_PATH');
}
// Validate path format
if (!this.config.filePathPattern.test(path)) {
throw new SecurityValidationError(`Invalid path format: ${path}`, 'INVALID_PATH_FORMAT');
}
return path;
}
/**
* Validate conversation IDs (UUIDs)
*/
validateConversationId(id) {
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidPattern.test(id)) {
throw new SecurityValidationError(`Invalid conversation ID format: ${id}`, 'INVALID_CONVERSATION_ID');
}
return id;
}
/**
* Validate API keys to prevent exposure
*/
validateApiKey(key) {
if (!key) {
throw new SecurityValidationError('API key cannot be empty', 'EMPTY_API_KEY');
}
// Length validation (most API keys are between 20-200 characters)
if (key.length < 10 || key.length > 300) {
throw new SecurityValidationError(`Invalid API key length: ${key.length}`, 'INVALID_API_KEY_LENGTH');
}
// Check for common API key patterns (more flexible for real world usage)
const keyPatterns = [
/^sk-[a-zA-Z0-9\-_]{20,}$/, // OpenAI (flexible length)
/^sk-ant-[a-zA-Z0-9\-_]{50,}$/, // Anthropic (sk-ant- prefix, flexible)
/^AIzaS[a-zA-Z0-9\-_]{20,}$/, // Google (AIzaS prefix, flexible)
/^xai-[a-zA-Z0-9\-_]{20,}$/, // xAI (flexible length)
/^gsk_[a-zA-Z0-9\-_]{20,}$/, // Google alternative format
/^[a-zA-Z0-9\-_\.]{20,300}$/ // Generic pattern (very flexible for any provider)
];
const isValidPattern = keyPatterns.some(pattern => pattern.test(key));
if (!isValidPattern) {
throw new SecurityValidationError('Invalid API key format', 'INVALID_API_KEY_FORMAT');
}
return key;
}
/**
* Sanitize input by removing/escaping dangerous characters
*/
sanitizeInput(input) {
return input
// Remove null bytes
.replace(/\0/g, '')
// Remove or escape HTML/XML tags
.replace(/<[^>]*>/g, '')
// Remove invisible characters
.replace(/[\u200B-\u200D\uFEFF]/g, '')
// Normalize whitespace
.replace(/\s+/g, ' ')
.trim();
}
/**
* Validate prompt specifically for AI models to prevent injection
*/
validatePrompt(prompt) {
// Use text validation first
const validatedPrompt = this.validateTextInput(prompt, 'prompt');
// Additional prompt-specific validations
const promptInjectionPatterns = [
/ignore\s+previous\s+instructions/gi,
/disregard\s+.+above/gi,
/forget\s+everything/gi,
/new\s+instructions/gi,
/system\s*:\s*/gi,
/assistant\s*:\s*/gi,
/\[\/INST\]/gi,
/\<\|system\|\>/gi,
/\<\|user\|\>/gi,
/\<\|assistant\|\>/gi,
];
for (const pattern of promptInjectionPatterns) {
if (pattern.test(validatedPrompt)) {
throw new SecurityValidationError('Prompt injection pattern detected', 'PROMPT_INJECTION');
}
}
return validatedPrompt;
}
}
// Export default instance
export const defaultValidator = new InputValidator();
//# sourceMappingURL=input-validator.js.map