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.

439 lines 57.9 kB
/** * Danger Zone Enforcer * * Provides programmatic enforcement for DANGER_ZONE safety tier. * Maintains a list of blocked agent execution contexts and prevents * further tool execution until verification is completed. * * This addresses Issue #110 - the semantic-only approach is insufficient * for the highest risk tier, as a compromised LLM could ignore warnings. * * Issue #402: Refactored from singleton to DI-managed class with * file-based persistence. Blocks survive server restarts via * ~/.dollhouse/security/blocked-agents.json. * * @since v2.0.0 - Agentic Loop Completion (Epic #380) * @since v2.1.0 - File-based persistence (Issue #402) */ import os from 'os'; import path from 'path'; import fs from 'fs/promises'; import { logger } from '../utils/logger.js'; import { SecurityMonitor } from './securityMonitor.js'; import { EvictingQueue } from '../utils/EvictingQueue.js'; /** Rolling-window size for block-duration metrics (matches SecurityMonitor/LogManager). */ const METRICS_WINDOW_SIZE = 1000; /** * Validates agent name input * @throws Error if agent name is invalid */ function validateAgentName(agentName, operation) { if (!agentName || typeof agentName !== 'string') { throw new Error(`${operation}: Agent name is required`); } const trimmed = agentName.trim(); if (trimmed.length === 0) { throw new Error(`${operation}: Agent name cannot be empty or whitespace`); } // Prevent path traversal and special characters that could cause issues // eslint-disable-next-line no-control-regex -- Intentionally checking for control chars as security measure if (/[<>:"/\\|?*\x00-\x1f]/.test(trimmed)) { throw new Error(`${operation}: Agent name contains invalid characters`); } if (trimmed.length > 256) { throw new Error(`${operation}: Agent name exceeds maximum length of 256 characters`); } } /** * DI-managed class for danger zone enforcement with file-based persistence. * * Thread-safety note: This uses a Map which is not inherently thread-safe, * but in a Node.js single-threaded environment this is acceptable. * For multi-process deployments, consider Redis or similar. * * Persistence: Blocks are persisted to ~/.dollhouse/security/blocked-agents.json. * The in-memory Map is the hot path for check(); disk is the durable backing store. * Disk writes are fire-and-forget — disk failure does not block enforcement. */ export class DangerZoneEnforcer { blockedContexts = new Map(); // Metrics tracking totalBlocksSinceStartup = 0; totalUnblocksSinceStartup = 0; totalClearAllCalls = 0; blockDurations = new EvictingQueue(METRICS_WINDOW_SIZE); // Admin token for clearAll (can be set via environment or configuration) adminToken = process.env.DOLLHOUSE_DANGER_ZONE_ADMIN_TOKEN || null; // Persistence fileOps; securityDir; persistPath; constructor(fileOps, securityDir) { this.fileOps = fileOps; this.securityDir = securityDir ?? path.join(os.homedir(), '.dollhouse', 'security'); this.persistPath = path.join(this.securityDir, 'blocked-agents.json'); } /** * Load persisted blocks from disk. * Call once after construction to restore state from a previous session. * If the file is missing or corrupt, starts with empty blocks. * * @example * ```ts * const enforcer = new DangerZoneEnforcer(fileOps); * await enforcer.initialize(); // restores blocks from ~/.dollhouse/security/blocked-agents.json * ``` */ async initialize() { try { const content = await this.fileOps.readFile(this.persistPath); const data = JSON.parse(content); if (data.version === 1 && data.blocks && typeof data.blocks === 'object') { for (const [name, block] of Object.entries(data.blocks)) { this.blockedContexts.set(name, { agentName: name, reason: block.reason, triggeredPatterns: block.triggeredPatterns ?? [], blockedAt: new Date(block.blockedAt), verificationId: block.verificationId, }); } if (this.blockedContexts.size > 0) { const names = Array.from(this.blockedContexts.keys()); logger.warn(`Restored ${this.blockedContexts.size} danger zone block(s) from disk: ${names.join(', ')}`); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_DENIED', severity: 'MEDIUM', source: 'DangerZoneEnforcer.initialize', details: `Restored ${this.blockedContexts.size} danger zone block(s) from disk: ${names.join(', ')}`, additionalData: { restoredAgents: names, count: this.blockedContexts.size }, }); } } } catch (error) { if (error.code === 'ENOENT') { logger.debug('No blocked-agents.json found, starting with empty blocks'); } else { logger.warn('Failed to load blocked-agents.json, starting with empty blocks', { error }); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_DENIED', severity: 'MEDIUM', source: 'DangerZoneEnforcer.initialize', details: 'Failed to load blocked-agents.json — starting with empty blocks (possible data corruption)', additionalData: { error: String(error) }, }); } } } /** * Block an agent context due to danger zone trigger * * @param agentName - Name of the agent to block * @param reason - Why the agent is blocked * @param triggeredPatterns - Patterns that caused the block * @param verificationId - Optional verification ID for unblocking * @param auditContext - Optional execution context for audit trail (Issue #404) * @throws Error if agentName is invalid * * @example * ```ts * enforcer.block('code-reviewer', 'Danger zone pattern matched', ['rm -rf'], 'verify-abc'); * enforcer.check('code-reviewer').blocked; // true * ``` */ block(agentName, reason, triggeredPatterns, verificationId, auditContext) { validateAgentName(agentName, 'block'); const trimmed = agentName.trim(); const context = { agentName: trimmed, reason, triggeredPatterns, blockedAt: new Date(), verificationId, }; this.blockedContexts.set(trimmed, context); this.totalBlocksSinceStartup++; logger.warn(`Agent '${trimmed}' blocked: danger zone pattern matched (${triggeredPatterns.join(', ')})`, { agentName: trimmed, reason, triggeredPatterns, verificationId, }); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_DENIED', severity: 'HIGH', source: 'DangerZoneEnforcer.block', details: `Agent '${trimmed}' blocked: danger zone pattern matched (${triggeredPatterns.join(', ')})`, additionalData: { agentName: trimmed, reason, triggeredPatterns, verificationId, totalActiveBlocks: this.blockedContexts.size, // Issue #404: Audit context for post-hoc investigation stepNumber: auditContext?.stepNumber, currentStepDescription: auditContext?.currentStepDescription, currentStepOutcome: auditContext?.currentStepOutcome, nextActionHint: auditContext?.nextActionHint, riskScore: auditContext?.riskScore, goalDescription: auditContext?.goalDescription?.substring(0, 200), goalId: auditContext?.goalId, safetyFactors: auditContext?.safetyFactors, }, }); this.persistAsync(); } /** * Unblock an agent context after verification * * @param agentName - Name of the agent to unblock * @param verificationId - Verification ID that was completed * @returns Whether the unblock was successful * @throws Error if agentName is invalid * * @example * ```ts * enforcer.block('code-reviewer', 'Blocked', ['rm -rf'], 'verify-abc'); * * enforcer.unblock('code-reviewer', 'wrong-id'); // false — mismatch * enforcer.unblock('code-reviewer', 'verify-abc'); // true — success * ``` */ unblock(agentName, verificationId) { validateAgentName(agentName, 'unblock'); const normalizedName = agentName.trim(); const context = this.blockedContexts.get(normalizedName); if (!context) { logger.debug(`Unblock requested for agent '${normalizedName}' which is not currently blocked`, { agentName: normalizedName }); return true; // Not blocked, so "successfully" unblocked } // If verification was required, check that it matches if (context.verificationId && verificationId !== context.verificationId) { logger.warn(`Unblock failed for agent '${normalizedName}': verification ID mismatch (expected: ${context.verificationId}, got: ${verificationId ?? 'none'})`, { agentName: normalizedName, expected: context.verificationId, provided: verificationId, }); SecurityMonitor.logSecurityEvent({ type: 'VERIFICATION_FAILED', severity: 'HIGH', source: 'DangerZoneEnforcer.unblock', details: `Unblock denied for agent '${normalizedName}': verification ID mismatch (expected: ${context.verificationId}, got: ${verificationId ?? 'none'})`, additionalData: { agentName: normalizedName, expectedVerificationId: context.verificationId, providedVerificationId: verificationId ?? null, reason: 'verification_id_mismatch', }, }); return false; } const blockDuration = Date.now() - context.blockedAt.getTime(); this.blockedContexts.delete(normalizedName); this.totalUnblocksSinceStartup++; this.blockDurations.push(blockDuration); logger.info(`Agent '${normalizedName}' unblocked after verification (blocked for ${blockDuration}ms)`, { agentName: normalizedName, blockDurationMs: blockDuration, }); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_PAUSED', severity: 'MEDIUM', source: 'DangerZoneEnforcer.unblock', details: `Agent '${normalizedName}' unblocked after verification (blocked for ${blockDuration}ms)`, additionalData: { agentName: normalizedName, verificationId, blockDurationMs: blockDuration, }, }); this.persistAsync(); return true; } /** * Check if an agent context is blocked * * @param agentName - Name of the agent to check * @returns Block check result * @throws Error if agentName is invalid * * @example * ```ts * const result = enforcer.check('code-reviewer'); * if (result.blocked) { * console.log(result.reason); // why it was blocked * console.log(result.resolution); // how to unblock * } * ``` */ check(agentName) { validateAgentName(agentName, 'check'); const context = this.blockedContexts.get(agentName.trim()); if (!context) { return { blocked: false }; } return { blocked: true, reason: context.reason, // Issue #142 / #405: Actionable verify_challenge instructions resolution: context.verificationId ? `Use verify_challenge with params { challenge_id: "${context.verificationId}", code: "<code from screen>" } to unblock this agent.` : 'Contact administrator to enable danger zone operations', verificationId: context.verificationId, }; } /** * Check if any agent is currently blocked * Useful for diagnostics * * @example * ```ts * if (enforcer.hasBlockedAgents()) { * console.log('Blocked agents:', enforcer.getBlockedAgents()); * } * ``` */ hasBlockedAgents() { return this.blockedContexts.size > 0; } /** * Get list of all blocked agents * Useful for admin/diagnostic purposes * * @example * ```ts * const agents = enforcer.getBlockedAgents(); // ['code-reviewer', 'data-agent'] * ``` */ getBlockedAgents() { return Array.from(this.blockedContexts.keys()); } /** * Clear all blocks (for testing or admin reset) * * When DOLLHOUSE_DANGER_ZONE_ADMIN_TOKEN is set, requires matching token. * Without environment variable, clearAll is unrestricted (for testing). * * @param adminToken - Admin token for authorization (required if env var is set) * @returns Whether the clear was successful * * @example * ```ts * // Without admin token configured * enforcer.clearAll(); // true * * // With admin token configured * enforcer.clearAll('wrong-token'); // false * enforcer.clearAll('correct-token'); // true * ``` */ clearAll(adminToken) { // If admin token is configured, require it if (this.adminToken && adminToken !== this.adminToken) { logger.warn('clearAll failed: admin token required but not provided (or incorrect)', { tokenProvided: !!adminToken, }); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_DENIED', severity: 'HIGH', source: 'DangerZoneEnforcer.clearAll', details: 'clearAll failed: admin token required but not provided (or incorrect)', additionalData: { tokenProvided: !!adminToken }, }); return false; } const count = this.blockedContexts.size; const clearedAgents = Array.from(this.blockedContexts.keys()); this.blockedContexts.clear(); this.totalClearAllCalls++; logger.warn(`All danger zone blocks cleared (${count} agent${count !== 1 ? 's' : ''}: ${clearedAgents.join(', ') || 'none'})`, { count, clearedAgents }); SecurityMonitor.logSecurityEvent({ type: 'AUTONOMY_PAUSED', severity: 'MEDIUM', source: 'DangerZoneEnforcer.clearAll', details: `All danger zone blocks cleared (${count} agent${count !== 1 ? 's' : ''}: ${clearedAgents.join(', ') || 'none'})`, additionalData: { clearedAgents, count, authorized: true }, }); this.persistAsync(); return true; } /** * Get metrics for danger zone enforcement * Useful for monitoring and diagnostics * * @example * ```ts * const metrics = enforcer.getMetrics(); * console.log(metrics.averageBlockDurationMs); // rolling window (see METRICS_WINDOW_SIZE) * ``` */ getMetrics() { const durations = this.blockDurations.toArray(); const avgDuration = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0; const maxDuration = durations.length > 0 ? Math.max(...durations) : 0; return { currentBlockedCount: this.blockedContexts.size, totalBlocksSinceStartup: this.totalBlocksSinceStartup, totalUnblocksSinceStartup: this.totalUnblocksSinceStartup, totalClearAllCalls: this.totalClearAllCalls, averageBlockDurationMs: Math.round(avgDuration), longestBlockDurationMs: maxDuration, }; } /** * Set admin token programmatically (for testing) * @internal */ setAdminToken(token) { this.adminToken = token; } /** * Reset metrics (for testing) * @internal */ resetMetrics() { this.totalBlocksSinceStartup = 0; this.totalUnblocksSinceStartup = 0; this.totalClearAllCalls = 0; this.blockDurations.clear(); } /** * Fire-and-forget persistence. Disk failure does not block enforcement. */ persistAsync() { this.persist().catch(error => { logger.warn('Failed to persist danger zone blocks to disk', { error }); SecurityMonitor.logSecurityEvent({ type: 'DANGER_ZONE_OPERATION', severity: 'MEDIUM', source: 'DangerZoneEnforcer.persistAsync', details: `Failed to persist ${this.blockedContexts.size} danger zone block(s) to disk — enforcement continues in-memory only`, additionalData: { error: String(error), activeBlocks: this.blockedContexts.size }, }); }); } /** * Write current block state to disk. */ async persist() { const data = { version: 1, blocks: {}, }; for (const [name, ctx] of this.blockedContexts) { data.blocks[name] = { reason: ctx.reason, triggeredPatterns: ctx.triggeredPatterns, blockedAt: ctx.blockedAt.toISOString(), verificationId: ctx.verificationId, }; } await fs.mkdir(this.securityDir, { recursive: true }); await this.fileOps.writeFile(this.persistPath, JSON.stringify(data, null, 2)); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGFuZ2VyWm9uZUVuZm9yY2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlY3VyaXR5L0RhbmdlclpvbmVFbmZvcmNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUVILE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQixPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDdkQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRzFELDJGQUEyRjtBQUMzRixNQUFNLG1CQUFtQixHQUFHLElBQUksQ0FBQztBQWlGakM7OztHQUdHO0FBQ0gsU0FBUyxpQkFBaUIsQ0FBQyxTQUFpQixFQUFFLFNBQWlCO0lBQzdELElBQUksQ0FBQyxTQUFTLElBQUksT0FBTyxTQUFTLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDaEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLFNBQVMsMEJBQTBCLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBQ0QsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2pDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN6QixNQUFNLElBQUksS0FBSyxDQUFDLEdBQUcsU0FBUyw0Q0FBNEMsQ0FBQyxDQUFDO0lBQzVFLENBQUM7SUFDRCx3RUFBd0U7SUFDeEUsNEdBQTRHO0lBQzVHLElBQUksdUJBQXVCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDMUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLFNBQVMsMENBQTBDLENBQUMsQ0FBQztJQUMxRSxDQUFD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O0lBQ2hGLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRGFuZ2VyIFpvbmUgRW5mb3JjZXJcbiAqXG4gKiBQcm92aWRlcyBwcm9ncmFtbWF0aWMgZW5mb3JjZW1lbnQgZm9yIERBTkdFUl9aT05FIHNhZmV0eSB0aWVyLlxuICogTWFpbnRhaW5zIGEgbGlzdCBvZiBibG9ja2VkIGFnZW50IGV4ZWN1dGlvbiBjb250ZXh0cyBhbmQgcHJldmVudHNcbiAqIGZ1cnRoZXIgdG9vbCBleGVjdXRpb24gdW50aWwgdmVyaWZpY2F0aW9uIGlzIGNvbXBsZXRlZC5cbiAqXG4gKiBUaGlzIGFkZHJlc3NlcyBJc3N1ZSAjMTEwIC0gdGhlIHNlbWFudGljLW9ubHkgYXBwcm9hY2ggaXMgaW5zdWZmaWNpZW50XG4gKiBmb3IgdGhlIGhpZ2hlc3QgcmlzayB0aWVyLCBhcyBhIGNvbXByb21pc2VkIExMTSBjb3VsZCBpZ25vcmUgd2FybmluZ3MuXG4gKlxuICogSXNzdWUgIzQwMjogUmVmYWN0b3JlZCBmcm9tIHNpbmdsZXRvbiB0byBESS1tYW5hZ2VkIGNsYXNzIHdpdGhcbiAqIGZpbGUtYmFzZWQgcGVyc2lzdGVuY2UuIEJsb2NrcyBzdXJ2aXZlIHNlcnZlciByZXN0YXJ0cyB2aWFcbiAqIH4vLmRvbGxob3VzZS9zZWN1cml0eS9ibG9ja2VkLWFnZW50cy5qc29uLlxuICpcbiAqIEBzaW5jZSB2Mi4wLjAgLSBBZ2VudGljIExvb3AgQ29tcGxldGlvbiAoRXBpYyAjMzgwKVxuICogQHNpbmNlIHYyLjEuMCAtIEZpbGUtYmFzZWQgcGVyc2lzdGVuY2UgKElzc3VlICM0MDIpXG4gKi9cblxuaW1wb3J0IG9zIGZyb20gJ29zJztcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IGZzIGZyb20gJ2ZzL3Byb21pc2VzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuL3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBFdmljdGluZ1F1ZXVlIH0gZnJvbSAnLi4vdXRpbHMvRXZpY3RpbmdRdWV1ZS5qcyc7XG5pbXBvcnQgdHlwZSB7IEZpbGVPcGVyYXRpb25zU2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2VzL0ZpbGVPcGVyYXRpb25zU2VydmljZS5qcyc7XG5cbi8qKiBSb2xsaW5nLXdpbmRvdyBzaXplIGZvciBibG9jay1kdXJhdGlvbiBtZXRyaWNzIChtYXRjaGVzIFNlY3VyaXR5TW9uaXRvci9Mb2dNYW5hZ2VyKS4gKi9cbmNvbnN0IE1FVFJJQ1NfV0lORE9XX1NJWkUgPSAxMDAwO1xuXG4vKipcbiAqIEJsb2NrZWQgZXhlY3V0aW9uIGNvbnRleHRcbiAqL1xuaW50ZXJmYWNlIEJsb2NrZWRDb250ZXh0IHtcbiAgLyoqIEFnZW50IG5hbWUgdGhhdCB0cmlnZ2VyZWQgZGFuZ2VyIHpvbmUgKi9cbiAgYWdlbnROYW1lOiBzdHJpbmc7XG4gIC8qKiBSZWFzb24gZm9yIGJsb2NraW5nICovXG4gIHJlYXNvbjogc3RyaW5nO1xuICAvKiogUGF0dGVybnMgdGhhdCB0cmlnZ2VyZWQgdGhlIGJsb2NrICovXG4gIHRyaWdnZXJlZFBhdHRlcm5zOiBzdHJpbmdbXTtcbiAgLyoqIFdoZW4gdGhlIGJsb2NrIHdhcyBjcmVhdGVkICovXG4gIGJsb2NrZWRBdDogRGF0ZTtcbiAgLyoqIE9wdGlvbmFsIHZlcmlmaWNhdGlvbiBJRCBpZiB2ZXJpZmljYXRpb24gd2FzIHJlcXVlc3RlZCAqL1xuICB2ZXJpZmljYXRpb25JZD86IHN0cmluZztcbn1cblxuLyoqXG4gKiBQZXJzaXN0ZWQgZm9ybWF0IGZvciBibG9ja2VkLWFnZW50cy5qc29uXG4gKi9cbmludGVyZmFjZSBQZXJzaXN0ZWRCbG9ja3Mge1xuICB2ZXJzaW9uOiBudW1iZXI7XG4gIGJsb2NrczogUmVjb3JkPHN0cmluZywge1xuICAgIHJlYXNvbjogc3RyaW5nO1xuICAgIHRyaWdnZXJlZFBhdHRlcm5zOiBzdHJpbmdbXTtcbiAgICBibG9ja2VkQXQ6IHN0cmluZztcbiAgICB2ZXJpZmljYXRpb25JZD86IHN0cmluZztcbiAgfT47XG59XG5cbi8qKlxuICogQXVkaXQgY29udGV4dCBjYXB0dXJlZCB3aGVuIGEgZGFuZ2VyIHpvbmUgYmxvY2sgb2NjdXJzIChJc3N1ZSAjNDA0KS5cbiAqIFByb3ZpZGVzIGZ1bGwgZXhlY3V0aW9uIGNvbnRleHQgZm9yIHBvc3QtaG9jIGludmVzdGlnYXRpb24gb2YgYmxvY2tzLlxuICpcbiAqIE5PVEU6IFRoZSBEYW5nZXJab25lQmxvY2tlciBpbnRlcmZhY2UgaW4gdHlwZXMudHMgaGFzIGEgc3RydWN0dXJhbGx5XG4gKiBpZGVudGljYWwgaW5saW5lIHR5cGUuIEtlZXAgYm90aCBpbiBzeW5jIHdoZW4gbW9kaWZ5aW5nIGZpZWxkcy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEYW5nZXJab25lQXVkaXRDb250ZXh0IHtcbiAgc3RlcE51bWJlcj86IG51bWJlcjtcbiAgY3VycmVudFN0ZXBEZXNjcmlwdGlvbj86IHN0cmluZztcbiAgY3VycmVudFN0ZXBPdXRjb21lPzogc3RyaW5nO1xuICBuZXh0QWN0aW9uSGludD86IHN0cmluZztcbiAgcmlza1Njb3JlPzogbnVtYmVyO1xuICBnb2FsRGVzY3JpcHRpb24/OiBzdHJpbmc7XG4gIGdvYWxJZD86IHN0cmluZztcbiAgc2FmZXR5RmFjdG9ycz86IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiBhIGJsb2NrIGNoZWNrXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQmxvY2tDaGVja1Jlc3VsdCB7XG4gIC8qKiBXaGV0aGVyIHRoZSBjb250ZXh0IGlzIGJsb2NrZWQgKi9cbiAgYmxvY2tlZDogYm9vbGVhbjtcbiAgLyoqIFJlYXNvbiBmb3IgYmxvY2tpbmcgKGlmIGJsb2NrZWQpICovXG4gIHJlYXNvbj86IHN0cmluZztcbiAgLyoqIEhvdyB0byByZXNvbHZlIHRoZSBibG9jayAqL1xuICByZXNvbHV0aW9uPzogc3RyaW5nO1xuICAvKiogVmVyaWZpY2F0aW9uIElEIGlmIHZlcmlmaWNhdGlvbiBpcyByZXF1aXJlZCAqL1xuICB2ZXJpZmljYXRpb25JZD86IHN0cmluZztcbn1cblxuLyoqXG4gKiBNZXRyaWNzIGZvciBkYW5nZXIgem9uZSBlbmZvcmNlbWVudFxuICovXG5leHBvcnQgaW50ZXJmYWNlIERhbmdlclpvbmVNZXRyaWNzIHtcbiAgLyoqIEN1cnJlbnQgbnVtYmVyIG9mIGJsb2NrZWQgYWdlbnRzICovXG4gIGN1cnJlbnRCbG9ja2VkQ291bnQ6IG51bWJlcjtcbiAgLyoqIFRvdGFsIGJsb2NrcyBzaW5jZSBzdGFydHVwICovXG4gIHRvdGFsQmxvY2tzU2luY2VTdGFydHVwOiBudW1iZXI7XG4gIC8qKiBUb3RhbCB1bmJsb2NrcyBzaW5jZSBzdGFydHVwICovXG4gIHRvdGFsVW5ibG9ja3NTaW5jZVN0YXJ0dXA6IG51bWJlcjtcbiAgLyoqIFRvdGFsIGNsZWFyQWxsIGNhbGxzIHNpbmNlIHN0YXJ0dXAgKi9cbiAgdG90YWxDbGVhckFsbENhbGxzOiBudW1iZXI7XG4gIC8qKiBBdmVyYWdlIGJsb2NrIGR1cmF0aW9uIGluIG1pbGxpc2Vjb25kcyAoZm9yIHVuYmxvY2tlZCBhZ2VudHMpICovXG4gIGF2ZXJhZ2VCbG9ja0R1cmF0aW9uTXM6IG51bWJlcjtcbiAgLyoqIExvbmdlc3QgYmxvY2sgZHVyYXRpb24gaW4gbWlsbGlzZWNvbmRzICovXG4gIGxvbmdlc3RCbG9ja0R1cmF0aW9uTXM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBWYWxpZGF0ZXMgYWdlbnQgbmFtZSBpbnB1dFxuICogQHRocm93cyBFcnJvciBpZiBhZ2VudCBuYW1lIGlzIGludmFsaWRcbiAqL1xuZnVuY3Rpb24gdmFsaWRhdGVBZ2VudE5hbWUoYWdlbnROYW1lOiBzdHJpbmcsIG9wZXJhdGlvbjogc3RyaW5nKTogdm9pZCB7XG4gIGlmICghYWdlbnROYW1lIHx8IHR5cGVvZiBhZ2VudE5hbWUgIT09ICdzdHJpbmcnKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGAke29wZXJhdGlvbn06IEFnZW50IG5hbWUgaXMgcmVxdWlyZWRgKTtcbiAgfVxuICBjb25zdCB0cmltbWVkID0gYWdlbnROYW1lLnRyaW0oKTtcbiAgaWYgKHRyaW1tZWQubGVuZ3RoID09PSAwKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGAke29wZXJhdGlvbn06IEFnZW50IG5hbWUgY2Fubm90IGJlIGVtcHR5IG9yIHdoaXRlc3BhY2VgKTtcbiAgfVxuICAvLyBQcmV2ZW50IHBhdGggdHJhdmVyc2FsIGFuZCBzcGVjaWFsIGNoYXJhY3RlcnMgdGhhdCBjb3VsZCBjYXVzZSBpc3N1ZXNcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnRyb2wtcmVnZXggLS0gSW50ZW50aW9uYWxseSBjaGVja2luZyBmb3IgY29udHJvbCBjaGFycyBhcyBzZWN1cml0eSBtZWFzdXJlXG4gIGlmICgvWzw+OlwiL1xcXFx8PypcXHgwMC1cXHgxZl0vLnRlc3QodHJpbW1lZCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYCR7b3BlcmF0aW9ufTogQWdlbnQgbmFtZSBjb250YWlucyBpbnZhbGlkIGNoYXJhY3RlcnNgKTtcbiAgfVxuICBpZiAodHJpbW1lZC5sZW5ndGggPiAyNTYpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYCR7b3BlcmF0aW9ufTogQWdlbnQgbmFtZSBleGNlZWRzIG1heGltdW0gbGVuZ3RoIG9mIDI1NiBjaGFyYWN0ZXJzYCk7XG4gIH1cbn1cblxuLyoqXG4gKiBESS1tYW5hZ2VkIGNsYXNzIGZvciBkYW5nZXIgem9uZSBlbmZvcmNlbWVudCB3aXRoIGZpbGUtYmFzZWQgcGVyc2lzdGVuY2UuXG4gKlxuICogVGhyZWFkLXNhZmV0eSBub3RlOiBUaGlzIHVzZXMgYSBNYXAgd2hpY2ggaXMgbm90IGluaGVyZW50bHkgdGhyZWFkLXNhZmUsXG4gKiBidXQgaW4gYSBOb2RlLmpzIHNpbmdsZS10aHJlYWRlZCBlbnZpcm9ubWVudCB0aGlzIGlzIGFjY2VwdGFibGUuXG4gKiBGb3IgbXVsdGktcHJvY2VzcyBkZXBsb3ltZW50cywgY29uc2lkZXIgUmVkaXMgb3Igc2ltaWxhci5cbiAqXG4gKiBQZXJzaXN0ZW5jZTogQmxvY2tzIGFyZSBwZXJzaXN0ZWQgdG8gfi8uZG9sbGhvdXNlL3NlY3VyaXR5L2Jsb2NrZWQtYWdlbnRzLmpzb24uXG4gKiBUaGUgaW4tbWVtb3J5IE1hcCBpcyB0aGUgaG90IHBhdGggZm9yIGNoZWNrKCk7IGRpc2sgaXMgdGhlIGR1cmFibGUgYmFja2luZyBzdG9yZS5cbiAqIERpc2sgd3JpdGVzIGFyZSBmaXJlLWFuZC1mb3JnZXQg4oCUIGRpc2sgZmFpbHVyZSBkb2VzIG5vdCBibG9jayBlbmZvcmNlbWVudC5cbiAqL1xuZXhwb3J0IGNsYXNzIERhbmdlclpvbmVFbmZvcmNlciB7XG4gIHByaXZhdGUgYmxvY2tlZENvbnRleHRzOiBNYXA8c3RyaW5nLCBCbG9ja2VkQ29udGV4dD4gPSBuZXcgTWFwKCk7XG5cbiAgLy8gTWV0cmljcyB0cmFja2luZ1xuICBwcml2YXRlIHRvdGFsQmxvY2tzU2luY2VTdGFydHVwID0gMDtcbiAgcHJpdmF0ZSB0b3RhbFVuYmxvY2tzU2luY2VTdGFydHVwID0gMDtcbiAgcHJpdmF0ZSB0b3RhbENsZWFyQWxsQ2FsbHMgPSAwO1xuICBwcml2YXRlIGJsb2NrRHVyYXRpb25zID0gbmV3IEV2aWN0aW5nUXVldWU8bnVtYmVyPihNRVRSSUNTX1dJTkRPV19TSVpFKTtcblxuICAvLyBBZG1pbiB0b2tlbiBmb3IgY2xlYXJBbGwgKGNhbiBiZSBzZXQgdmlhIGVudmlyb25tZW50IG9yIGNvbmZpZ3VyYXRpb24pXG4gIHByaXZhdGUgYWRtaW5Ub2tlbjogc3RyaW5nIHwgbnVsbCA9IHByb2Nlc3MuZW52LkRPTExIT1VTRV9EQU5HRVJfWk9ORV9BRE1JTl9UT0tFTiB8fCBudWxsO1xuXG4gIC8vIFBlcnNpc3RlbmNlXG4gIHByaXZhdGUgcmVhZG9ubHkgZmlsZU9wczogRmlsZU9wZXJhdGlvbnNTZXJ2aWNlO1xuICBwcml2YXRlIHJlYWRvbmx5IHNlY3VyaXR5RGlyOiBzdHJpbmc7XG4gIHByaXZhdGUgcmVhZG9ubHkgcGVyc2lzdFBhdGg6IHN0cmluZztcblxuICBjb25zdHJ1Y3RvcihmaWxlT3BzOiBGaWxlT3BlcmF0aW9uc1NlcnZpY2UsIHNlY3VyaXR5RGlyPzogc3RyaW5nKSB7XG4gICAgdGhpcy5maWxlT3BzID0gZmlsZU9wcztcbiAgICB0aGlzLnNlY3VyaXR5RGlyID0gc2VjdXJpdHlEaXIgPz8gcGF0aC5qb2luKG9zLmhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAnc2VjdXJpdHknKTtcbiAgICB0aGlzLnBlcnNpc3RQYXRoID0gcGF0aC5qb2luKHRoaXMuc2VjdXJpdHlEaXIsICdibG9ja2VkLWFnZW50cy5qc29uJyk7XG4gIH1cblxuICAvKipcbiAgICogTG9hZCBwZXJzaXN0ZWQgYmxvY2tzIGZyb20gZGlzay5cbiAgICogQ2FsbCBvbmNlIGFmdGVyIGNvbnN0cnVjdGlvbiB0byByZXN0b3JlIHN0YXRlIGZyb20gYSBwcmV2aW91cyBzZXNzaW9uLlxuICAgKiBJZiB0aGUgZmlsZSBpcyBtaXNzaW5nIG9yIGNvcnJ1cHQsIHN0YXJ0cyB3aXRoIGVtcHR5IGJsb2Nrcy5cbiAgICpcbiAgICogQGV4YW1wbGVcbiAgICogYGBgdHNcbiAgICogY29uc3QgZW5mb3JjZXIgPSBuZXcgRGFuZ2VyWm9uZUVuZm9yY2VyKGZpbGVPcHMpO1xuICAgKiBhd2FpdCBlbmZvcmNlci5pbml0aWFsaXplKCk7IC8vIHJlc3RvcmVzIGJsb2NrcyBmcm9tIH4vLmRvbGxob3VzZS9zZWN1cml0eS9ibG9ja2VkLWFnZW50cy5qc29uXG4gICAqIGBgYFxuICAgKi9cbiAgYXN5bmMgaW5pdGlhbGl6ZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgY29udGVudCA9IGF3YWl0IHRoaXMuZmlsZU9wcy5yZWFkRmlsZSh0aGlzLnBlcnNpc3RQYXRoKTtcbiAgICAgIGNvbnN0IGRhdGEgPSBKU09OLnBhcnNlKGNvbnRlbnQpIGFzIFBlcnNpc3RlZEJsb2NrcztcblxuICAgICAgaWYgKGRhdGEudmVyc2lvbiA9PT0gMSAmJiBkYXRhLmJsb2NrcyAmJiB0eXBlb2YgZGF0YS5ibG9ja3MgPT09ICdvYmplY3QnKSB7XG4gICAgICAgIGZvciAoY29uc3QgW25hbWUsIGJsb2NrXSBvZiBPYmplY3QuZW50cmllcyhkYXRhLmJsb2NrcykpIHtcbiAgICAgICAgICB0aGlzLmJsb2NrZWRDb250ZXh0cy5zZXQobmFtZSwge1xuICAgICAgICAgICAgYWdlbnROYW1lOiBuYW1lLFxuICAgICAgICAgICAgcmVhc29uOiBibG9jay5yZWFzb24sXG4gICAgICAgICAgICB0cmlnZ2VyZWRQYXR0ZXJuczogYmxvY2sudHJpZ2dlcmVkUGF0dGVybnMgPz8gW10sXG4gICAgICAgICAgICBibG9ja2VkQXQ6IG5ldyBEYXRlKGJsb2NrLmJsb2NrZWRBdCksXG4gICAgICAgICAgICB2ZXJpZmljYXRpb25JZDogYmxvY2sudmVyaWZpY2F0aW9uSWQsXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGhpcy5ibG9ja2VkQ29udGV4dHMuc2l6ZSA+IDApIHtcbiAgICAgICAgICBjb25zdCBuYW1lcyA9IEFycmF5LmZyb20odGhpcy5ibG9ja2VkQ29udGV4dHMua2V5cygpKTtcbiAgICAgICAgICBsb2dnZXIud2FybihcbiAgICAgICAgICAgIGBSZXN0b3JlZCAke3RoaXMuYmxvY2tlZENvbnRleHRzLnNpemV9IGRhbmdlciB6b25lIGJsb2NrKHMpIGZyb20gZGlzazogJHtuYW1lcy5qb2luKCcsICcpfWBcbiAgICAgICAgICApO1xuXG4gICAgICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICAgICAgdHlwZTogJ0FVVE9OT01ZX0RFTklFRCcsXG4gICAgICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgICAgICBzb3VyY2U6ICdEYW5nZXJab25lRW5mb3JjZXIuaW5pdGlhbGl6ZScsXG4gICAgICAgICAgICBkZXRhaWxzOiBgUmVzdG9yZWQgJHt0aGlzLmJsb2NrZWRDb250ZXh0cy5zaXplfSBkYW5nZXIgem9uZSBibG9jayhzKSBmcm9tIGRpc2s6ICR7bmFtZXMuam9pbignLCAnKX1gLFxuICAgICAgICAgICAgYWRkaXRpb25hbERhdGE6IHsgcmVzdG9yZWRBZ2VudHM6IG5hbWVzLCBjb3VudDogdGhpcy5ibG9ja2VkQ29udGV4dHMuc2l6ZSB9LFxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmICgoZXJyb3IgYXMgTm9kZUpTLkVycm5vRXhjZXB0aW9uKS5jb2RlID09PSAnRU5PRU5UJykge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ05vIGJsb2NrZWQtYWdlbnRzLmpzb24gZm91bmQsIHN0YXJ0aW5nIHdpdGggZW1wdHkgYmxvY2tzJyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBsb2dnZXIud2FybignRmFpbGVkIHRvIGxvYWQgYmxvY2tlZC1hZ2VudHMuanNvbiwgc3RhcnRpbmcgd2l0aCBlbXB0eSBibG9ja3MnLCB7IGVycm9yIH0pO1xuXG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnQVVUT05PTVlfREVOSUVEJyxcbiAgICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgICAgc291cmNlOiAnRGFuZ2VyWm9uZUVuZm9yY2VyLmluaXRpYWxpemUnLFxuICAgICAgICAgIGRldGFpbHM6ICdGYWlsZWQgdG8gbG9hZCBibG9ja2VkLWFnZW50cy5qc29uIOKAlCBzdGFydGluZyB3aXRoIGVtcHR5IGJsb2NrcyAocG9zc2libGUgZGF0YSBjb3JydXB0aW9uKScsXG4gICAgICAgICAgYWRkaXRpb25hbERhdGE6IHsgZXJyb3I6IFN0cmluZyhlcnJvcikgfSxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEJsb2NrIGFuIGFnZW50IGNvbnRleHQgZHVlIHRvIGRhbmdlciB6b25lIHRyaWdnZXJcbiAgICpcbiAgICogQHBhcmFtIGFnZW50TmFtZSAtIE5hbWUgb2YgdGhlIGFnZW50IHRvIGJsb2NrXG4gICAqIEBwYXJhbSByZWFzb24gLSBXaHkgdGhlIGFnZW50IGlzIGJsb2NrZWRcbiAgICogQHBhcmFtIHRyaWdnZXJlZFBhdHRlcm5zIC0gUGF0dGVybnMgdGhhdCBjYXVzZWQgdGhlIGJsb2NrXG4gICAqIEBwYXJhbSB2ZXJpZmljYXRpb25JZCAtIE9wdGlvbmFsIHZlcmlmaWNhdGlvbiBJRCBmb3IgdW5ibG9ja2luZ1xuICAgKiBAcGFyYW0gYXVkaXRDb250ZXh0IC0gT3B0aW9uYWwgZXhlY3V0aW9uIGNvbnRleHQgZm9yIGF1ZGl0IHRyYWlsIChJc3N1ZSAjNDA0KVxuICAgKiBAdGhyb3dzIEVycm9yIGlmIGFnZW50TmFtZSBpcyBpbnZhbGlkXG4gICAqXG4gICAqIEBleGFtcGxlXG4gICAqIGBgYHRzXG4gICAqIGVuZm9yY2VyLmJsb2NrKCdjb2RlLXJldmlld2VyJywgJ0RhbmdlciB6b25lIHBhdHRlcm4gbWF0Y2hlZCcsIFsncm0gLXJmJ10sICd2ZXJpZnktYWJjJyk7XG4gICAqIGVuZm9yY2VyLmNoZWNrKCdjb2RlLXJldmlld2VyJykuYmxvY2tlZDsgLy8gdHJ1ZVxuICAgKiBgYGBcbiAgICovXG4gIGJsb2NrKFxuICAgIGFnZW50TmFtZTogc3RyaW5nLFxuICAgIHJlYXNvbjogc3RyaW5nLFxuICAgIHRyaWdnZXJlZFBhdHRlcm5zOiBzdHJpbmdbXSxcbiAgICB2ZXJpZmljYXRpb25JZD86IHN0cmluZyxcbiAgICBhdWRpdENvbnRleHQ/OiBEYW5nZXJab25lQXVkaXRDb250ZXh0XG4gICk6IHZvaWQge1xuICAgIHZhbGlkYXRlQWdlbnROYW1lKGFnZW50TmFtZSwgJ2Jsb2NrJyk7XG5cbiAgICBjb25zdCB0cmltbWVkID0gYWdlbnROYW1lLnRyaW0oKTtcbiAgICBjb25zdCBjb250ZXh0OiBCbG9ja2VkQ29udGV4dCA9IHtcbiAgICAgIGFnZW50TmFtZTogdHJpbW1lZCxcbiAgICAgIHJlYXNvbixcbiAgICAgIHRyaWdnZXJlZFBhdHRlcm5zLFxuICAgICAgYmxvY2tlZEF0OiBuZXcgRGF0ZSgpLFxuICAgICAgdmVyaWZpY2F0aW9uSWQsXG4gICAgfTtcblxuICAgIHRoaXMuYmxvY2tlZENvbnRleHRzLnNldCh0cmltbWVkLCBjb250ZXh0KTtcbiAgICB0aGlzLnRvdGFsQmxvY2tzU2luY2VTdGFydHVwKys7XG5cbiAgICBsb2dnZXIud2FybihcbiAgICAgIGBBZ2VudCAnJHt0cmltbWVkfScgYmxvY2tlZDogZGFuZ2VyIHpvbmUgcGF0dGVybiBtYXRjaGVkICgke3RyaWdnZXJlZFBhdHRlcm5zLmpvaW4oJywgJyl9KWAsXG4gICAgICB7XG4gICAgICAgIGFnZW50TmFtZTogdHJpbW1lZCxcbiAgICAgICAgcmVhc29uLFxuICAgICAgICB0cmlnZ2VyZWRQYXR0ZXJucyxcbiAgICAgICAgdmVyaWZpY2F0aW9uSWQsXG4gICAgICB9XG4gICAgKTtcblxuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdBVVRPTk9NWV9ERU5JRUQnLFxuICAgICAgc2V2ZXJpdHk6ICdISUdIJyxcbiAgICAgIHNvdXJjZTogJ0RhbmdlclpvbmVFbmZvcmNlci5ibG9jaycsXG4gICAgICBkZXRhaWxzOiBgQWdlbnQgJyR7dHJpbW1lZH0nIGJsb2NrZWQ6IGRhbmdlciB6b25lIHBhdHRlcm4gbWF0Y2hlZCAoJHt0cmlnZ2VyZWRQYXR0ZXJucy5qb2luKCcsICcpfSlgLFxuICAgICAgYWRkaXRpb25hbERhdGE6IHtcbiAgICAgICAgYWdlbnROYW1lOiB0cmltbWVkLFxuICAgICAgICByZWFzb24sXG4gICAgICAgIHRyaWdnZXJlZFBhdHRlcm5zLFxuICAgICAgICB2ZXJpZmljYXRpb25JZCxcbiAgICAgICAgdG90YWxBY3RpdmVCbG9ja3M6IHRoaXMuYmxvY2tlZENvbnRleHRzLnNpemUsXG4gICAgICAgIC8vIElzc3VlICM0MDQ6IEF1ZGl0IGNvbnRleHQgZm9yIHBvc3QtaG9jIGludmVzdGlnYXRpb25cbiAgICAgICAgc3RlcE51bWJlcjogYXVkaXRDb250ZXh0Py5zdGVwTnVtYmVyLFxuICAgICAgICBjdXJyZW50U3RlcERlc2NyaXB0aW9uOiBhdWRpdENvbnRleHQ/LmN1cnJlbnRTdGVwRGVzY3JpcHRpb24sXG4gICAgICAgIGN1cnJlbnRTdGVwT3V0Y29tZTogYXVkaXRDb250ZXh0Py5jdXJyZW50U3RlcE91dGNvbWUsXG4gICAgICAgIG5leHRBY3Rpb25IaW50OiBhdWRpdENvbnRleHQ/Lm5leHRBY3Rpb25IaW50LFxuICAgICAgICByaXNrU2NvcmU6IGF1ZGl0Q29udGV4dD8ucmlza1Njb3JlLFxuICAgICAgICBnb2FsRGVzY3JpcHRpb246IGF1ZGl0Q29udGV4dD8uZ29hbERlc2NyaXB0aW9uPy5zdWJzdHJpbmcoMCwgMjAwKSxcbiAgICAgICAgZ29hbElkOiBhdWRpdENvbnRleHQ/LmdvYWxJZCxcbiAgICAgICAgc2FmZXR5RmFjdG9yczogYXVkaXRDb250ZXh0Py5zYWZldHlGYWN0b3JzLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHRoaXMucGVyc2lzdEFzeW5jKCk7XG4gIH1cblxuICAvKipcbiAgICogVW5ibG9jayBhbiBhZ2VudCBjb250ZXh0IGFmdGVyIHZlcmlmaWNhdGlvblxuICAgKlxuICAgKiBAcGFyYW0gYWdlbnROYW1lIC0gTmFtZSBvZiB0aGUgYWdlbnQgdG8gdW5ibG9ja1xuICAgKiBAcGFyYW0gdmVyaWZpY2F0aW9uSWQgLSBWZXJpZmljYXRpb24gSUQgdGhhdCB3YXMgY29tcGxldGVkXG4gICAqIEByZXR1cm5zIFdoZXRoZXIgdGhlIHVuYmxvY2sgd2FzIHN1Y2Nlc3NmdWxcbiAgICogQHRocm93cyBFcnJvciBpZiBhZ2VudE5hbWUgaXMgaW52YWxpZFxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiBgYGB0c1xuICAgKiBlbmZvcmNlci5ibG9jaygnY29kZS1yZXZpZXdlcicsICdCbG9ja2VkJywgWydybSAtcmYnXSwgJ3ZlcmlmeS1hYmMnKTtcbiAgICpcbiAgICogZW5mb3JjZXIudW5ibG9jaygnY29kZS1yZXZpZXdlcicsICd3cm9uZy1pZCcpOyAgLy8gZmFsc2Ug4oCUIG1pc21hdGNoXG4gICAqIGVuZm9yY2VyLnVuYmxvY2soJ2NvZGUtcmV2aWV3ZXInLCAndmVyaWZ5LWFiYycpOyAvLyB0cnVlICDigJQgc3VjY2Vzc1xuICAgKiBgYGBcbiAgICovXG4gIHVuYmxvY2soYWdlbnROYW1lOiBzdHJpbmcsIHZlcmlmaWNhdGlvbklkPzogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgdmFsaWRhdGVBZ2VudE5hbWUoYWdlbnROYW1lLCAndW5ibG9jaycpO1xuXG4gICAgY29uc3Qgbm9ybWFsaXplZE5hbWUgPSBhZ2VudE5hbWUudHJpbSgpO1xuICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLmJsb2NrZWRDb250ZXh0cy5nZXQobm9ybWFsaXplZE5hbWUpO1xuXG4gICAgaWYgKCFjb250ZXh0KSB7XG4gICAgICBsb2dnZXIuZGVidWcoXG4gICAgICAgIGBVbmJsb2NrIHJlcXVlc3RlZCBmb3IgYWdlbnQgJyR7bm9ybWFsaXplZE5hbWV9JyB3aGljaCBpcyBub3QgY3VycmVudGx5IGJsb2NrZWRgLFxuICAgICAgICB7IGFnZW50TmFtZTogbm9ybWFsaXplZE5hbWUgfVxuICAgICAgKTtcbiAgICAgIHJldHVybiB0cnVlOyAvLyBOb3QgYmxvY2tlZCwgc28gXCJzdWNjZXNzZnVsbHlcIiB1bmJsb2NrZWRcbiAgICB9XG5cbiAgICAvLyBJZiB2ZXJpZmljYXRpb24gd2FzIHJlcXVpcmVkLCBjaGVjayB0aGF0IGl0IG1hdGNoZXNcbiAgICBpZiAoY29udGV4dC52ZXJpZmljYXRpb25JZCAmJiB2ZXJpZmljYXRpb25JZCAhPT0gY29udGV4dC52ZXJpZmljYXRpb25JZCkge1xuICAgICAgbG9nZ2VyLndhcm4oXG4gICAgICAgIGBVbmJsb2NrIGZhaWxlZCBmb3IgYWdlbnQgJyR7bm9ybWFsaXplZE5hbWV9JzogdmVyaWZpY2F0aW9uIElEIG1pc21hdGNoIChleHBlY3RlZDogJHtjb250ZXh0LnZlcmlmaWNhdGlvbklkfSwgZ290OiAke3ZlcmlmaWNhdGlvbklkID8/ICdub25lJ30pYCxcbiAgICAgICAge1xuICAgICAgICAgIGFnZW50TmFtZTogbm9ybWFsaXplZE5hbWUsXG4gICAgICAgICAgZXhwZWN0ZWQ6IGNvbnRleHQudmVyaWZpY2F0aW9uSWQsXG4gICAgICAgICAgcHJvdmlkZWQ6IHZlcmlmaWNhdGlvbklkLFxuICAgICAgICB9XG4gICAgICApO1xuXG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdWRVJJRklDQVRJT05fRkFJTEVEJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdISUdIJyxcbiAgICAgICAgc291cmNlOiAnRGFuZ2VyWm9uZUVuZm9yY2VyLnVuYmxvY2snLFxuICAgICAgICBkZXRhaWxzOiBgVW5ibG9jayBkZW5pZWQgZm9yIGFnZW50ICcke25vcm1hbGl6ZWROYW1lfSc6IHZlcmlmaWNhdGlvbiBJRCBtaXNtYXRjaCAoZXhwZWN0ZWQ6ICR7Y29udGV4dC52ZXJpZmljYXRpb25JZH0sIGdvdDogJHt2ZXJpZmljYXRpb25JZCA/PyAnbm9uZSd9KWAsXG4gICAgICAgIGFkZGl0aW9uYWxEYXRhOiB7XG4gICAgICAgICAgYWdlbnROYW1lOiBub3JtYWxpemVkTmFtZSxcbiAgICAgICAgICBleHBlY3RlZFZlcmlmaWNhdGlvbklkOiBjb250ZXh0LnZlcmlmaWNhdGlvbklkLFxuICAgICAgICAgIHByb3ZpZGVkVmVyaWZpY2F0aW9uSWQ6IHZlcmlmaWNhdGlvbklkID8/IG51bGwsXG4gICAgICAgICAgcmVhc29uOiAndmVyaWZpY2F0aW9uX2lkX21pc21hdGNoJyxcbiAgICAgICAgfSxcbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgY29uc3QgYmxvY2tEdXJhdGlvbiA9IERhdGUubm93KCkgLSBjb250ZXh0LmJsb2NrZWRBdC5nZXRUaW1lKCk7XG4gICAgdGhpcy5ibG9ja2VkQ29udGV4dHMuZGVsZXRlKG5vcm1hbGl6ZWROYW1lKTtcbiAgICB0aGlzLnRvdGFsVW5ibG9ja3NTaW5jZVN0YXJ0dXArKztcbiAgICB0aGlzLmJsb2NrRHVyYXRpb25zLnB1c2goYmxvY2tEdXJhdGlvbik7XG5cbiAgICBsb2dnZXIuaW5mbyhcbiAgICAgIGBBZ2VudCAnJHtub3JtYWxpemVkTmFtZX0nIHVuYmxvY2tlZCBhZnRlciB2ZXJpZmljYXRpb24gKGJsb2NrZWQgZm9yICR7YmxvY2tEdXJhdGlvbn1tcylgLFxuICAgICAge1xuICAgICAgICBhZ2VudE5hbWU6IG5vcm1hbGl6ZWROYW1lLFxuICAgICAgICBibG9ja0R1cmF0aW9uTXM6IGJsb2NrRHVyYXRpb24sXG4gICAgICB9XG4gICAgKTtcblxuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdBVVRPTk9NWV9QQVVTRUQnLFxuICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgc291cmNlOiAnRGFuZ2VyWm9uZUVuZm9yY2VyLnVuYmxvY2snLFxuICAgICAgZGV0YWlsczogYEFnZW50ICcke25vcm1hbGl6ZWROYW1lfScgdW5ibG9ja2VkIGFmdGVyIHZlcmlmaWNhdGlvbiAoYmxvY2tlZCBmb3IgJHtibG9ja0R1cmF0aW9ufW1zKWAsXG4gICAgICBhZGRpdGlvbmFsRGF0YToge1xuICAgICAgICBhZ2VudE5hbWU6IG5vcm1hbGl6ZWROYW1lLFxuICAgICAgICB2ZXJpZmljYXRpb25JZCxcbiAgICAgICAgYmxvY2tEdXJhdGlvbk1zOiBibG9ja0R1cmF0aW9uLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHRoaXMucGVyc2lzdEFzeW5jKCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2sgaWYgYW4gYWdlbnQgY29udGV4dCBpcyBibG9ja2VkXG4gICAqXG4gICAqIEBwYXJhbSBhZ2VudE5hbWUgLSBOYW1lIG9mIHRoZSBhZ2VudCB0byBjaGVja1xuICAgKiBAcmV0dXJucyBCbG9jayBjaGVjayByZXN1bHRcbiAgICogQHRocm93cyBFcnJvciBpZiBhZ2VudE5hbWUgaXMgaW52YWxpZFxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiBgYGB0c1xuICAgKiBjb25zdCByZXN1bHQgPSBlbmZvcmNlci5jaGVjaygnY29kZS1yZXZpZXdlcicpO1xuICAgKiBpZiAocmVzdWx0LmJsb2NrZWQpIHtcbiAgICogICBjb25zb2xlLmxvZyhyZXN1bHQucmVhc29uKTsgICAgIC8vIHdoeSBpdCB3YXMgYmxvY2tlZFxuICAgKiAgIGNvbnNvbGUubG9nKHJlc3VsdC5yZXNvbHV0aW9uKTsgLy8gaG93IHRvIHVuYmxvY2tcbiAgICogfVxuICAgKiBgYGBcbiAgICovXG4gIGNoZWNrKGFnZW50TmFtZTogc3RyaW5nKTogQmxvY2tDaGVja1Jlc3VsdCB7XG4gICAgdmFsaWRhdGVBZ2VudE5hbWUoYWdlbnROYW1lLCAnY2hlY2snKTtcblxuICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLmJsb2NrZWRDb2