UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

303 lines 10.5 kB
/** * ValidationService - Centralized validation and sanitization for DollhouseMCP * * Eliminates duplicate validation code across element managers by providing * a unified security-first validation approach. * * Key Features: * - Security-first pattern: validate → sanitize → re-validate * - Wraps existing validators (UnicodeValidator, ContentValidator, sanitizeInput) * - Consistent validation algorithm across all element types * - Comprehensive error reporting and logging * - Full TypeScript type safety * * Security Pattern: * The service enforces the correct validation sequence to prevent security bypasses: * 1. VALIDATE input against pattern/rules (reject malicious input) * 2. SANITIZE if validation passes (clean up input) * 3. RE-VALIDATE sanitized output (ensure sanitization didn't break constraints) * * WRONG (common bug): * const sanitized = sanitizeInput(category); * metadata.category = sanitized.toLowerCase(); // No validation! * * CORRECT (what this service enforces): * if (!isValidCategory(category)) throw error; // Validate FIRST * const sanitized = sanitizeInput(category); // Then sanitize * if (!isValidCategory(sanitized)) throw error; // Re-validate * * @example * ```typescript * import { validationService } from './services/ValidationService'; * * // High-level coordinated validation * const result = validationService.validateAndSanitizeInput(userInput, { * maxLength: 200, * allowSpaces: true * }); * * if (!result.isValid) { * console.error(result.errors); * } else { * use(result.sanitizedValue); * } * * // Metadata field validation * const categoryResult = validationService.validateCategory('creative'); * const usernameResult = validationService.validateUsername('john-doe'); * const emailResult = validationService.validateEmail('user@example.com'); * ``` */ import type { ContentValidatorOptions, ContentValidationResult } from '../../security/contentValidator.js'; /** * Field types for validation with appropriate strictness levels */ export type ValidationFieldType = 'name' | 'description' | 'content' | 'filename'; /** * Options for input validation */ export interface InputValidationOptions { /** Maximum length of input (default: 1000) */ maxLength?: number; /** Allow spaces in input (default: false) */ allowSpaces?: boolean; /** Custom validation pattern (overrides default) */ customPattern?: RegExp; /** Skip Unicode normalization (default: false) */ skipUnicode?: boolean; /** Field type for appropriate validation strictness (default: validates based on allowSpaces) */ fieldType?: ValidationFieldType; } /** * Options for metadata field validation */ export interface FieldValidationOptions { /** Whether field is required (default: true) */ required?: boolean; /** Maximum field length (default: 500) */ maxLength?: number; /** Custom validation pattern */ pattern?: RegExp; } /** * Generic validation result */ export interface ValidationResult { /** Whether the input passed all validation checks */ isValid: boolean; /** Sanitized and validated value (only present if isValid is true) */ sanitizedValue?: string; /** List of validation errors (only present if isValid is false) */ errors?: string[]; /** List of validation warnings (non-fatal issues) */ warnings?: string[]; } /** * Field-specific validation result with additional metadata */ export interface FieldValidationResult extends ValidationResult { /** Original field name that was validated */ fieldName?: string; /** Whether the field was empty before validation */ wasEmpty?: boolean; } /** * Result from Unicode validation */ export interface UnicodeValidationResult { /** Whether the Unicode content is valid */ isValid: boolean; /** Normalized Unicode content */ normalizedContent: string; /** Detected Unicode issues */ detectedIssues?: string[]; /** Severity of detected issues */ severity?: 'low' | 'medium' | 'high' | 'critical'; } /** * Service for validating and sanitizing input across all element types * * This service provides centralized validation to eliminate code duplication * and enforce the security-first validation pattern across the codebase. */ export declare class ValidationService { /** * Validate and sanitize general input with sanitize-then-validate approach * * Validation process: * 1. Check for empty/null input * 2. SANITIZE input first (remove control chars, shell metacharacters, dangerous patterns) * 3. Normalize Unicode if enabled (convert confusables, remove zero-width) * 4. VALIDATE the cleaned result against field-appropriate pattern * 5. Log any security issues detected * * This approach allows legitimate Unicode content (like "Cafe Assistant") to pass * while still blocking security threats after they've been normalized/sanitized. * * @param input - Raw input string to validate * @param options - Validation options * @returns Validation result with sanitized value or errors * * @example * ```typescript * const result = service.validateAndSanitizeInput('user-input', { * maxLength: 100, * allowSpaces: true, * fieldType: 'name' * }); * * if (result.isValid) { * console.log('Clean input:', result.sanitizedValue); * } else { * console.error('Validation failed:', result.errors); * } * ``` */ validateAndSanitizeInput(input: string, options?: InputValidationOptions): ValidationResult; /** * Get the appropriate validation pattern for a field type * * @param options - Input validation options * @returns RegExp pattern appropriate for the field type */ private getPatternForField; /** * Validate a metadata field with sanitize-then-validate approach * * @param fieldName - Name of the field being validated * @param value - Field value to validate * @param options - Field validation options * @returns Field validation result * * @example * ```typescript * const result = service.validateMetadataField('category', 'creative', { * required: true, * maxLength: 50 * }); * ``` */ validateMetadataField(fieldName: string, value: unknown, options?: FieldValidationOptions): FieldValidationResult; /** * Validate a category field * * Enforces security-first pattern: * 1. Validate format (lowercase letters and hyphens only) * 2. Sanitize input * 3. Re-validate sanitized output * * @param category - Category string to validate * @returns Validation result with sanitized category * * @example * ```typescript * const result = service.validateCategory('creative'); * if (result.isValid) { * metadata.category = result.sanitizedValue; * } * ``` */ validateCategory(category: string): ValidationResult; /** * Validate a username field * * @param username - Username string to validate * @returns Validation result with sanitized username * * @example * ```typescript * const result = service.validateUsername('john-doe'); * ``` */ validateUsername(username: string): ValidationResult; /** * Validate an email address * * @param email - Email address to validate * @returns Validation result with sanitized email * * @example * ```typescript * const result = service.validateEmail('user@example.com'); * ``` */ validateEmail(email: string): ValidationResult; /** * Normalize Unicode content using UnicodeValidator * * Delegates to existing UnicodeValidator.normalize() to prevent * homograph attacks, direction override attacks, and other Unicode-based bypasses. * * @param text - Text to normalize * @returns Unicode validation result with normalized content * * @example * ```typescript * const result = service.normalizeUnicode('text with unicode'); * console.log(result.normalizedContent); * ``` */ normalizeUnicode(text: string): UnicodeValidationResult; /** * Validate content for security threats using ContentValidator * * Delegates to existing ContentValidator.validateAndSanitize() to detect * prompt injection, YAML bombs, command injection, and other content-based attacks. * * @param content - Content to validate * @param options - Content validation options * @returns Content validation result * * @example * ```typescript * const result = service.validateContent(personaContent, { * skipSizeCheck: false * }); * * if (!result.isValid) { * console.error('Security threat detected:', result.detectedPatterns); * } * ``` */ validateContent(content: string, options?: ContentValidatorOptions): ContentValidationResult; /** * Find characters in `input` that do not match `charTest`. * Returns deduplicated list of invalid characters. */ private findInvalidCharacters; /** * Format a list of characters for display in error messages. * Labels spaces and non-printable characters; caps output at 5 unique chars. */ private formatCharList; /** * Resolve which PATTERN_DESCRIPTIONS entry applies for InputValidationOptions. * Mirrors the logic of `getPatternForField`. */ private getPatternDescription; /** * Reverse-lookup from a RegExp instance to its PATTERN_DESCRIPTIONS entry. * Compares by source+flags. Returns undefined for unknown patterns. */ private getDescriptionForPattern; } /** * Singleton instance of ValidationService * * Use this instance throughout the application to ensure consistent * validation behavior across all element managers. * * @example * ```typescript * import { validationService } from './services/ValidationService'; * * // In any element manager: * const result = validationService.validateCategory(metadata.category); * if (!result.isValid) { * throw new Error(result.errors.join(', ')); * } * metadata.category = result.sanitizedValue; * ``` */ export declare const validationService: ValidationService; //# sourceMappingURL=ValidationService.d.ts.map