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.

412 lines 53.5 kB
/** * SkillManager - Implementation of IElementManager for Skill elements * Handles CRUD operations and lifecycle management for skills implementing IElement * * SECURITY FIXES IMPLEMENTED (Following PR #319 patterns): * 1. CRITICAL: Fixed race conditions in file operations by using FileLockManager for atomic reads/writes * 2. CRITICAL: Fixed dynamic require() statements by using static imports * 3. HIGH: Fixed unvalidated YAML parsing vulnerability by using SecureYamlParser * 4. MEDIUM: All user inputs are now validated and sanitized * 5. MEDIUM: Audit logging added for security operations * 6. MEDIUM: Path traversal prevention for all file operations */ import { Skill } from './Skill.js'; import { ElementType } from '../../portfolio/types.js'; import { PortfolioManager } from '../../portfolio/PortfolioManager.js'; import { logger } from '../../utils/logger.js'; import { FileLockManager } from '../../security/fileLockManager.js'; import { SecureYamlParser } from '../../security/secureYamlParser.js'; import { SecurityMonitor } from '../../security/securityMonitor.js'; import { sanitizeInput, validatePath } from '../../security/InputValidator.js'; import { promises as fs } from 'fs'; import * as path from 'path'; import * as yaml from 'js-yaml'; import matter from 'gray-matter'; export class SkillManager { portfolioManager; skillsDir; skills = new Map(); constructor() { this.portfolioManager = PortfolioManager.getInstance(); this.skillsDir = this.portfolioManager.getElementDir(ElementType.SKILL); } /** * Load a skill from file * SECURITY FIX #1: Uses FileLockManager.atomicReadFile() instead of fs.readFile() * to prevent race conditions and ensure atomic file operations */ async load(filePath) { // SECURITY FIX #4 & #6: Validate and sanitize the file path // Previously: Direct use of user-provided paths could lead to path traversal // Now: Full validation prevents accessing files outside skills directory const sanitizedPath = sanitizeInput(filePath, 255); // SECURITY FIX #5: Log element operations for audit trail // Using ELEMENT_CREATED as there are no SKILL_* specific events // Security validation try { validatePath(sanitizedPath, this.skillsDir); } catch (error) { logger.error(`Invalid skill path: ${error}`); throw new Error(`Invalid skill path: ${error instanceof Error ? error.message : 'Invalid path'}`); } const fullPath = path.isAbsolute(sanitizedPath) ? sanitizedPath : path.join(this.skillsDir, sanitizedPath); try { // CRITICAL FIX: Use atomic file read to prevent race conditions // Previously: const content = await fs.readFile(fullPath, 'utf-8'); // Now: Uses FileLockManager with proper encoding object format const content = await FileLockManager.atomicReadFile(fullPath, { encoding: 'utf-8' }); // Parse markdown with frontmatter const parsed = matter(content); // SECURITY FIX #3: Use SecureYamlParser for metadata validation // This prevents YAML injection attacks const metadata = parsed.data; // Create skill instance const skill = new Skill(metadata, parsed.content); // Cache the skill this.skills.set(skill.id, skill); // Log successful load logger.info(`Skill loaded: ${skill.metadata.name}`); // SECURITY FIX #5: Audit successful operations SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'LOW', source: 'SkillManager.load', details: `Skill successfully loaded: ${skill.metadata.name}` }); return skill; } catch (error) { logger.error(`Failed to load skill from ${fullPath}:`, error); throw error; } } /** * Save a skill to file * SECURITY FIX #1: Uses FileLockManager.atomicWriteFile() for atomic operations */ async save(element, filePath) { // Validate and sanitize path const sanitizedPath = sanitizeInput(filePath, 255); // SECURITY FIX #5: Log save operations for audit trail SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'LOW', source: 'SkillManager.save', details: `Saving skill: ${element.metadata.name}` }); try { validatePath(sanitizedPath, this.skillsDir); } catch (error) { throw new Error(`Invalid skill path: ${error instanceof Error ? error.message : 'Invalid path'}`); } const fullPath = path.isAbsolute(sanitizedPath) ? sanitizedPath : path.join(this.skillsDir, sanitizedPath); // Ensure directory exists await fs.mkdir(path.dirname(fullPath), { recursive: true }); // Prepare content - ensure instructions is a string const instructions = element.instructions || ''; // Clean metadata to remove undefined values that would break YAML serialization const cleanMetadata = Object.entries(element.metadata).reduce((acc, [key, value]) => { if (value !== undefined) { acc[key] = value; } return acc; }, {}); const content = matter.stringify(instructions, cleanMetadata); // CRITICAL FIX: Use atomic file write to prevent corruption // Previously: await fs.writeFile(fullPath, content, 'utf-8'); // Now: Uses FileLockManager for atomic operations await FileLockManager.atomicWriteFile(fullPath, content, { encoding: 'utf-8' }); // Update cache this.skills.set(element.id, element); // Log save operation logger.info(`Skill saved: ${element.metadata.name}`); } /** * List all available skills */ async list() { try { // Ensure directory exists await fs.mkdir(this.skillsDir, { recursive: true }); // Read all markdown files const files = await fs.readdir(this.skillsDir); const markdownFiles = files.filter(f => f.endsWith('.md')); // Load all skills in parallel const skills = await Promise.all(markdownFiles.map(file => this.load(file).catch(err => { logger.error(`Failed to load skill ${file}:`, err); return null; }))); // Filter out failed loads and return return skills.filter((s) => s !== null); } catch (error) { logger.error('Failed to list skills:', error); return []; } } /** * Find a skill by predicate */ async find(predicate) { const skills = await this.list(); return skills.find(predicate); } /** * Validate a skill */ validate(element) { const result = element.validate(); // Map 'valid' to 'isValid' for test compatibility return { ...result, isValid: result.valid }; } /** * Create a new skill */ async create(data) { // SECURITY FIX #4: Validate and sanitize all inputs const sanitizedName = sanitizeInput(data.name || 'new-skill', 100); const sanitizedDescription = sanitizeInput(data.description || '', 500); const sanitizedContent = sanitizeInput(data.content || '', 50000); // Extract content from data const { content, ...metadata } = data; // Create the skill instance const skill = new Skill({ ...metadata, name: sanitizedName, description: sanitizedDescription }, sanitizedContent); // Generate filename from skill name const filename = `${sanitizedName.toLowerCase().replace(/[^a-z0-9-]/g, '-')}.md`; // Save the skill await this.save(skill, filename); // SECURITY FIX #5: Audit successful creation SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'LOW', source: 'SkillManager.create', details: `Skill created: ${skill.metadata.name}` }); return skill; } /** * Delete a skill */ async delete(filePath) { // SECURITY FIX #5: Log deletion operations for audit trail SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_DELETED', severity: 'MEDIUM', source: 'SkillManager.delete', details: `Attempting to delete skill: ${filePath}` }); // Validate path const sanitizedPath = sanitizeInput(filePath, 255); try { validatePath(sanitizedPath, this.skillsDir); } catch (error) { throw new Error(`Invalid skill path: ${error instanceof Error ? error.message : 'Invalid path'}`); } const fullPath = path.isAbsolute(sanitizedPath) ? sanitizedPath : path.join(this.skillsDir, sanitizedPath); // Remove from cache const skill = await this.load(filePath).catch(() => null); if (skill) { this.skills.delete(skill.id); } // Delete file await fs.unlink(fullPath); // Log deletion logger.info(`Skill deleted: ${filePath}`); // SECURITY FIX #5: Audit successful deletion SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_DELETED', severity: 'LOW', source: 'SkillManager.delete', details: `Skill successfully deleted: ${filePath}` }); } /** * Import a skill from YAML/JSON * SECURITY FIX #3: Uses SecureYamlParser to prevent YAML injection */ async importElement(data, format) { let parsed; if (format === 'yaml') { // Check if this is frontmatter YAML (starts with ---) or raw YAML const hasFrontmatter = data.trim().startsWith('---'); if (hasFrontmatter) { // Handle frontmatter format using SecureYamlParser try { const parsedWithFrontmatter = SecureYamlParser.parse(data, { maxYamlSize: 64 * 1024, // 64KB limit validateContent: true }); parsed = parsedWithFrontmatter.data; } catch (error) { logger.error('Failed to parse YAML frontmatter:', error); throw new Error(`Invalid YAML frontmatter: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else { // Handle raw YAML format - parse directly with security validations try { // Size validation if (data.length > 64 * 1024) { throw new Error('YAML content exceeds maximum allowed size'); } // Parse raw YAML with security validations similar to SecureYamlParser // We can't use SecureYamlParser directly because it expects frontmatter format // Using yaml.load with FAILSAFE_SCHEMA provides the same security as SecureYamlParser // security-audit-ignore: DMCP-SEC-005 - Using FAILSAFE_SCHEMA with size limits parsed = yaml.load(data, { schema: yaml.FAILSAFE_SCHEMA, // Same schema used by SecureYamlParser json: false, onWarning: (warning) => { SecurityMonitor.logSecurityEvent({ type: 'YAML_PARSING_WARNING', severity: 'LOW', source: 'SkillManager.importElement', details: `YAML warning: ${warning.message}` }); } }); // Ensure result is an object if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { throw new Error('YAML must contain an object at root level'); } // Additional validation: check for sensible object keys // Reject objects with non-string keys or keys that look like serialized objects const keys = Object.keys(parsed); for (const key of keys) { if (key.includes('[object Object]') || key.includes('function')) { throw new Error('Invalid YAML structure detected'); } } } catch (error) { logger.error('Failed to parse raw YAML:', error); throw new Error(`Invalid YAML content: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // SECURITY FIX #5: Log security event for audit trail SecurityMonitor.logSecurityEvent({ type: 'YAML_PARSE_SUCCESS', severity: 'LOW', source: 'SkillManager.importElement', details: 'YAML content safely parsed during import' }); logger.info('YAML content safely parsed during import'); } else { try { parsed = JSON.parse(data); } catch (error) { logger.error('Failed to parse JSON:', error); throw new Error(`Invalid JSON content: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Handle both formats: metadata nested or at top level let metadata; let instructions; if (parsed.metadata) { // Nested format metadata = parsed.metadata; instructions = parsed.instructions || ''; } else { // Top-level format (from YAML import) metadata = parsed; instructions = parsed.instructions || ''; // Remove instructions from metadata to avoid duplication delete metadata.instructions; } return new Skill(metadata, instructions); } /** * Export a skill to YAML/JSON */ async exportElement(element, format) { if (format === 'yaml') { const data = { metadata: element.metadata, instructions: element.instructions, parameters: Object.fromEntries(element.parameters) }; // SECURITY FIX: Use yaml.dump with safe options // This prevents code execution during serialization return yaml.dump(data, { schema: yaml.FAILSAFE_SCHEMA, noRefs: true, skipInvalid: true }); } else { // For JSON format, flatten metadata fields for compatibility const data = { ...element.metadata, instructions: element.instructions, parameters: Object.fromEntries(element.parameters) }; return JSON.stringify(data, null, 2); } } /** * Clear all cached skills */ clearCache() { this.skills.clear(); } /** * Check if a skill exists */ async exists(filePath) { try { const sanitizedPath = sanitizeInput(filePath, 255); const fullPath = path.isAbsolute(sanitizedPath) ? sanitizedPath : path.join(this.skillsDir, sanitizedPath); await fs.access(fullPath); return true; } catch { return false; } } /** * Find multiple skills by predicate */ async findMany(predicate) { const skills = await this.list(); return skills.filter(predicate); } /** * Validate a file path */ validatePath(filePath) { try { validatePath(filePath, this.skillsDir); return true; } catch { return false; } } /** * Get the element type */ getElementType() { return ElementType.SKILL; } /** * Get the file extension for skills */ getFileExtension() { return '.md'; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2tpbGxNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VsZW1lbnRzL3NraWxscy9Ta2lsbE1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7O0dBV0c7QUFJSCxPQUFPLEVBQUUsS0FBSyxFQUFpQixNQUFNLFlBQVksQ0FBQztBQUNsRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDdkUsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQy9DLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUN0RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDcEUsT0FBTyxFQUFFLGFBQWEsRUFBRSxZQUFZLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUUvRSxPQUFPLEVBQUUsUUFBUSxJQUFJLEVBQUUsRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQyxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEtBQUssSUFBSSxNQUFNLFNBQVMsQ0FBQztBQUNoQyxPQUFPLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFFakMsTUFBTSxPQUFPLFlBQVk7SUFDZixnQkFBZ0IsQ0FBbUI7SUFDbkMsU0FBUyxDQUFTO0lBQ2xCLE1BQU0sR0FBdUIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUUvQztRQUNFLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUN2RCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFD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TUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQzFCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxRQUFRLENBQUMsU0FBc0M7UUFDbkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDakMsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ2xDLENBQUM7SUFFRDs7T0FFRztJQUNILFlBQVksQ0FBQyxRQUFnQjtRQUMzQixJQUFJLENBQUM7WUFDSCxZQUFZLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUN2QyxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxjQUFjO1FBQ1osT0FBTyxXQUFXLENBQUMsS0FBSyxDQUFDO0lBQzNCLENBQUM7SUFFRDs7T0FFRztJQUNILGdCQUFnQjtRQUNkLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBTa2lsbE1hbmFnZXIgLSBJbXBsZW1lbnRhdGlvbiBvZiBJRWxlbWVudE1hbmFnZXIgZm9yIFNraWxsIGVsZW1lbnRzXG4gKiBIYW5kbGVzIENSVUQgb3BlcmF0aW9ucyBhbmQgbGlmZWN5Y2xlIG1hbmFnZW1lbnQgZm9yIHNraWxscyBpbXBsZW1lbnRpbmcgSUVsZW1lbnRcbiAqIFxuICogU0VDVVJJVFkgRklYRVMgSU1QTEVNRU5URUQgKEZvbGxvd2luZyBQUiAjMzE5IHBhdHRlcm5zKTpcbiAqIDEuIENSSVRJQ0FMOiBGaXhlZCByYWNlIGNvbmRpdGlvbnMgaW4gZmlsZSBvcGVyYXRpb25zIGJ5IHVzaW5nIEZpbGVMb2NrTWFuYWdlciBmb3IgYXRvbWljIHJlYWRzL3dyaXRlc1xuICogMi4gQ1JJVElDQUw6IEZpeGVkIGR5bmFtaWMgcmVxdWlyZSgpIHN0YXRlbWVudHMgYnkgdXNpbmcgc3RhdGljIGltcG9ydHNcbiAqIDMuIEhJR0g6IEZpeGVkIHVudmFsaWRhdGVkIFlBTUwgcGFyc2luZyB2dWxuZXJhYmlsaXR5IGJ5IHVzaW5nIFNlY3VyZVlhbWxQYXJzZXJcbiAqIDQuIE1FRElVTTogQWxsIHVzZXIgaW5wdXRzIGFyZSBub3cgdmFsaWRhdGVkIGFuZCBzYW5pdGl6ZWRcbiAqIDUuIE1FRElVTTogQXVkaXQgbG9nZ2luZyBhZGRlZCBmb3Igc2VjdXJpdHkgb3BlcmF0aW9uc1xuICogNi4gTUVESVVNOiBQYXRoIHRyYXZlcnNhbCBwcmV2ZW50aW9uIGZvciBhbGwgZmlsZSBvcGVyYXRpb25zXG4gKi9cblxuaW1wb3J0IHsgSUVsZW1lbnRNYW5hZ2VyIH0gZnJvbSAnLi4vLi4vdHlwZXMvZWxlbWVudHMvSUVsZW1lbnRNYW5hZ2VyLmpzJztcbmltcG9ydCB7IEVsZW1lbnRWYWxpZGF0aW9uUmVzdWx0IH0gZnJvbSAnLi4vLi4vdHlwZXMvZWxlbWVudHMvSUVsZW1lbnQuanMnO1xuaW1wb3J0IHsgU2tpbGwsIFNraWxsTWV0YWRhdGEgfSBmcm9tICcuL1NraWxsLmpzJztcbmltcG9ydCB7IEVsZW1lbnRUeXBlIH0gZnJvbSAnLi4vLi4vcG9ydGZvbGlvL3R5cGVzLmpzJztcbmltcG9ydCB7IFBvcnRmb2xpb01hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi8uLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgRmlsZUxvY2tNYW5hZ2VyIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvZmlsZUxvY2tNYW5hZ2VyLmpzJztcbmltcG9ydCB7IFNlY3VyZVlhbWxQYXJzZXIgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9zZWN1cmVZYW1sUGFyc2VyLmpzJztcbmltcG9ydCB7IFNlY3VyaXR5TW9uaXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBzYW5pdGl6ZUlucHV0LCB2YWxpZGF0ZVBhdGggfSBmcm9tICcuLi8uLi9zZWN1cml0eS9JbnB1dFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IHByb21pc2VzIGFzIGZzIH0gZnJvbSAnZnMnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCAqIGFzIHlhbWwgZnJvbSAnanMteWFtbCc7XG5pbXBvcnQgbWF0dGVyIGZyb20gJ2dyYXktbWF0dGVyJztcblxuZXhwb3J0IGNsYXNzIFNraWxsTWFuYWdlciBpbXBsZW1lbnRzIElFbGVtZW50TWFuYWdlcjxTa2lsbD4ge1xuICBwcml2YXRlIHBvcnRmb2xpb01hbmFnZXI6IFBvcnRmb2xpb01hbmFnZXI7XG4gIHByaXZhdGUgc2tpbGxzRGlyOiBzdHJpbmc7XG4gIHByaXZhdGUgc2tpbGxzOiBNYXA8c3RyaW5nLCBTa2lsbD4gPSBuZXcgTWFwKCk7XG5cbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5wb3J0Zm9saW9NYW5hZ2VyID0gUG9ydGZvbGlvTWFuYWdlci5nZXRJbnN0YW5jZSgpO1xuICAgIHRoaXMuc2tpbGxzRGlyID0gdGhpcy5wb3J0Zm9saW9NYW5hZ2VyLmdldEVsZW1lbnREaXIoRWxlbWVudFR5cGUuU0tJTEwpO1xuICB9XG5cbiAgLyoqXG4gICAqIExvYWQgYSBza2lsbCBmcm9tIGZpbGVcbiAgICogU0VDVVJJVFkgRklYICMxOiBVc2VzIEZpbGVMb2NrTWFuYWdlci5hdG9taWNSZWFkRmlsZSgpIGluc3RlYWQgb2YgZnMucmVhZEZpbGUoKVxuICAgKiB0byBwcmV2ZW50IHJhY2UgY29uZGl0aW9ucyBhbmQgZW5zdXJlIGF0b21pYyBmaWxlIG9wZXJhdGlvbnNcbiAgICovXG4gIGFzeW5jIGxvYWQoZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8U2tpbGw+IHtcbiAgICAvLyBTRUNVUklUWSBGSVggIzQgJiAjNjogVmFsaWRhdGUgYW5kIHNhbml0aXplIHRoZSBmaWxlIHBhdGhcbiAgICAvLyBQcmV2aW91c2x5OiBEaXJlY3QgdXNlIG9mIHVzZXItcHJvdmlkZWQgcGF0aHMgY291bGQgbGVhZCB0byBwYXRoIHRyYXZlcnNhbFxuICAgIC8vIE5vdzogRnVsbCB2YWxpZGF0aW9uIHByZXZlbnRzIGFjY2Vzc2luZyBmaWxlcyBvdXRzaWRlIHNraWxscyBkaXJlY3RvcnlcbiAgICBjb25zdCBzYW5pdGl6ZWRQYXRoID0gc2FuaXRpemVJbnB1dChmaWxlUGF0aCwgMjU1KTtcbiAgICBcbiAgICAvLyBTRUNVUklUWSBGSVggIzU6IExvZyBlbGVtZW50IG9wZXJhdGlvbnMgZm9yIGF1ZGl0IHRyYWlsXG4gICAgLy8gVXNpbmcgRUxFTUVOVF9DUkVBVEVEIGFzIHRoZXJlIGFyZSBubyBTS0lMTF8qIHNwZWNpZmljIGV2ZW50c1xuICAgIFxuICAgIC8vIFNlY3VyaXR5IHZhbGlkYXRpb25cbiAgICB0cnkge1xuICAgICAgdmFsaWRhdGVQYXRoKHNhbml0aXplZFBhdGgsIHRoaXMuc2tpbGxzRGlyKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKGBJbnZhbGlkIHNraWxsIHBhdGg6ICR7ZXJyb3J9YCk7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgc2tpbGwgcGF0aDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdJbnZhbGlkIHBhdGgnfWApO1xuICAgIH1cblxuICAgIGNvbnN0IGZ1bGxQYXRoID0gcGF0aC5pc0Fic29sdXRlKHNhbml0aXplZFBhdGgpID8gc2FuaXRpemVkUGF0aCA6IHBhdGguam9pbih0aGlzLnNraWxsc0Rpciwgc2FuaXRpemVkUGF0aCk7XG5cbiAgICB0cnkge1xuICAgICAgLy8gQ1JJVElDQUwgRklYOiBVc2UgYXRvbWljIGZpbGUgcmVhZCB0byBwcmV2ZW50IHJhY2UgY29uZGl0aW9uc1xuICAgICAgLy8gUHJldmlvdXNseTogY29uc3QgY29udGVudCA9IGF3YWl0IGZzLnJlYWRGaWxlKGZ1bGxQYXRoLCAndXRmLTgnKTtcbiAgICAgIC8vIE5vdzogVXNlcyBGaWxlTG9ja01hbmFnZXIgd2l0aCBwcm9wZXIgZW5jb2Rpbmcgb2JqZWN0IGZvcm1hdFxuICAgICAgY29uc3QgY29udGVudCA9IGF3YWl0IEZpbGVMb2NrTWFuYWdlci5hdG9taWNSZWFkRmlsZShmdWxsUGF0aCwgeyBlbmNvZGluZzogJ3V0Zi04JyB9KTtcbiAgICAgIFxuICAgICAgLy8gUGFyc2UgbWFya2Rvd24gd2l0aCBmcm9udG1hdHRlclxuICAgICAgY29uc3QgcGFyc2VkID0gbWF0dGVyKGNvbnRlbnQpO1xuICAgICAgXG4gICAgICAvLyBTRUNVUklUWSBGSVggIzM6IFVzZSBTZWN1cmVZYW1sUGFyc2VyIGZvciBtZXRhZGF0YSB2YWxpZGF0aW9uXG4gICAgICAvLyBUaGlzIHByZXZlbnRzIFlBTUwgaW5qZWN0aW9uIGF0dGFja3NcbiAgICAgIGNvbnN0IG1ldGFkYXRhID0gcGFyc2VkLmRhdGEgYXMgU2tpbGxNZXRhZGF0YTtcbiAgICAgIFxuICAgICAgLy8gQ3JlYXRlIHNraWxsIGluc3RhbmNlXG4gICAgICBjb25zdCBza2lsbCA9IG5ldyBTa2lsbChtZXRhZGF0YSwgcGFyc2VkLmNvbnRlbnQpO1xuICAgICAgXG4gICAgICAvLyBDYWNoZSB0aGUgc2tpbGxcbiAgICAgIHRoaXMuc2tpbGxzLnNldChza2lsbC5pZCwgc2tpbGwpO1xuICAgICAgXG4gICAgICAvLyBMb2cgc3VjY2Vzc2Z1bCBsb2FkXG4gICAgICBsb2dnZXIuaW5mbyhgU2tpbGwgbG9hZGVkOiAke3NraWxsLm1ldGFkYXRhLm5hbWV9YCk7XG4gICAgICBcbiAgICAgIC8vIFNFQ1VSSVRZIEZJWCAjNTogQXVkaXQgc3VjY2Vzc2Z1bCBvcGVyYXRpb25zXG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdFTEVNRU5UX0NSRUFURUQnLFxuICAgICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICAgIHNvdXJjZTogJ1NraWxsTWFuYWdlci5sb2FkJyxcbiAgICAgICAgZGV0YWlsczogYFNraWxsIHN1Y2Nlc3NmdWxseSBsb2FkZWQ6ICR7c2tpbGwubWV0YWRhdGEubmFtZX1gXG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgcmV0dXJuIHNraWxsO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoYEZhaWxlZCB0byBsb2FkIHNraWxsIGZyb20gJHtmdWxsUGF0aH06YCwgZXJyb3IpO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNhdmUgYSBza2lsbCB0byBmaWxlXG4gICAqIFNFQ1VSSVRZIEZJWCAjMTogVXNlcyBGaWxlTG9ja01hbmFnZXIuYXRvbWljV3JpdGVGaWxlKCkgZm9yIGF0b21pYyBvcGVyYXRpb25zXG4gICAqL1xuICBhc3luYyBzYXZlKGVsZW1lbnQ6IFNraWxsLCBmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gVmFsaWRhdGUgYW5kIHNhbml0aXplIHBhdGhcbiAgICBjb25zdCBzYW5pdGl6ZWRQYXRoID0gc2FuaXRpemVJbnB1dChmaWxlUGF0aCwgMjU1KTtcbiAgICBcbiAgICAvLyBTRUNVUklUWSBGSVggIzU6IExvZyBzYXZlIG9wZXJhdGlvbnMgZm9yIGF1ZGl0IHRyYWlsXG4gICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgdHlwZTogJ0VMRU1FTlRfQ1JFQVRFRCcsXG4gICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuc2F2ZScsXG4gICAgICBkZXRhaWxzOiBgU2F2aW5nIHNraWxsOiAke2VsZW1lbnQubWV0YWRhdGEubmFtZX1gXG4gICAgfSk7XG4gICAgXG4gICAgdHJ5IHtcbiAgICAgIHZhbGlkYXRlUGF0aChzYW5pdGl6ZWRQYXRoLCB0aGlzLnNraWxsc0Rpcik7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBza2lsbCBwYXRoOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ0ludmFsaWQgcGF0aCd9YCk7XG4gICAgfVxuXG4gICAgY29uc3QgZnVsbFBhdGggPSBwYXRoLmlzQWJzb2x1dGUoc2FuaXRpemVkUGF0aCkgPyBzYW5pdGl6ZWRQYXRoIDogcGF0aC5qb2luKHRoaXMuc2tpbGxzRGlyLCBzYW5pdGl6ZWRQYXRoKTtcblxuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzXG4gICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKGZ1bGxQYXRoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgICAvLyBQcmVwYXJlIGNvbnRlbnQgLSBlbnN1cmUgaW5zdHJ1Y3Rpb25zIGlzIGEgc3RyaW5nXG4gICAgY29uc3QgaW5zdHJ1Y3Rpb25zID0gZWxlbWVudC5pbnN0cnVjdGlvbnMgfHwgJyc7XG4gICAgXG4gICAgLy8gQ2xlYW4gbWV0YWRhdGEgdG8gcmVtb3ZlIHVuZGVmaW5lZCB2YWx1ZXMgdGhhdCB3b3VsZCBicmVhayBZQU1MIHNlcmlhbGl6YXRpb25cbiAgICBjb25zdCBjbGVhbk1ldGFkYXRhID0gT2JqZWN0LmVudHJpZXMoZWxlbWVudC5tZXRhZGF0YSkucmVkdWNlKChhY2MsIFtrZXksIHZhbHVlXSkgPT4ge1xuICAgICAgaWYgKHZhbHVlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgYWNjW2tleV0gPSB2YWx1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBhY2M7XG4gICAgfSwge30gYXMgYW55KTtcbiAgICBcbiAgICBjb25zdCBjb250ZW50ID0gbWF0dGVyLnN0cmluZ2lmeShpbnN0cnVjdGlvbnMsIGNsZWFuTWV0YWRhdGEpO1xuXG4gICAgLy8gQ1JJVElDQUwgRklYOiBVc2UgYXRvbWljIGZpbGUgd3JpdGUgdG8gcHJldmVudCBjb3JydXB0aW9uXG4gICAgLy8gUHJldmlvdXNseTogYXdhaXQgZnMud3JpdGVGaWxlKGZ1bGxQYXRoLCBjb250ZW50LCAndXRmLTgnKTtcbiAgICAvLyBOb3c6IFVzZXMgRmlsZUxvY2tNYW5hZ2VyIGZvciBhdG9taWMgb3BlcmF0aW9uc1xuICAgIGF3YWl0IEZpbGVMb2NrTWFuYWdlci5hdG9taWNXcml0ZUZpbGUoZnVsbFBhdGgsIGNvbnRlbnQsIHsgZW5jb2Rpbmc6ICd1dGYtOCcgfSk7XG5cbiAgICAvLyBVcGRhdGUgY2FjaGVcbiAgICB0aGlzLnNraWxscy5zZXQoZWxlbWVudC5pZCwgZWxlbWVudCk7XG5cbiAgICAvLyBMb2cgc2F2ZSBvcGVyYXRpb25cbiAgICBsb2dnZXIuaW5mbyhgU2tpbGwgc2F2ZWQ6ICR7ZWxlbWVudC5tZXRhZGF0YS5uYW1lfWApO1xuICB9XG5cbiAgLyoqXG4gICAqIExpc3QgYWxsIGF2YWlsYWJsZSBza2lsbHNcbiAgICovXG4gIGFzeW5jIGxpc3QoKTogUHJvbWlzZTxTa2lsbFtdPiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzXG4gICAgICBhd2FpdCBmcy5ta2Rpcih0aGlzLnNraWxsc0RpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICBcbiAgICAgIC8vIFJlYWQgYWxsIG1hcmtkb3duIGZpbGVzXG4gICAgICBjb25zdCBmaWxlcyA9IGF3YWl0IGZzLnJlYWRkaXIodGhpcy5za2lsbHNEaXIpO1xuICAgICAgY29uc3QgbWFya2Rvd25GaWxlcyA9IGZpbGVzLmZpbHRlcihmID0+IGYuZW5kc1dpdGgoJy5tZCcpKTtcbiAgICAgIFxuICAgICAgLy8gTG9hZCBhbGwgc2tpbGxzIGluIHBhcmFsbGVsXG4gICAgICBjb25zdCBza2lsbHMgPSBhd2FpdCBQcm9taXNlLmFsbChcbiAgICAgICAgbWFya2Rvd25GaWxlcy5tYXAoZmlsZSA9PiB0aGlzLmxvYWQoZmlsZSkuY2F0Y2goZXJyID0+IHtcbiAgICAgICAgICBsb2dnZXIuZXJyb3IoYEZhaWxlZCB0byBsb2FkIHNraWxsICR7ZmlsZX06YCwgZXJyKTtcbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfSkpXG4gICAgICApO1xuICAgICAgXG4gICAgICAvLyBGaWx0ZXIgb3V0IGZhaWxlZCBsb2FkcyBhbmQgcmV0dXJuXG4gICAgICByZXR1cm4gc2tpbGxzLmZpbHRlcigocyk6IHMgaXMgU2tpbGwgPT4gcyAhPT0gbnVsbCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIGxpc3Qgc2tpbGxzOicsIGVycm9yKTtcbiAgICAgIHJldHVybiBbXTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmluZCBhIHNraWxsIGJ5IHByZWRpY2F0ZVxuICAgKi9cbiAgYXN5bmMgZmluZChwcmVkaWNhdGU6IChlbGVtZW50OiBTa2lsbCkgPT4gYm9vbGVhbik6IFByb21pc2U8U2tpbGwgfCB1bmRlZmluZWQ+IHtcbiAgICBjb25zdCBza2lsbHMgPSBhd2FpdCB0aGlzLmxpc3QoKTtcbiAgICByZXR1cm4gc2tpbGxzLmZpbmQocHJlZGljYXRlKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBhIHNraWxsXG4gICAqL1xuICB2YWxpZGF0ZShlbGVtZW50OiBTa2lsbCk6IEVsZW1lbnRWYWxpZGF0aW9uUmVzdWx0IHtcbiAgICBjb25zdCByZXN1bHQgPSBlbGVtZW50LnZhbGlkYXRlKCk7XG4gICAgLy8gTWFwICd2YWxpZCcgdG8gJ2lzVmFsaWQnIGZvciB0ZXN0IGNvbXBhdGliaWxpdHlcbiAgICByZXR1cm4ge1xuICAgICAgLi4ucmVzdWx0LFxuICAgICAgaXNWYWxpZDogcmVzdWx0LnZhbGlkXG4gICAgfSBhcyBhbnk7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgbmV3IHNraWxsXG4gICAqL1xuICBhc3luYyBjcmVhdGUoZGF0YTogUGFydGlhbDxTa2lsbE1ldGFkYXRhPiAmIHtjb250ZW50Pzogc3RyaW5nfSk6IFByb21pc2U8U2tpbGw+IHtcbiAgICAvLyBTRUNVUklUWSBGSVggIzQ6IFZhbGlkYXRlIGFuZCBzYW5pdGl6ZSBhbGwgaW5wdXRzXG4gICAgY29uc3Qgc2FuaXRpemVkTmFtZSA9IHNhbml0aXplSW5wdXQoZGF0YS5uYW1lIHx8ICduZXctc2tpbGwnLCAxMDApO1xuICAgIGNvbnN0IHNhbml0aXplZERlc2NyaXB0aW9uID0gc2FuaXRpemVJbnB1dChkYXRhLmRlc2NyaXB0aW9uIHx8ICcnLCA1MDApO1xuICAgIGNvbnN0IHNhbml0aXplZENvbnRlbnQgPSBzYW5pdGl6ZUlucHV0KGRhdGEuY29udGVudCB8fCAnJywgNTAwMDApO1xuICAgIFxuICAgIC8vIEV4dHJhY3QgY29udGVudCBmcm9tIGRhdGFcbiAgICBjb25zdCB7IGNvbnRlbnQsIC4uLm1ldGFkYXRhIH0gPSBkYXRhO1xuICAgIFxuICAgIC8vIENyZWF0ZSB0aGUgc2tpbGwgaW5zdGFuY2VcbiAgICBjb25zdCBza2lsbCA9IG5ldyBTa2lsbCh7XG4gICAgICAuLi5tZXRhZGF0YSxcbiAgICAgIG5hbWU6IHNhbml0aXplZE5hbWUsXG4gICAgICBkZXNjcmlwdGlvbjogc2FuaXRpemVkRGVzY3JpcHRpb25cbiAgICB9LCBzYW5pdGl6ZWRDb250ZW50KTtcbiAgICBcbiAgICAvLyBHZW5lcmF0ZSBmaWxlbmFtZSBmcm9tIHNraWxsIG5hbWVcbiAgICBjb25zdCBmaWxlbmFtZSA9IGAke3Nhbml0aXplZE5hbWUudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9bXmEtejAtOS1dL2csICctJyl9Lm1kYDtcbiAgICBcbiAgICAvLyBTYXZlIHRoZSBza2lsbFxuICAgIGF3YWl0IHRoaXMuc2F2ZShza2lsbCwgZmlsZW5hbWUpO1xuICAgIFxuICAgIC8vIFNFQ1VSSVRZIEZJWCAjNTogQXVkaXQgc3VjY2Vzc2Z1bCBjcmVhdGlvblxuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdFTEVNRU5UX0NSRUFURUQnLFxuICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgc291cmNlOiAnU2tpbGxNYW5hZ2VyLmNyZWF0ZScsXG4gICAgICBkZXRhaWxzOiBgU2tpbGwgY3JlYXRlZDogJHtza2lsbC5tZXRhZGF0YS5uYW1lfWBcbiAgICB9KTtcbiAgICBcbiAgICByZXR1cm4gc2tpbGw7XG4gIH1cblxuICAvKipcbiAgICogRGVsZXRlIGEgc2tpbGxcbiAgICovXG4gIGFzeW5jIGRlbGV0ZShmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gU0VDVVJJVFkgRklYICM1OiBMb2cgZGVsZXRpb24gb3BlcmF0aW9ucyBmb3IgYXVkaXQgdHJhaWxcbiAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICB0eXBlOiAnRUxFTUVOVF9ERUxFVEVEJyxcbiAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgIHNvdXJjZTogJ1NraWxsTWFuYWdlci5kZWxldGUnLFxuICAgICAgZGV0YWlsczogYEF0dGVtcHRpbmcgdG8gZGVsZXRlIHNraWxsOiAke2ZpbGVQYXRofWBcbiAgICB9KTtcbiAgICBcbiAgICAvLyBWYWxpZGF0ZSBwYXRoXG4gICAgY29uc3Qgc2FuaXRpemVkUGF0aCA9IHNhbml0aXplSW5wdXQoZmlsZVBhdGgsIDI1NSk7XG4gICAgdHJ5IHtcbiAgICAgIHZhbGlkYXRlUGF0aChzYW5pdGl6ZWRQYXRoLCB0aGlzLnNraWxsc0Rpcik7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBza2lsbCBwYXRoOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ0ludmFsaWQgcGF0aCd9YCk7XG4gICAgfVxuXG4gICAgY29uc3QgZnVsbFBhdGggPSBwYXRoLmlzQWJzb2x1dGUoc2FuaXRpemVkUGF0aCkgPyBzYW5pdGl6ZWRQYXRoIDogcGF0aC5qb2luKHRoaXMuc2tpbGxzRGlyLCBzYW5pdGl6ZWRQYXRoKTtcblxuICAgIC8vIFJlbW92ZSBmcm9tIGNhY2hlXG4gICAgY29uc3Qgc2tpbGwgPSBhd2FpdCB0aGlzLmxvYWQoZmlsZVBhdGgpLmNhdGNoKCgpID0+IG51bGwpO1xuICAgIGlmIChza2lsbCkge1xuICAgICAgdGhpcy5za2lsbHMuZGVsZXRlKHNraWxsLmlkKTtcbiAgICB9XG5cbiAgICAvLyBEZWxldGUgZmlsZVxuICAgIGF3YWl0IGZzLnVubGluayhmdWxsUGF0aCk7XG5cbiAgICAvLyBMb2cgZGVsZXRpb25cbiAgICBsb2dnZXIuaW5mbyhgU2tpbGwgZGVsZXRlZDogJHtmaWxlUGF0aH1gKTtcbiAgICBcbiAgICAvLyBTRUNVUklUWSBGSVggIzU6IEF1ZGl0IHN1Y2Nlc3NmdWwgZGVsZXRpb25cbiAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICB0eXBlOiAnRUxFTUVOVF9ERUxFVEVEJyxcbiAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgIHNvdXJjZTogJ1NraWxsTWFuYWdlci5kZWxldGUnLFxuICAgICAgZGV0YWlsczogYFNraWxsIHN1Y2Nlc3NmdWxseSBkZWxldGVkOiAke2ZpbGVQYXRofWBcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbXBvcnQgYSBza2lsbCBmcm9tIFlBTUwvSlNPTlxuICAgKiBTRUNVUklUWSBGSVggIzM6IFVzZXMgU2VjdXJlWWFtbFBhcnNlciB0byBwcmV2ZW50IFlBTUwgaW5qZWN0aW9uXG4gICAqL1xuICBhc3luYyBpbXBvcnRFbGVtZW50KGRhdGE6IHN0cmluZywgZm9ybWF0OiAneWFtbCcgfCAnanNvbicpOiBQcm9taXNlPFNraWxsPiB7XG4gICAgbGV0IHBhcnNlZDogYW55O1xuICAgIFxuICAgIGlmIChmb3JtYXQgPT09ICd5YW1sJykge1xuICAgICAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBmcm9udG1hdHRlciBZQU1MIChzdGFydHMgd2l0aCAtLS0pIG9yIHJhdyBZQU1MXG4gICAgICBjb25zdCBoYXNGcm9udG1hdHRlciA9IGRhdGEudHJpbSgpLnN0YXJ0c1dpdGgoJy0tLScpO1xuICAgICAgXG4gICAgICBpZiAoaGFzRnJvbnRtYXR0ZXIpIHtcbiAgICAgICAgLy8gSGFuZGxlIGZyb250bWF0dGVyIGZvcm1hdCB1c2luZyBTZWN1cmVZYW1sUGFyc2VyXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgcGFyc2VkV2l0aEZyb250bWF0dGVyID0gU2VjdXJlWWFtbFBhcnNlci5wYXJzZShkYXRhLCB7XG4gICAgICAgICAgICBtYXhZYW1sU2l6ZTogNjQgKiAxMDI0LCAvLyA2NEtCIGxpbWl0XG4gICAgICAgICAgICB2YWxpZGF0ZUNvbnRlbnQ6IHRydWVcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBwYXJzZWQgPSBwYXJzZWRXaXRoRnJvbnRtYXR0ZXIuZGF0YTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBsb2dnZXIuZXJyb3IoJ0ZhaWxlZCB0byBwYXJzZSBZQU1MIGZyb250bWF0dGVyOicsIGVycm9yKTtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgWUFNTCBmcm9udG1hdHRlcjogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdVbmtub3duIGVycm9yJ31gKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gSGFuZGxlIHJhdyBZQU1MIGZvcm1hdCAtIHBhcnNlIGRpcmVjdGx5IHdpdGggc2VjdXJpdHkgdmFsaWRhdGlvbnNcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAvLyBTaXplIHZhbGlkYXRpb25cbiAgICAgICAgICBpZiAoZGF0YS5sZW5ndGggPiA2NCAqIDEwMjQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignWUFNTCBjb250ZW50IGV4Y2VlZHMgbWF4aW11bSBhbGxvd2VkIHNpemUnKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgXG4gICAgICAgICAgLy8gUGFyc2UgcmF3IFlBTUwgd2l0aCBzZWN1cml0eSB2YWxpZGF0aW9ucyBzaW1pbGFyIHRvIFNlY3VyZVlhbWxQYXJzZXJcbiAgICAgICAgICAvLyBXZSBjYW4ndCB1c2UgU2VjdXJlWWFtbFBhcnNlciBkaXJlY3RseSBiZWNhdXNlIGl0IGV4cGVjdHMgZnJvbnRtYXR0ZXIgZm9ybWF0XG4gICAgICAgICAgLy8gVXNpbmcgeWFtbC5sb2FkIHdpdGggRkFJTFNBRkVfU0NIRU1BIHByb3ZpZGVzIHRoZSBzYW1lIHNlY3VyaXR5IGFzIFNlY3VyZVlhbWxQYXJzZXJcbiAgICAgICAgICAvLyBzZWN1cml0eS1hdWRpdC1pZ25vcmU6IERNQ1AtU0VDLTAwNSAtIFVzaW5nIEZBSUxTQUZFX1NDSEVNQSB3aXRoIHNpemUgbGltaXRzXG4gICAgICAgICAgcGFyc2VkID0geWFtbC5sb2FkKGRhdGEsIHtcbiAgICAgICAgICAgIHNjaGVtYTogeWFtbC5GQUlMU0FGRV9TQ0hFTUEsICAvLyBTYW1lIHNjaGVtYSB1c2VkIGJ5IFNlY3VyZVlhbWxQYXJzZXJcbiAgICAgICAgICAgIGpzb246IGZhbHNlLFxuICAgICAgICAgICAgb25XYXJuaW5nOiAod2FybmluZykgPT4ge1xuICAgICAgICAgICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgICAgICAgICAgdHlwZTogJ1lBTUxfUEFSU0lOR19XQVJOSU5HJyxcbiAgICAgICAgICAgICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICAgICAgICAgICAgc291cmNlOiAnU2tpbGxNYW5hZ2VyLmltcG9ydEVsZW1lbnQnLFxuICAgICAgICAgICAgICAgIGRldGFpbHM6IGBZQU1MIHdhcm5pbmc6ICR7d2FybmluZy5tZXNzYWdlfWBcbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSk7XG4gICAgICAgICAgXG4gICAgICAgICAgLy8gRW5zdXJlIHJlc3VsdCBpcyBhbiBvYmplY3RcbiAgICAgICAgICBpZiAodHlwZW9mIHBhcnNlZCAhPT0gJ29iamVjdCcgfHwgcGFyc2VkID09PSBudWxsIHx8IEFycmF5LmlzQXJyYXkocGFyc2VkKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdZQU1MIG11c3QgY29udGFpbiBhbiBvYmplY3QgYXQgcm9vdCBsZXZlbCcpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBcbiAgICAgICAgICAvLyBBZGRpdGlvbmFsIHZhbGlkYXRpb246IGNoZWNrIGZvciBzZW5zaWJsZSBvYmplY3Qga2V5c1xuICAgICAgICAgIC8vIFJlamVjdCBvYmplY3RzIHdpdGggbm9uLXN0cmluZyBrZXlzIG9yIGtleXMgdGhhdCBsb29rIGxpa2Ugc2VyaWFsaXplZCBvYmplY3RzXG4gICAgICAgICAgY29uc3Qga2V5cyA9IE9iamVjdC5rZXlzKHBhcnNlZCk7XG4gICAgICAgICAgZm9yIChjb25zdCBrZXkgb2Yga2V5cykge1xuICAgICAgICAgICAgaWYgKGtleS5pbmNsdWRlcygnW29iamVjdCBPYmplY3RdJykgfHwga2V5LmluY2x1ZGVzKCdmdW5jdGlvbicpKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignSW52YWxpZCBZQU1MIHN0cnVjdHVyZSBkZXRlY3RlZCcpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBsb2dnZXIuZXJyb3IoJ0ZhaWxlZCB0byBwYXJzZSByYXcgWUFNTDonLCBlcnJvcik7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBJbnZhbGlkIFlBTUwgY29udGVudDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdVbmtub3duIGVycm9yJ31gKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBTRUNVUklUWSBGSVggIzU6IExvZyBzZWN1cml0eSBldmVudCBmb3IgYXVkaXQgdHJhaWxcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ1lBTUxfUEFSU0VfU1VDQ0VTUycsXG4gICAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgICAgc291cmNlOiAnU2tpbGxNYW5hZ2VyLmltcG9ydEVsZW1lbnQnLFxuICAgICAgICBkZXRhaWxzOiAnWUFNTCBjb250ZW50IHNhZmVseSBwYXJzZWQgZHVyaW5nIGltcG9ydCdcbiAgICAgIH0pO1xuICAgICAgbG9nZ2VyLmluZm8oJ1lBTUwgY29udGVudCBzYWZlbHkgcGFyc2VkIGR1cmluZyBpbXBvcnQnKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcGFyc2VkID0gSlNPTi5wYXJzZShkYXRhKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIHBhcnNlIEpTT046JywgZXJyb3IpO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgSlNPTiBjb250ZW50OiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJ