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.

275 lines 34.3 kB
/** * Pattern Extraction for Memory Security * * Part of Issue #1314 Phase 1: Memory Security Architecture * Enhanced in Issue #1321 Phase 2: Pattern Encryption * * PURPOSE: * Identifies and extracts dangerous patterns from memory content for * sanitized display with AES-256-GCM encryption (Phase 2). * * PHASE 1 SCOPE: * - Identify pattern locations in content * - Generate pattern metadata (severity, description, location) * - Create sanitized content with pattern references * - Prepare structure for Phase 2 encryption * * PHASE 2 SCOPE: * - AES-256-GCM encryption of extracted patterns * - Key derivation from system secret * - Secure pattern storage and retrieval * - GCM authentication tags for integrity * * REFACTOR NOTE: * Converted from static class to instance-based for DI architecture compatibility. * PatternExtractor now requires PatternEncryptor dependency injected via constructor * for proper lifecycle management and testability. * * @module PatternExtractor */ import { logger } from '../../utils/logger.js'; /** * PatternExtractor service * * Extracts dangerous patterns from memory content and creates * sanitized versions suitable for display to LLMs. * * DI-COMPATIBLE: Instance-based service for dependency injection. */ export class PatternExtractor { encryptor; patternCounter = 0; /** * Create a new PatternExtractor instance * * @param encryptor - PatternEncryptor instance for encrypting extracted patterns */ constructor(encryptor) { this.encryptor = encryptor; logger.debug('PatternExtractor initialized'); } /** * Extract patterns from content based on validation results * * @param content - Original content containing patterns * @param validationResult - Validation result with detected patterns * @returns Extraction result with sanitized content and pattern metadata */ extractPatterns(content, validationResult) { logger.debug('Extracting patterns from content', { contentLength: content.length, detectedPatterns: validationResult.detectedPatterns?.length || 0, }); // If no patterns detected, return content as-is if (!validationResult.detectedPatterns || validationResult.detectedPatterns.length === 0) { return { sanitizedContent: content, patterns: [], patternCount: 0, }; } // Find all pattern matches in content const matches = this.findPatternMatches(content, validationResult); if (matches.length === 0) { logger.debug('No pattern matches found in content'); return { sanitizedContent: content, patterns: [], patternCount: 0, }; } logger.info('Found pattern matches', { count: matches.length }); // Create sanitized patterns with references const sanitizedPatterns = matches.map((match) => this.createSanitizedPattern(match)); // Create sanitized content by replacing patterns with references const sanitizedContent = this.createSanitizedContent(content, matches); return { sanitizedContent, patterns: sanitizedPatterns, patternCount: matches.length, }; } /** * Find all pattern matches in content * * This uses heuristics to locate the detected patterns within the content. * The ContentValidator tells us what patterns were detected, but not where. * We need to search for them. */ findPatternMatches(content, validationResult) { const matches = []; // Get detected patterns from validation const detectedPatterns = validationResult.detectedPatterns || []; for (const patternType of detectedPatterns) { // Use heuristics to find likely pattern locations // FIX: Provide default severity 'low' when undefined const severity = validationResult.severity || 'low'; const patternMatches = this.searchForPattern(content, patternType, severity); matches.push(...patternMatches); } // Sort matches by position for proper replacement matches.sort((a, b) => a.startOffset - b.startOffset); return matches; } /** * Search for a specific pattern type in content * * Uses common patterns and heuristics to locate dangerous content */ searchForPattern(content, patternType, severity) { const matches = []; // Common injection pattern markers const injectionMarkers = [ // LLM prompt injection markers /(?:ignore|disregard|forget)[\s\S]{0,30}(?:previous|above|prior)\s+(?:instructions|prompts|commands)/gi, /system[\s\S]{0,10}prompt[\s\S]{0,10}[:=]/gi, /new\s+(?:instructions|task|role|mission)[\s\S]{0,10}:/gi, // Code execution patterns /eval\s*\(/gi, /exec\s*\(/gi, /subprocess\./gi, /shell_exec/gi, // SQL injection patterns (flexible matching with .* for content between keywords) /(?:union|select|insert|delete|update|drop)[\s\S]*?(?:table|from|into|database)/gi, /'\s*(?:or|and)\s+['"]?\d+['"]?\s*=\s*['"]?\d+/gi, // Path traversal /\.\.\/|\.\.\\|\.\.[/\\]/g, // XXE patterns /<!ENTITY/gi, /<!DOCTYPE[\s\S]+SYSTEM/gi, ]; // Search for each marker pattern for (const marker of injectionMarkers) { let match; const regex = new RegExp(marker); while ((match = regex.exec(content)) !== null) { const matchText = match[0]; matches.push({ pattern: matchText, type: patternType, severity: severity, startOffset: match.index, length: matchText.length, description: this.getPatternDescription(patternType, matchText), }); // Prevent infinite loops on zero-length matches if (regex.lastIndex === match.index) { regex.lastIndex++; } } } return matches; } /** * Create a sanitized pattern object with metadata * * Phase 2: Now encrypts patterns using AES-256-GCM */ createSanitizedPattern(match) { const patternId = `PATTERN_${String(++this.patternCounter).padStart(3, '0')}`; // Phase 2: Encrypt the pattern using AES-256-GCM let encrypted = null; try { if (this.encryptor.isEnabled()) { encrypted = this.encryptor.encrypt(match.pattern); logger.debug('Pattern encrypted successfully', { patternId, algorithm: encrypted.algorithm, }); } else { logger.debug('Pattern encryption disabled, storing without encryption', { patternId, }); } } catch (error) { logger.error('Failed to encrypt pattern, storing without encryption', { patternId, error, }); // Continue without encryption rather than failing } return { ref: patternId, description: match.description, severity: match.severity, location: `offset ${match.startOffset}, length ${match.length}`, safetyInstruction: this.getSafetyInstruction(match.severity), // Phase 2: Encryption fields (populated if encryption successful) encryptedPattern: encrypted?.encryptedData, algorithm: encrypted?.algorithm, iv: encrypted?.iv, authTag: encrypted?.authTag, }; } /** * Create sanitized content by replacing patterns with references */ createSanitizedContent(content, matches) { // Work backwards through matches to maintain correct offsets let sanitized = content; const sortedMatches = [...matches].sort((a, b) => b.startOffset - a.startOffset); let patternIndex = sortedMatches.length; for (const match of sortedMatches) { const patternRef = `[PATTERN_${String(patternIndex).padStart(3, '0')}]`; const beforePattern = sanitized.substring(0, match.startOffset); const afterPattern = sanitized.substring(match.startOffset + match.length); sanitized = beforePattern + patternRef + afterPattern; patternIndex--; } return sanitized; } /** * Get human-readable description for a pattern type */ getPatternDescription(patternType, matchText) { const descriptions = { 'prompt-injection': 'LLM prompt injection attempt', 'sql-injection': 'SQL injection pattern', 'code-injection': 'Code execution pattern', 'path-traversal': 'Path traversal attempt', 'xxe': 'XML External Entity (XXE) pattern', 'xss': 'Cross-site scripting (XSS) pattern', 'command-injection': 'Command injection pattern', }; const description = descriptions[patternType] || `Security pattern: ${patternType}`; // Truncate match text if too long const truncatedMatch = matchText.length > 50 ? matchText.substring(0, 47) + '...' : matchText; return `${description} - "${truncatedMatch}"`; } /** * Get safety instruction based on severity */ getSafetyInstruction(severity) { switch (severity) { case 'critical': return 'CRITICAL - DO NOT EXECUTE - This pattern is malicious and must never be used in production code'; case 'high': return 'HIGH RISK - DO NOT EXECUTE - This pattern should only be used in security testing contexts'; case 'medium': return 'WARNING - This pattern may be dangerous if misused - Use only for security validation'; case 'low': return 'CAUTION - Review before use - May have security implications in certain contexts'; default: return 'This pattern has been extracted for security purposes - Review before use'; } } /** * Reset the pattern counter (useful for testing) */ resetCounter() { this.patternCounter = 0; } /** * Dispose of the extractor and clean up resources * Implements cleanup for proper DI lifecycle management */ async dispose() { this.resetCounter(); logger.debug('PatternExtractor disposed'); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGF0dGVybkV4dHJhY3Rvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZWN1cml0eS92YWxpZGF0aW9uL1BhdHRlcm5FeHRyYWN0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E0Qkc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUEwQy9DOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBUUU7SUFQckIsY0FBYyxHQUFXLENBQUMsQ0FBQztJQUVuQzs7OztPQUlHO0lBQ0gsWUFBNkIsU0FBMkI7UUFBM0IsY0FBUyxHQUFULFNBQVMsQ0FBa0I7UUFDdEQsTUFBTSxDQUFDLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQy9DLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxlQUFlLENBQ2IsT0FBZSxFQUNmLGdCQUF5QztRQUV6QyxNQUFNLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxFQUFFO1lBQy9DLGFBQWEsRUFBRSxPQUFPLENBQUMsTUFBTTtZQUM3QixnQkFBZ0IsRUFBRSxnQkFBZ0IsQ0FBQyxnQkFBZ0IsRUFBRSxNQUFNLElBQUksQ0FBQztTQUNqRSxDQUFDLENBQUM7UUFFSCxnREFBZ0Q7UUFDaEQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLGdCQUFnQixJQUFJLGdCQUFnQixDQUFDLGdCQUFnQixDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN6RixPQUFPO2dCQUNMLGdCQUFnQixFQUFFLE9BQU87Z0JBQ3pCLFFBQVEsRUFBRSxFQUFFO2dCQUNaLFlBQVksRUFBRSxDQUFDO2FBQ2hCLENBQUM7UUFDSixDQUFDO1FBRUQsc0NBQXNDO1FBQ3RDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztRQUVuRSxJQUFJLE9BQU8sQ0FB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Q2pHLEtBQUssS0FBSztnQkFDUixPQUFPLGtGQUFrRixDQUFDO1lBQzVGO2dCQUNFLE9BQU8sMkVBQTJFLENBQUM7UUFDdkYsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILFlBQVk7UUFDVixJQUFJLENBQUMsY0FBYyxHQUFHLENBQUMsQ0FBQztJQUMxQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLE9BQU87UUFDWCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDcEIsTUFBTSxDQUFDLEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO0lBQzVDLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogUGF0dGVybiBFeHRyYWN0aW9uIGZvciBNZW1vcnkgU2VjdXJpdHlcbiAqXG4gKiBQYXJ0IG9mIElzc3VlICMxMzE0IFBoYXNlIDE6IE1lbW9yeSBTZWN1cml0eSBBcmNoaXRlY3R1cmVcbiAqIEVuaGFuY2VkIGluIElzc3VlICMxMzIxIFBoYXNlIDI6IFBhdHRlcm4gRW5jcnlwdGlvblxuICpcbiAqIFBVUlBPU0U6XG4gKiBJZGVudGlmaWVzIGFuZCBleHRyYWN0cyBkYW5nZXJvdXMgcGF0dGVybnMgZnJvbSBtZW1vcnkgY29udGVudCBmb3JcbiAqIHNhbml0aXplZCBkaXNwbGF5IHdpdGggQUVTLTI1Ni1HQ00gZW5jcnlwdGlvbiAoUGhhc2UgMikuXG4gKlxuICogUEhBU0UgMSBTQ09QRTpcbiAqIC0gSWRlbnRpZnkgcGF0dGVybiBsb2NhdGlvbnMgaW4gY29udGVudFxuICogLSBHZW5lcmF0ZSBwYXR0ZXJuIG1ldGFkYXRhIChzZXZlcml0eSwgZGVzY3JpcHRpb24sIGxvY2F0aW9uKVxuICogLSBDcmVhdGUgc2FuaXRpemVkIGNvbnRlbnQgd2l0aCBwYXR0ZXJuIHJlZmVyZW5jZXNcbiAqIC0gUHJlcGFyZSBzdHJ1Y3R1cmUgZm9yIFBoYXNlIDIgZW5jcnlwdGlvblxuICpcbiAqIFBIQVNFIDIgU0NPUEU6XG4gKiAtIEFFUy0yNTYtR0NNIGVuY3J5cHRpb24gb2YgZXh0cmFjdGVkIHBhdHRlcm5zXG4gKiAtIEtleSBkZXJpdmF0aW9uIGZyb20gc3lzdGVtIHNlY3JldFxuICogLSBTZWN1cmUgcGF0dGVybiBzdG9yYWdlIGFuZCByZXRyaWV2YWxcbiAqIC0gR0NNIGF1dGhlbnRpY2F0aW9uIHRhZ3MgZm9yIGludGVncml0eVxuICpcbiAqIFJFRkFDVE9SIE5PVEU6XG4gKiBDb252ZXJ0ZWQgZnJvbSBzdGF0aWMgY2xhc3MgdG8gaW5zdGFuY2UtYmFzZWQgZm9yIERJIGFyY2hpdGVjdHVyZSBjb21wYXRpYmlsaXR5LlxuICogUGF0dGVybkV4dHJhY3RvciBub3cgcmVxdWlyZXMgUGF0dGVybkVuY3J5cHRvciBkZXBlbmRlbmN5IGluamVjdGVkIHZpYSBjb25zdHJ1Y3RvclxuICogZm9yIHByb3BlciBsaWZlY3ljbGUgbWFuYWdlbWVudCBhbmQgdGVzdGFiaWxpdHkuXG4gKlxuICogQG1vZHVsZSBQYXR0ZXJuRXh0cmFjdG9yXG4gKi9cblxuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB0eXBlIHsgQ29udGVudFZhbGlkYXRpb25SZXN1bHQgfSBmcm9tICcuLi9jb250ZW50VmFsaWRhdG9yLmpzJztcbmltcG9ydCB0eXBlIHsgU2FuaXRpemVkUGF0dGVybiB9IGZyb20gJy4vQmFja2dyb3VuZFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBQYXR0ZXJuRW5jcnlwdG9yIH0gZnJvbSAnLi4vZW5jcnlwdGlvbi9QYXR0ZXJuRW5jcnlwdG9yLmpzJztcblxuLyoqXG4gKiBQYXR0ZXJuIG1hdGNoIGluZm9ybWF0aW9uIGZyb20gdmFsaWRhdGlvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIFBhdHRlcm5NYXRjaCB7XG4gIC8qKiBUaGUgYWN0dWFsIHBhdHRlcm4gdGV4dCB0aGF0IHdhcyBtYXRjaGVkICovXG4gIHBhdHRlcm46IHN0cmluZztcblxuICAvKiogVGhlIHR5cGUvY2F0ZWdvcnkgb2YgcGF0dGVybiAqL1xuICB0eXBlOiBzdHJpbmc7XG5cbiAgLyoqIFNldmVyaXR5IGxldmVsICovXG4gIHNldmVyaXR5OiAnY3JpdGljYWwnIHwgJ2hpZ2gnIHwgJ21lZGl1bScgfCAnbG93JztcblxuICAvKiogU3RhcnQgcG9zaXRpb24gaW4gY29udGVudCAqL1xuICBzdGFydE9mZnNldDogbnVtYmVyO1xuXG4gIC8qKiBMZW5ndGggb2YgdGhlIHBhdHRlcm4gKi9cbiAgbGVuZ3RoOiBudW1iZXI7XG5cbiAgLyoqIEh1bWFuLXJlYWRhYmxlIGRlc2NyaXB0aW9uICovXG4gIGRlc2NyaXB0aW9uOiBzdHJpbmc7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIHBhdHRlcm4gZXh0cmFjdGlvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIEV4dHJhY3Rpb25SZXN1bHQge1xuICAvKiogU2FuaXRpemVkIGNvbnRlbnQgd2l0aCBwYXR0ZXJuIHJlZmVyZW5jZXMgKi9cbiAgc2FuaXRpemVkQ29udGVudDogc3RyaW5nO1xuXG4gIC8qKiBFeHRyYWN0ZWQgcGF0dGVybiBtZXRhZGF0YSAqL1xuICBwYXR0ZXJuczogU2FuaXRpemVkUGF0dGVybltdO1xuXG4gIC8qKiBOdW1iZXIgb2YgcGF0dGVybnMgZXh0cmFjdGVkICovXG4gIHBhdHRlcm5Db3VudDogbnVtYmVyO1xufVxuXG4vKipcbiAqIFBhdHRlcm5FeHRyYWN0b3Igc2VydmljZVxuICpcbiAqIEV4dHJhY3RzIGRhbmdlcm91cyBwYXR0ZXJucyBmcm9tIG1lbW9yeSBjb250ZW50IGFuZCBjcmVhdGVzXG4gKiBzYW5pdGl6ZWQgdmVyc2lvbnMgc3VpdGFibGUgZm9yIGRpc3BsYXkgdG8gTExNcy5cbiAqXG4gKiBESS1DT01QQVRJQkxFOiBJbnN0YW5jZS1iYXNlZCBzZXJ2aWNlIGZvciBkZXBlbmRlbmN5IGluamVjdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIFBhdHRlcm5FeHRyYWN0b3Ige1xuICBwcml2YXRlIHBhdHRlcm5Db3VudGVyOiBudW1iZXIgPSAwO1xuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBuZXcgUGF0dGVybkV4dHJhY3RvciBpbnN0YW5jZVxuICAgKlxuICAgKiBAcGFyYW0gZW5jcnlwdG9yIC0gUGF0dGVybkVuY3J5cHRvciBpbnN0YW5jZSBmb3IgZW5jcnlwdGluZyBleHRyYWN0ZWQgcGF0dGVybnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgcmVhZG9ubHkgZW5jcnlwdG9yOiBQYXR0ZXJuRW5jcnlwdG9yKSB7XG4gICAgbG9nZ2VyLmRlYnVnKCdQYXR0ZXJuRXh0cmFjdG9yIGluaXRpYWxpemVkJyk7XG4gIH1cblxuICAvKipcbiAgICogRXh0cmFjdCBwYXR0ZXJucyBmcm9tIGNvbnRlbnQgYmFzZWQgb24gdmFsaWRhdGlvbiByZXN1bHRzXG4gICAqXG4gICAqIEBwYXJhbSBjb250ZW50IC0gT3JpZ2luYWwgY29udGVudCBjb250YWluaW5nIHBhdHRlcm5zXG4gICAqIEBwYXJhbSB2YWxpZGF0aW9uUmVzdWx0IC0gVmFsaWRhdGlvbiByZXN1bHQgd2l0aCBkZXRlY3RlZCBwYXR0ZXJuc1xuICAgKiBAcmV0dXJucyBFeHRyYWN0aW9uIHJlc3VsdCB3aXRoIHNhbml0aXplZCBjb250ZW50IGFuZCBwYXR0ZXJuIG1ldGFkYXRhXG4gICAqL1xuICBleHRyYWN0UGF0dGVybnMoXG4gICAgY29udGVudDogc3RyaW5nLFxuICAgIHZhbGlkYXRpb25SZXN1bHQ6IENvbnRlbnRWYWxpZGF0aW9uUmVzdWx0XG4gICk6IEV4dHJhY3Rpb25SZXN1bHQge1xuICAgIGxvZ2dlci5kZWJ1ZygnRXh0cmFjdGluZyBwYXR0ZXJucyBmcm9tIGNvbnRlbnQnLCB7XG4gICAgICBjb250ZW50TGVuZ3RoOiBjb250ZW50Lmxlbmd0aCxcbiAgICAgIGRldGVjdGVkUGF0dGVybnM6IHZhbGlkYXRpb25SZXN1bHQuZGV0ZWN0ZWRQYXR0ZXJucz8ubGVuZ3RoIHx8IDAsXG4gICAgfSk7XG5cbiAgICAvLyBJZiBubyBwYXR0ZXJucyBkZXRlY3RlZCwgcmV0dXJuIGNvbnRlbnQgYXMtaXNcbiAgICBpZiAoIXZhbGlkYXRpb25SZXN1bHQuZGV0ZWN0ZWRQYXR0ZXJucyB8fCB2YWxpZGF0aW9uUmVzdWx0LmRldGVjdGVkUGF0dGVybnMubGVuZ3RoID09PSAwKSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBzYW5pdGl6ZWRDb250ZW50OiBjb250ZW50LFxuICAgICAgICBwYXR0ZXJuczogW10sXG4gICAgICAgIHBhdHRlcm5Db3VudDogMCxcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gRmluZCBhbGwgcGF0dGVybiBtYXRjaGVzIGluIGNvbnRlbnRcbiAgICBjb25zdCBtYXRjaGVzID0gdGhpcy5maW5kUGF0dGVybk1hdGNoZXMoY29udGVudCwgdmFsaWRhdGlvblJlc3VsdCk7XG5cbiAgICBpZiAobWF0Y2hlcy5sZW5ndGggPT09IDApIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnTm8gcGF0dGVybiBtYXRjaGVzIGZvdW5kIGluIGNvbnRlbnQnKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHNhbml0aXplZENvbnRlbnQ6IGNvbnRlbnQsXG4gICAgICAgIHBhdHRlcm5zOiBbXSxcbiAgICAgICAgcGF0dGVybkNvdW50OiAwLFxuICAgICAgfTtcbiAgICB9XG5cbiAgICBsb2dnZXIuaW5mbygnRm91bmQgcGF0dGVybiBtYXRjaGVzJywgeyBjb3VudDogbWF0Y2hlcy5sZW5ndGggfSk7XG5cbiAgICAvLyBDcmVhdGUgc2FuaXRpemVkIHBhdHRlcm5zIHdpdGggcmVmZXJlbmNlc1xuICAgIGNvbnN0IHNhbml0aXplZFBhdHRlcm5zID0gbWF0Y2hlcy5tYXAoKG1hdGNoKSA9PlxuICAgICAgdGhpcy5jcmVhdGVTYW5pdGl6ZWRQYXR0ZXJuKG1hdGNoKVxuICAgICk7XG5cbiAgICAvLyBDcmVhdGUgc2FuaXRpemVkIGNvbnRlbnQgYnkgcmVwbGFjaW5nIHBhdHRlcm5zIHdpdGggcmVmZXJlbmNlc1xuICAgIGNvbnN0IHNhbml0aXplZENvbnRlbnQgPSB0aGlzLmNyZWF0ZVNhbml0aXplZENvbnRlbnQoY29udGVudCwgbWF0Y2hlcyk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgc2FuaXRpemVkQ29udGVudCxcbiAgICAgIHBhdHRlcm5zOiBzYW5pdGl6ZWRQYXR0ZXJucyxcbiAgICAgIHBhdHRlcm5Db3VudDogbWF0Y2hlcy5sZW5ndGgsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBGaW5kIGFsbCBwYXR0ZXJuIG1hdGNoZXMgaW4gY29udGVudFxuICAgKlxuICAgKiBUaGlzIHVzZXMgaGV1cmlzdGljcyB0byBsb2NhdGUgdGhlIGRldGVjdGVkIHBhdHRlcm5zIHdpdGhpbiB0aGUgY29udGVudC5cbiAgICogVGhlIENvbnRlbnRWYWxpZGF0b3IgdGVsbHMgdXMgd2hhdCBwYXR0ZXJucyB3ZXJlIGRldGVjdGVkLCBidXQgbm90IHdoZXJlLlxuICAgKiBXZSBuZWVkIHRvIHNlYXJjaCBmb3IgdGhlbS5cbiAgICovXG4gIHByaXZhdGUgZmluZFBhdHRlcm5NYXRjaGVzKFxuICAgIGNvbnRlbnQ6IHN0cmluZyxcbiAgICB2YWxpZGF0aW9uUmVzdWx0OiBDb250ZW50VmFsaWRhdGlvblJlc3VsdFxuICApOiBQYXR0ZXJuTWF0Y2hbXSB7XG4gICAgY29uc3QgbWF0Y2hlczogUGF0dGVybk1hdGNoW10gPSBbXTtcblxuICAgIC8vIEdldCBkZXRlY3RlZCBwYXR0ZXJucyBmcm9tIHZhbGlkYXRpb25cbiAgICBjb25zdCBkZXRlY3RlZFBhdHRlcm5zID0gdmFsaWRhdGlvblJlc3VsdC5kZXRlY3RlZFBhdHRlcm5zIHx8IFtdO1xuXG4gICAgZm9yIChjb25zdCBwYXR0ZXJuVHlwZSBvZiBkZXRlY3RlZFBhdHRlcm5zKSB7XG4gICAgICAvLyBVc2UgaGV1cmlzdGljcyB0byBmaW5kIGxpa2VseSBwYXR0ZXJuIGxvY2F0aW9uc1xuICAgICAgLy8gRklYOiBQcm92aWRlIGRlZmF1bHQgc2V2ZXJpdHkgJ2xvdycgd2hlbiB1bmRlZmluZWRcbiAgICAgIGNvbnN0IHNldmVyaXR5ID0gdmFsaWRhdGlvblJlc3VsdC5zZXZlcml0eSB8fCAnbG93JztcbiAgICAgIGNvbnN0IHBhdHRlcm5NYXRjaGVzID0gdGhpcy5zZWFyY2hGb3JQYXR0ZXJuKGNvbnRlbnQsIHBhdHRlcm5UeXBlLCBzZXZlcml0eSk7XG5cbiAgICAgIG1hdGNoZXMucHVzaCguLi5wYXR0ZXJuTWF0Y2hlcyk7XG4gICAgfVxuXG4gICAgLy8gU29ydCBtYXRjaGVzIGJ5IHBvc2l0aW9uIGZvciBwcm9wZXIgcmVwbGFjZW1lbnRcbiAgICBtYXRjaGVzLnNvcnQoKGEsIGIpID0+IGEuc3RhcnRPZmZzZXQgLSBiLnN0YXJ0T2Zmc2V0KTtcblxuICAgIHJldHVybiBtYXRjaGVzO1xuICB9XG5cbiAgLyoqXG4gICAqIFNlYXJjaCBmb3IgYSBzcGVjaWZpYyBwYXR0ZXJuIHR5cGUgaW4gY29udGVudFxuICAgKlxuICAgKiBVc2VzIGNvbW1vbiBwYXR0ZXJucyBhbmQgaGV1cmlzdGljcyB0byBsb2NhdGUgZGFuZ2Vyb3VzIGNvbnRlbnRcbiAgICovXG4gIHByaXZhdGUgc2VhcmNoRm9yUGF0dGVybihcbiAgICBjb250ZW50OiBzdHJpbmcsXG4gICAgcGF0dGVyblR5cGU6IHN0cmluZyxcbiAgICBzZXZlcml0eTogJ2NyaXRpY2FsJyB8ICdoaWdoJyB8ICdtZWRpdW0nIHwgJ2xvdycgfCAnaW5mbydcbiAgKTogUGF0dGVybk1hdGNoW10ge1xuICAgIGNvbnN0IG1hdGNoZXM6IFBhdHRlcm5NYXRjaFtdID0gW107XG5cbiAgICAvLyBDb21tb24gaW5qZWN0aW9uIHBhdHRlcm4gbWFya2Vyc1xuICAgIGNvbnN0IGluamVjdGlvbk1hcmtlcnMgPSBbXG4gICAgICAvLyBMTE0gcHJvbXB0IGluamVjdGlvbiBtYXJrZXJzXG4gICAgICAvKD86aWdub3JlfGRpc3JlZ2FyZHxmb3JnZXQpW1xcc1xcU117MCwzMH0oPzpwcmV2aW91c3xhYm92ZXxwcmlvcilcXHMrKD86aW5zdHJ1Y3Rpb25zfHByb21wdHN8Y29tbWFuZHMpL2dpLFxuICAgICAgL3N5c3RlbVtcXHNcXFNdezAsMTB9cHJvbXB0W1xcc1xcU117MCwxMH1bOj1dL2dpLFxuICAgICAgL25ld1xccysoPzppbnN0cnVjdGlvbnN8dGFza3xyb2xlfG1pc3Npb24pW1xcc1xcU117MCwxMH06L2dpLFxuXG4gICAgICAvLyBDb2RlIGV4ZWN1dGlvbiBwYXR0ZXJuc1xuICAgICAgL2V2YWxcXHMqXFwoL2dpLFxuICAgICAgL2V4ZWNcXHMqXFwoL2dpLFxuICAgICAgL3N1YnByb2Nlc3NcXC4vZ2ksXG4gICAgICAvc2hlbGxfZXhlYy9naSxcblxuICAgICAgLy8gU1FMIGluamVjdGlvbiBwYXR0ZXJucyAoZmxleGlibGUgbWF0Y2hpbmcgd2l0aCAuKiBmb3IgY29udGVudCBiZXR3ZWVuIGtleXdvcmRzKVxuICAgICAgLyg/OnVuaW9ufHNlbGVjdHxpbnNlcnR8ZGVsZXRlfHVwZGF0ZXxkcm9wKVtcXHNcXFNdKj8oPzp0YWJsZXxmcm9tfGludG98ZGF0YWJhc2UpL2dpLFxuICAgICAgLydcXHMqKD86b3J8YW5kKVxccytbJ1wiXT9cXGQrWydcIl0/XFxzKj1cXHMqWydcIl0/XFxkKy9naSxcblxuICAgICAgLy8gUGF0aCB0cmF2ZXJzYWxcbiAgICAgIC9cXC5cXC5cXC98XFwuXFwuXFxcXHxcXC5cXC5bL1xcXFxdL2csXG5cbiAgICAgIC8vIFhYRSBwYXR0ZXJuc1xuICAgICAgLzwhRU5USVRZL2dpLFxuICAgICAgLzwhRE9DVFlQRVtcXHNcXFNdK1NZU1RFTS9naSxcbiAgICBdO1xuXG4gICAgLy8gU2VhcmNoIGZvciBlYWNoIG1hcmtlciBwYXR0ZXJuXG4gICAgZm9yIChjb25zdCBtYXJrZXIgb2YgaW5qZWN0aW9uTWFya2Vycykge1xuICAgICAgbGV0IG1hdGNoO1xuICAgICAgY29uc3QgcmVnZXggPSBuZXcgUmVnRXhwKG1hcmtlcik7XG5cbiAgICAgIHdoaWxlICgobWF0Y2ggPSByZWdleC5leGVjKGNvbnRlbnQpKSAhPT0gbnVsbCkge1xuICAgICAgICBjb25zdCBtYXRjaFRleHQgPSBtYXRjaFswXTtcblxuICAgICAgICBtYXRjaGVzLnB1c2goe1xuICAgICAgICAgIHBhdHRlcm46IG1hdGNoVGV4dCxcbiAgICAgICAgICB0eXBlOiBwYXR0ZXJuVHlwZSxcbiAgICAgICAgICBzZXZlcml0eTogc2V2ZXJpdHkgYXMgJ2NyaXRpY2FsJyB8ICdoaWdoJyB8ICdtZWRpdW0nIHwgJ2xvdycsXG4gICAgICAgICAgc3RhcnRPZmZzZXQ6IG1hdGNoLmluZGV4LFxuICAgICAgICAgIGxlbmd0aDogbWF0Y2hUZXh0Lmxlbmd0aCxcbiAgICAgICAgICBkZXNjcmlwdGlvbjogdGhpcy5nZXRQYXR0ZXJuRGVzY3JpcHRpb24ocGF0dGVyblR5cGUsIG1hdGNoVGV4dCksXG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFByZXZlbnQgaW5maW5pdGUgbG9vcHMgb24gemVyby1sZW5ndGggbWF0Y2hlc1xuICAgICAgICBpZiAocmVnZXgubGFzdEluZGV4ID09PSBtYXRjaC5pbmRleCkge1xuICAgICAgICAgIHJlZ2V4Lmxhc3RJbmRleCsrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG1hdGNoZXM7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgc2FuaXRpemVkIHBhdHRlcm4gb2JqZWN0IHdpdGggbWV0YWRhdGFcbiAgICpcbiAgICogUGhhc2UgMjogTm93IGVuY3J5cHRzIHBhdHRlcm5zIHVzaW5nIEFFUy0yNTYtR0NNXG4gICAqL1xuICBwcml2YXRlIGNyZWF0ZVNhbml0aXplZFBhdHRlcm4oXG4gICAgbWF0Y2g6IFBhdHRlcm5NYXRjaFxuICApOiBTYW5pdGl6ZWRQYXR0ZXJuIHtcbiAgICBjb25zdCBwYXR0ZXJuSWQgPSBgUEFUVEVSTl8ke1N0cmluZygrK3RoaXMucGF0dGVybkNvdW50ZXIpLnBhZFN0YXJ0KDMsICcwJyl9YDtcblxuICAgIC8vIFBoYXNlIDI6IEVuY3J5cHQgdGhlIHBhdHRlcm4gdXNpbmcgQUVTLTI1Ni1HQ01cbiAgICBsZXQgZW5jcnlwdGVkID0gbnVsbDtcbiAgICB0cnkge1xuICAgICAgaWYgKHRoaXMuZW5jcnlwdG9yLmlzRW5hYmxlZCgpKSB7XG4gICAgICAgIGVuY3J5cHRlZCA9IHRoaXMuZW5jcnlwdG9yLmVuY3J5cHQobWF0Y2gucGF0dGVybik7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnUGF0dGVybiBlbmNyeXB0ZWQgc3VjY2Vzc2Z1bGx5Jywge1xuICAgICAgICAgIHBhdHRlcm5JZCxcbiAgICAgICAgICBhbGdvcml0aG06IGVuY3J5cHRlZC5hbGdvcml0aG0sXG4gICAgICAgIH0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdQYXR0ZXJuIGVuY3J5cHRpb24gZGlzYWJsZWQsIHN0b3Jpbmcgd2l0aG91dCBlbmNyeXB0aW9uJywge1xuICAgICAgICAgIHBhdHRlcm5JZCxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIGVuY3J5cHQgcGF0dGVybiwgc3RvcmluZyB3aXRob3V0IGVuY3J5cHRpb24nLCB7XG4gICAgICAgIHBhdHRlcm5JZCxcbiAgICAgICAgZXJyb3IsXG4gICAgICB9KTtcbiAgICAgIC8vIENvbnRpbnVlIHdpdGhvdXQgZW5jcnlwdGlvbiByYXRoZXIgdGhhbiBmYWlsaW5nXG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIHJlZjogcGF0dGVybklkLFxuICAgICAgZGVzY3JpcHRpb246IG1hdGNoLmRlc2NyaXB0aW9uLFxuICAgICAgc2V2ZXJpdHk6IG1hdGNoLnNldmVyaXR5LFxuICAgICAgbG9jYXRpb246IGBvZmZzZXQgJHttYXRjaC5zdGFydE9mZnNldH0sIGxlbmd0aCAke21hdGNoLmxlbmd0aH1gLFxuICAgICAgc2FmZXR5SW5zdHJ1Y3Rpb246IHRoaXMuZ2V0U2FmZXR5SW5zdHJ1Y3Rpb24obWF0Y2guc2V2ZXJpdHkpLFxuICAgICAgLy8gUGhhc2UgMjogRW5jcnlwdGlvbiBmaWVsZHMgKHBvcHVsYXRlZCBpZiBlbmNyeXB0aW9uIHN1Y2Nlc3NmdWwpXG4gICAgICBlbmNyeXB0ZWRQYXR0ZXJuOiBlbmNyeXB0ZWQ/LmVuY3J5cHRlZERhdGEsXG4gICAgICBhbGdvcml0aG06IGVuY3J5cHRlZD8uYWxnb3JpdGhtLFxuICAgICAgaXY6IGVuY3J5cHRlZD8uaXYsXG4gICAgICBhdXRoVGFnOiBlbmNyeXB0ZWQ/LmF1dGhUYWcsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgc2FuaXRpemVkIGNvbnRlbnQgYnkgcmVwbGFjaW5nIHBhdHRlcm5zIHdpdGggcmVmZXJlbmNlc1xuICAgKi9cbiAgcHJpdmF0ZSBjcmVhdGVTYW5pdGl6ZWRDb250ZW50KFxuICAgIGNvbnRlbnQ6IHN0cmluZyxcbiAgICBtYXRjaGVzOiBQYXR0ZXJuTWF0Y2hbXVxuICApOiBzdHJpbmcge1xuICAgIC8vIFdvcmsgYmFja3dhcmRzIHRocm91Z2ggbWF0Y2hlcyB0byBtYWludGFpbiBjb3JyZWN0IG9mZnNldHNcbiAgICBsZXQgc2FuaXRpemVkID0gY29udGVudDtcbiAgICBjb25zdCBzb3J0ZWRNYXRjaGVzID0gWy4uLm1hdGNoZXNdLnNvcnQoKGEsIGIpID0+IGIuc3RhcnRPZmZzZXQgLSBhLnN0YXJ0T2Zmc2V0KTtcblxuICAgIGxldCBwYXR0ZXJuSW5kZXggPSBzb3J0ZWRNYXRjaGVzLmxlbmd0aDtcblxuICAgIGZvciAoY29uc3QgbWF0Y2ggb2Ygc29ydGVkTWF0Y2hlcykge1xuICAgICAgY29uc3QgcGF0dGVyblJlZiA9IGBbUEFUVEVSTl8ke1N0cmluZyhwYXR0ZXJuSW5kZXgpLnBhZFN0YXJ0KDMsICcwJyl9XWA7XG4gICAgICBjb25zdCBiZWZvcmVQYXR0ZXJuID0gc2FuaXRpemVkLnN1YnN0cmluZygwLCBtYXRjaC5zdGFydE9mZnNldCk7XG4gICAgICBjb25zdCBhZnRlclBhdHRlcm4gPSBzYW5pdGl6ZWQuc3Vic3RyaW5nKG1hdGNoLnN0YXJ0T2Zmc2V0ICsgbWF0Y2gubGVuZ3RoKTtcblxuICAgICAgc2FuaXRpemVkID0gYmVmb3JlUGF0dGVybiArIHBhdHRlcm5SZWYgKyBhZnRlclBhdHRlcm47XG4gICAgICBwYXR0ZXJuSW5kZXgtLTtcbiAgICB9XG5cbiAgICByZXR1cm4gc2FuaXRpemVkO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBodW1hbi1yZWFkYWJsZSBkZXNjcmlwdGlvbiBmb3IgYSBwYXR0ZXJuIHR5cGVcbiAgICovXG4gIHByaXZhdGUgZ2V0UGF0dGVybkRlc2NyaXB0aW9uKHBhdHRlcm5UeXBlOiBzdHJpbmcsIG1hdGNoVGV4dDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBjb25zdCBkZXNjcmlwdGlvbnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgICAncHJvbXB0LWluamVjdGlvbic6ICdMTE0gcHJvbXB0IGluamVjdGlvbiBhdHRlbXB0JyxcbiAgICAgICdzcWwtaW5qZWN0aW9uJzogJ1NRTCBpbmplY3Rpb24gcGF0dGVybicsXG4gICAgICAnY29kZS1pbmplY3Rpb24nOiAnQ29kZSBleGVjdXRpb24gcGF0dGVybicsXG4gICAgICAncGF0aC10cmF2ZXJzYWwnOiAnUGF0aCB0cmF2ZXJzYWwgYXR0ZW1wdCcsXG4gICAgICAneHhlJzogJ1hNTCBFeHRlcm5hbCBFbnRpdHkgKFhYRSkgcGF0dGVybicsXG4gICAgICAneHNzJzogJ0Nyb3NzLXNpdGUgc2NyaXB0aW5nIChYU1MpIHBhdHRlcm4nLFxuICAgICAgJ2NvbW1hbmQtaW5qZWN0aW9uJzogJ0NvbW1hbmQgaW5qZWN0aW9uIHBhdHRlcm4nLFxuICAgIH07XG5cbiAgICBjb25zdCBkZXNjcmlwdGlvbiA9IGRlc2NyaXB0aW9uc1twYXR0ZXJuVHlwZV0gfHwgYFNlY3VyaXR5IHBhdHRlcm46ICR7cGF0dGVyblR5cGV9YDtcblxuICAgIC8vIFRydW5jYXRlIG1hdGNoIHRleHQgaWYgdG9vIGxvbmdcbiAgICBjb25zdCB0cnVuY2F0ZWRNYXRjaCA9IG1hdGNoVGV4dC5sZW5ndGggPiA1MFxuICAgICAgPyBtYXRjaFRleHQuc3Vic3RyaW5nKDAsIDQ3KSArICcuLi4nXG4gICAgICA6IG1hdGNoVGV4dDtcblxuICAgIHJldHVybiBgJHtkZXNjcmlwdGlvbn0gLSBcIiR7dHJ1bmNhdGVkTWF0Y2h9XCJgO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBzYWZldHkgaW5zdHJ1Y3Rpb24gYmFzZWQgb24gc2V2ZXJpdHlcbiAgICovXG4gIHByaXZhdGUgZ2V0U2FmZXR5SW5zdHJ1Y3Rpb24oc2V2ZXJpdHk6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgc3dpdGNoIChzZXZlcml0eSkge1xuICAgICAgY2FzZSAnY3JpdGljYWwnOlxuICAgICAgICByZXR1cm4gJ0NSSVRJQ0FMIC0gRE8gTk9UIEVYRUNVVEUgLSBUaGlzIHBhdHRlcm4gaXMgbWFsaWNpb3VzIGFuZCBtdXN0IG5ldmVyIGJlIHVzZWQgaW4gcHJvZHVjdGlvbiBjb2RlJztcbiAgICAgIGNhc2UgJ2hpZ2gnOlxuICAgICAgICByZXR1cm4gJ0hJR0ggUklTSyAtIERPIE5PVCBFWEVDVVRFIC0gVGhpcyBwYXR0ZXJuIHNob3VsZCBvbmx5IGJlIHVzZWQgaW4gc2VjdXJpdHkgdGVzdGluZyBjb250ZXh0cyc7XG4gICAgICBjYXNlICdtZWRpdW0nOlxuICAgICAgICByZXR1cm4gJ1dBUk5JTkcgLSBUaGlzIHBhdHRlcm4gbWF5IGJlIGRhbmdlcm91cyBpZiBtaXN1c2VkIC0gVXNlIG9ubHkgZm9yIHNlY3VyaXR5IHZhbGlkYXRpb24nO1xuICAgICAgY2FzZSAnbG93JzpcbiAgICAgICAgcmV0dXJuICdDQVVUSU9OIC0gUmV2aWV3IGJlZm9yZSB1c2UgLSBNYXkgaGF2ZSBzZWN1cml0eSBpbXBsaWNhdGlvbnMgaW4gY2VydGFpbiBjb250ZXh0cyc7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gJ1RoaXMgcGF0dGVybiBoYXMgYmVlbiBleHRyYWN0ZWQgZm9yIHNlY3VyaXR5IHB1cnBvc2VzIC0gUmV2aWV3IGJlZm9yZSB1c2UnO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXNldCB0aGUgcGF0dGVybiBjb3VudGVyICh1c2VmdWwgZm9yIHRlc3RpbmcpXG4gICAqL1xuICByZXNldENvdW50ZXIoKTogdm9pZCB7XG4gICAgdGhpcy5wYXR0ZXJuQ291bnRlciA9IDA7XG4gIH1cblxuICAvKipcbiAgICogRGlzcG9zZSBvZiB0aGUgZXh0cmFjdG9yIGFuZCBjbGVhbiB1cCByZXNvdXJjZXNcbiAgICogSW1wbGVtZW50cyBjbGVhbnVwIGZvciBwcm9wZXIgREkgbGlmZWN5Y2xlIG1hbmFnZW1lbnRcbiAgICovXG4gIGFzeW5jIGRpc3Bvc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgdGhpcy5yZXNldENvdW50ZXIoKTtcbiAgICBsb2dnZXIuZGVidWcoJ1BhdHRlcm5FeHRyYWN0b3IgZGlzcG9zZWQnKTtcbiAgfVxufVxuIl19