/**
 * Input Protection System - Guards against remote attacks on chat input
 *
 * Protects against:
 * 1. Remote stdin injection attacks
 * 2. Automated paste attacks (superhuman typing speed)
 * 3. Escape sequence injection via clipboard
 * 4. Buffer overflow attempts (massive pastes)
 * 5. Control character injection
 * 6. Unicode-based attacks (homoglyphs, RTL override)
 * 7. Timing-based attacks (burst injection)
 *
 * @module inputProtection
 */
import { EventEmitter } from 'events';
/** Singleton instance */
let inputProtectionInstance = null;
/**
 * Input Protection System
 */
export class InputProtection extends EventEmitter {
    config;
    inputTimings = [];
    lastKeystroke = 0;
    burstCounter = 0;
    suspicionScore = 0;
    blockedAttacks = 0;
    sanitizedInputs = 0;
    isInPasteMode = false;
    pasteStartTime = 0;
    // Dangerous escape sequences to block
    dangerousEscapes = [
        /\x1b\][0-9]+;/, // OSC sequences (can change terminal title, etc.)
        /\x1b[PX^_]/, // DCS, SOS, PM, APC sequences
        /\x1b\[[\d;]*[pls]/i, // Soft reset, various terminal modes
        /\x1b\[\?[\d;]*[hl]/, // Private mode sets (can enable dangerous modes)
        /\x1b\[[\d;]*n/, // Device status queries (info leak)
        /\x1b\[[\d;]*q/, // Keyboard LED control
        /\x1bP[^\\]*\x1b\\/, // Complete DCS sequence
        /\x1b\]0;[^\x07]*\x07/, // Title set sequence
        /\x1b\]52;/, // Clipboard manipulation sequence
    ];
    // Dangerous control characters
    dangerousControls = [
        0x00, // NUL - can cause issues
        0x01, // SOH
        0x02, // STX
        0x03, // ETX (Ctrl+C) - allow but track
        0x04, // EOT (Ctrl+D) - allow but track
        0x05, // ENQ
        0x06, // ACK
        0x07, // BEL - annoying beep
        0x0E, // SO - shift out
        0x0F, // SI - shift in
        0x10, // DLE
        0x11, // DC1 (XON)
        0x12, // DC2
        0x13, // DC3 (XOFF)
        0x14, // DC4
        0x15, // NAK
        0x16, // SYN
        0x17, // ETB
        0x18, // CAN
        0x19, // EM
        0x1A, // SUB (Ctrl+Z)
        0x1C, // FS
        0x1D, // GS
        0x1E, // RS
        0x1F, // US
        0x7F, // DEL
    ];
    // Unicode attack patterns
    unicodeAttacks = [
        { pattern: /\u202E/, name: 'RTL override' }, // Right-to-left override
        { pattern: /\u202D/, name: 'LTR override' }, // Left-to-right override
        { pattern: /\u202A/, name: 'LTR embedding' }, // Left-to-right embedding
        { pattern: /\u202B/, name: 'RTL embedding' }, // Right-to-left embedding
        { pattern: /\u202C/, name: 'Pop direction' }, // Pop directional formatting
        { pattern: /\u2066/, name: 'LTR isolate' }, // Left-to-right isolate
        { pattern: /\u2067/, name: 'RTL isolate' }, // Right-to-left isolate
        { pattern: /\u2068/, name: 'First strong isolate' },
        { pattern: /\u2069/, name: 'Pop isolate' }, // Pop directional isolate
        { pattern: /\u200E/, name: 'LTR mark' }, // Left-to-right mark
        { pattern: /\u200F/, name: 'RTL mark' }, // Right-to-left mark
        { pattern: /\uFEFF/, name: 'BOM' }, // Byte order mark
        { pattern: /[\u200B-\u200D]/, name: 'Zero-width chars' }, // Zero-width spaces/joiners
    ];
    constructor(config = {}) {
        super();
        this.config = {
            maxCharactersPerSecond: config.maxCharactersPerSecond ?? 500, // Allow fast paste
            maxPasteSize: config.maxPasteSize ?? 100000, // 100KB max paste
            maxBufferSize: config.maxBufferSize ?? 500000, // 500KB max buffer
            detectTimingAttacks: config.detectTimingAttacks ?? true,
            minKeystrokeInterval: config.minKeystrokeInterval ?? 5, // 5ms minimum (200 CPS)
            maxBurstSize: config.maxBurstSize ?? 1000, // Chars before suspicion
            strictMode: config.strictMode ?? false,
            verbose: config.verbose ?? false,
            onAttackDetected: config.onAttackDetected ?? (() => { }),
        };
    }
    /**
     * Validate and sanitize input before it enters the chat buffer
     */
    validateInput(input, isPaste = false) {
        const now = Date.now();
        let sanitized = input;
        let blocked = false;
        let reason;
        let attackType;
        let riskScore = 0;
        // Track timing
        this.inputTimings.push({ timestamp: now, charCount: input.length });
        this.cleanOldTimings();
        // === Size validation ===
        if (isPaste && input.length > this.config.maxPasteSize) {
            riskScore += 30;
            if (this.config.strictMode) {
                blocked = true;
                reason = `Paste size ${input.length} exceeds limit ${this.config.maxPasteSize}`;
                attackType = 'overflow_attempt';
            }
            else {
                sanitized = input.slice(0, this.config.maxPasteSize);
                reason = `Truncated paste from ${input.length} to ${this.config.maxPasteSize}`;
            }
            this.sanitizedInputs++;
        }
        // === Timing analysis ===
        if (this.config.detectTimingAttacks && !isPaste) {
            const interval = now - this.lastKeystroke;
            if (interval > 0 && interval < this.config.minKeystrokeInterval) {
                // Suspiciously fast keystrokes
                this.suspicionScore += 5;
                riskScore += 10;
                if (this.suspicionScore > 50) {
                    attackType = 'automated_injection';
                    reason = `Automated input detected (${Math.round(1000 / interval)} CPS)`;
                    this.log(`Suspicion score: ${this.suspicionScore}, interval: ${interval}ms`);
                }
            }
            else {
                // Normal typing - decrease suspicion
                this.suspicionScore = Math.max(0, this.suspicionScore - 1);
            }
        }
        this.lastKeystroke = now;
        // === Burst detection ===
        const recentChars = this.getRecentCharCount(100); // Last 100ms
        if (recentChars > this.config.maxBurstSize) {
            this.burstCounter++;
            riskScore += 20;
            if (this.burstCounter > 3) {
                attackType = 'timing_burst';
                reason = `Burst injection detected (${recentChars} chars in 100ms)`;
                if (this.config.strictMode) {
                    blocked = true;
                }
            }
        }
        else {
            this.burstCounter = Math.max(0, this.burstCounter - 1);
        }
        // === Escape sequence validation ===
        const escapeResult = this.checkEscapeSequences(sanitized);
        if (escapeResult.found) {
            riskScore += 40;
            attackType = 'escape_injection';
            reason = `Dangerous escape sequence: ${escapeResult.type}`;
            sanitized = escapeResult.sanitized;
            this.sanitizedInputs++;
            if (this.config.strictMode) {
                blocked = true;
            }
        }
        // === Control character validation ===
        const controlResult = this.checkControlCharacters(sanitized);
        if (controlResult.found) {
            riskScore += 15;
            if (!attackType) {
                attackType = 'control_injection';
                reason = `Control characters detected: ${controlResult.chars.join(', ')}`;
            }
            sanitized = controlResult.sanitized;
            this.sanitizedInputs++;
        }
        // === Unicode attack detection ===
        const unicodeResult = this.checkUnicodeAttacks(sanitized);
        if (unicodeResult.found) {
            riskScore += 30;
            if (!attackType) {
                attackType = 'unicode_attack';
                reason = `Unicode attack: ${unicodeResult.type}`;
            }
            sanitized = unicodeResult.sanitized;
            this.sanitizedInputs++;
        }
        // === Rate limiting check ===
        const cps = this.calculateCPS();
        if (cps > this.config.maxCharactersPerSecond && !isPaste) {
            riskScore += 25;
            if (!attackType) {
                attackType = 'automated_injection';
                reason = `Rate limit exceeded: ${Math.round(cps)} CPS`;
            }
            if (this.config.strictMode && cps > this.config.maxCharactersPerSecond * 2) {
                blocked = true;
            }
        }
        // Log attack detection
        if (attackType && riskScore > 30) {
            this.blockedAttacks++;
            this.config.onAttackDetected?.(attackType, reason || 'Unknown');
            this.emit('attack_detected', { type: attackType, reason, riskScore });
            this.log(`Attack detected: ${attackType} - ${reason} (risk: ${riskScore})`);
        }
        return {
            allowed: !blocked,
            sanitized: blocked ? '' : sanitized,
            blocked,
            reason,
            attackType,
            riskScore,
        };
    }
    /**
     * Enter paste mode (more lenient validation)
     */
    enterPasteMode() {
        this.isInPasteMode = true;
        this.pasteStartTime = Date.now();
        this.log('Entered paste mode');
    }
    /**
     * Exit paste mode
     */
    exitPasteMode() {
        const duration = Date.now() - this.pasteStartTime;
        this.isInPasteMode = false;
        this.log(`Exited paste mode (duration: ${duration}ms)`);
    }
    /**
     * Check if currently in paste mode
     */
    isPasting() {
        return this.isInPasteMode;
    }
    /**
     * Validate a complete prompt before submission
     */
    validatePromptSubmission(prompt) {
        // Final validation before sending to AI
        let sanitized = prompt;
        let riskScore = 0;
        let attackType;
        let reason;
        // Check total size
        if (prompt.length > this.config.maxBufferSize) {
            riskScore += 50;
            attackType = 'overflow_attempt';
            reason = `Prompt exceeds buffer limit`;
            sanitized = prompt.slice(0, this.config.maxBufferSize);
        }
        // Final escape sequence check
        const escapeResult = this.checkEscapeSequences(sanitized);
        if (escapeResult.found) {
            riskScore += 30;
            sanitized = escapeResult.sanitized;
        }
        // Final unicode check
        const unicodeResult = this.checkUnicodeAttacks(sanitized);
        if (unicodeResult.found) {
            riskScore += 20;
            sanitized = unicodeResult.sanitized;
        }
        return {
            allowed: true,
            sanitized,
            blocked: false,
            reason,
            attackType,
            riskScore,
        };
    }
    /**
     * Reset protection state (e.g., after idle period)
     */
    reset() {
        this.inputTimings = [];
        this.lastKeystroke = 0;
        this.burstCounter = 0;
        this.suspicionScore = 0;
        this.isInPasteMode = false;
    }
    /**
     * Get protection statistics
     */
    getStats() {
        return {
            blockedAttacks: this.blockedAttacks,
            sanitizedInputs: this.sanitizedInputs,
            currentSuspicion: this.suspicionScore,
            currentCPS: this.calculateCPS(),
        };
    }
    // === Private helper methods ===
    checkEscapeSequences(input) {
        let sanitized = input;
        let found = false;
        let type;
        for (const pattern of this.dangerousEscapes) {
            if (pattern.test(sanitized)) {
                found = true;
                type = pattern.source;
                sanitized = sanitized.replace(new RegExp(pattern.source, 'g'), '');
            }
        }
        return { found, type, sanitized };
    }
    checkControlCharacters(input) {
        const foundChars = [];
        let sanitized = '';
        for (let i = 0; i < input.length; i++) {
            const code = input.charCodeAt(i);
            // Allow normal control characters: Tab (9), Newline (10), Carriage Return (13), Escape (27 - for legit sequences)
            if (code === 9 || code === 10 || code === 13) {
                sanitized += input[i];
                continue;
            }
            // Block dangerous control characters
            if (this.dangerousControls.includes(code)) {
                foundChars.push(`0x${code.toString(16).padStart(2, '0')}`);
                continue;
            }
            sanitized += input[i];
        }
        return {
            found: foundChars.length > 0,
            chars: foundChars,
            sanitized,
        };
    }
    checkUnicodeAttacks(input) {
        let sanitized = input;
        let found = false;
        let type;
        for (const attack of this.unicodeAttacks) {
            const pattern = typeof attack.pattern === 'string'
                ? new RegExp(attack.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')
                : attack.pattern;
            if (pattern.test(sanitized)) {
                found = true;
                type = attack.name;
                sanitized = sanitized.replace(pattern, '');
            }
        }
        return { found, type, sanitized };
    }
    cleanOldTimings() {
        const cutoff = Date.now() - 1000; // Keep last 1 second
        this.inputTimings = this.inputTimings.filter(t => t.timestamp > cutoff);
    }
    getRecentCharCount(windowMs) {
        const cutoff = Date.now() - windowMs;
        return this.inputTimings
            .filter(t => t.timestamp > cutoff)
            .reduce((sum, t) => sum + t.charCount, 0);
    }
    calculateCPS() {
        if (this.inputTimings.length < 2)
            return 0;
        const now = Date.now();
        const windowMs = 1000;
        const cutoff = now - windowMs;
        const recentTimings = this.inputTimings.filter(t => t.timestamp > cutoff);
        if (recentTimings.length === 0)
            return 0;
        const totalChars = recentTimings.reduce((sum, t) => sum + t.charCount, 0);
        const timeSpan = now - recentTimings[0].timestamp;
        return timeSpan > 0 ? (totalChars / timeSpan) * 1000 : 0;
    }
    log(message) {
        if (this.config.verbose || process.env['AGI_DEBUG']) {
            console.error(`[InputProtection] ${message}`);
        }
    }
}
/**
 * Initialize global input protection (singleton)
 */
export function initializeInputProtection(config) {
    if (!inputProtectionInstance) {
        inputProtectionInstance = new InputProtection(config);
    }
    return inputProtectionInstance;
}
/**
 * Get input protection instance
 */
export function getInputProtection() {
    return inputProtectionInstance;
}
/**
 * Quick validate function for input
 */
export function validateChatInput(input, isPaste = false) {
    const protection = inputProtectionInstance ?? new InputProtection();
    return protection.validateInput(input, isPaste);
}
/**
 * Validate final prompt before submission
 */
export function validatePromptSubmit(prompt) {
    const protection = inputProtectionInstance ?? new InputProtection();
    return protection.validatePromptSubmission(prompt);
}
// Auto-initialize if environment variable is set
if (process.env['AGI_INPUT_PROTECTION'] === '1' || process.env['AGI_PROTECTED_MODE'] === '1') {
    initializeInputProtection({ verbose: process.env['AGI_DEBUG'] === '1' });
}
//# sourceMappingURL=inputProtection.js.map