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.

201 lines 26.7 kB
/** * Configuration Scanner - Walks configuration files (JSON/YAML/.env) and flags * insecure defaults. Rules are intentionally conservative to avoid false * positives while still catching common deployment risks called out in the * production readiness review. */ import * as path from 'path'; import * as fs from 'fs/promises'; import { logger } from '../../../utils/logger.js'; import yaml from 'js-yaml'; const SUSPICIOUS_PATTERNS = [ { match: (line) => /DOLLHOUSE_TELEMETRY\s*=\s*true/i.test(line), ruleId: 'CONFIG-TELEMETRY-OPT-IN', severity: 'medium', message: 'Telemetry should remain opt-in by default. Remove DOLLHOUSE_TELEMETRY=true from shared configs.', remediation: 'Set DOLLHOUSE_TELEMETRY=false or document why opt-in behaviour is overridden.' } ]; const CONFIG_RULES = [ { ruleId: 'CONFIG-BULK-PREVIEW', path: ['sync', 'bulk', 'require_preview'], insecureValue: false, severity: 'medium', message: 'Bulk sync preview is disabled. Users could upload/download without review.', remediation: 'Set sync.bulk.require_preview to true to require confirmation before bulk operations.' }, { ruleId: 'CONFIG-SECRET-SCANNING', path: ['sync', 'privacy', 'scan_for_secrets'], insecureValue: false, severity: 'high', message: 'Secret scanning is disabled in sync. This may leak credentials when uploading.', remediation: 'Set sync.privacy.scan_for_secrets to true.' }, { ruleId: 'CONFIG-TELEMETRY-DEFAULT', path: ['elements', 'enhanced_index', 'telemetry', 'enabled'], insecureValue: true, severity: 'medium', message: 'Enhanced index telemetry is enabled by default. It should require explicit opt-in.', remediation: 'Set elements.enhanced_index.telemetry.enabled to false unless the deployment has obtained consent.' } ]; export class ConfigurationScanner { name = 'ConfigurationScanner'; config; constructor(config) { this.config = config; } async scan(context) { if (!this.isEnabled()) { return []; } const findings = []; try { const files = await this.collectCandidateFiles(context.projectRoot); for (const filePath of files) { const fileFindings = await this.evaluateFile(filePath); findings.push(...fileFindings); } } catch (error) { logger.error('[ConfigurationScanner] Failed to scan configuration', { error: error instanceof Error ? error.message : String(error) }); } return findings; } isEnabled() { return Boolean(this.config?.enabled); } async collectCandidateFiles(projectRoot) { const files = []; const excludeDirs = new Set(['node_modules', 'dist', '.git']); const walk = async (dir) => { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (excludeDirs.has(entry.name)) continue; await walk(fullPath); } else if (this.matchesPatterns(entry.name)) { files.push(fullPath); } } }; await walk(projectRoot); return files; } matchesPatterns(fileName) { if (!this.config.checkFiles || this.config.checkFiles.length === 0) { return true; } return this.config.checkFiles.some(pattern => { const normalizedPattern = pattern.trim().toLowerCase(); if (normalizedPattern.startsWith('*')) { return fileName.toLowerCase().endsWith(normalizedPattern.slice(1)); } return fileName.toLowerCase() === normalizedPattern; }); } async evaluateFile(filePath) { const findings = []; const extension = path.extname(filePath).toLowerCase(); const baseName = path.basename(filePath); try { const content = await fs.readFile(filePath, 'utf-8'); if (baseName.startsWith('.env')) { findings.push(...this.evaluateEnvFile(content, filePath)); } else if (extension === '.json') { const parsed = JSON.parse(content); findings.push(...this.evaluateConfigObject(parsed, filePath)); } else if (extension === '.yml' || extension === '.yaml') { // Use FAILSAFE_SCHEMA for safe YAML parsing (no arbitrary object instantiation) const parsed = yaml.load(content, { schema: yaml.FAILSAFE_SCHEMA }); if (parsed && typeof parsed === 'object') { findings.push(...this.evaluateConfigObject(parsed, filePath)); } } } catch (error) { logger.debug('[ConfigurationScanner] Failed to parse config file', { file: filePath, error: error instanceof Error ? error.message : String(error) }); } return findings; } evaluateEnvFile(content, filePath) { const findings = []; const lines = content.split(/\r?\n/); for (const pattern of SUSPICIOUS_PATTERNS) { if (lines.some(line => pattern.match(line))) { findings.push({ ruleId: pattern.ruleId, severity: pattern.severity, message: pattern.message, file: path.relative(process.cwd(), filePath), remediation: pattern.remediation, confidence: 'high' }); } } return findings; } evaluateConfigObject(config, filePath) { const findings = []; for (const rule of CONFIG_RULES) { const value = this.getValue(config, rule.path); if (value === undefined) continue; // FIX: FAILSAFE_SCHEMA parses booleans as strings ("true"/"false") // Normalize for comparison: convert string booleans to actual booleans const normalizedValue = this.normalizeValue(value); if (normalizedValue === rule.insecureValue) { findings.push({ ruleId: rule.ruleId, severity: rule.severity, message: rule.message, file: path.relative(process.cwd(), filePath), remediation: rule.remediation, confidence: 'medium' }); } } return findings; } /** * Normalize YAML FAILSAFE_SCHEMA values for comparison. * FAILSAFE_SCHEMA parses everything as strings, so "false" -> false, "true" -> true. */ normalizeValue(value) { if (typeof value === 'string') { const lower = value.toLowerCase(); if (lower === 'true') return true; if (lower === 'false') return false; // Try to parse as number const num = Number(value); if (!isNaN(num) && value.trim() !== '') return num; } return value; } getValue(config, pathSegments) { return pathSegments.reduce((value, key) => { if (value && typeof value === 'object' && key in value) { return value[key]; } return undefined; }, config); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29uZmlndXJhdGlvblNjYW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvc2VjdXJpdHkvYXVkaXQvc2Nhbm5lcnMvQ29uZmlndXJhdGlvblNjYW5uZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0dBS0c7QUFFSCxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEtBQUssRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDbEQsT0FBTyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBVTNCLE1BQU0sbUJBQW1CLEdBQUc7SUFDMUI7UUFDRSxLQUFLLEVBQUUsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLGlDQUFpQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7UUFDckUsTUFBTSxFQUFFLHlCQUF5QjtRQUNqQyxRQUFRLEVBQUUsUUFBeUI7UUFDbkMsT0FBTyxFQUFFLGlHQUFpRztRQUMxRyxXQUFXLEVBQUUsK0VBQStFO0tBQzdGO0NBQ0YsQ0FBQztBQUVGLE1BQU0sWUFBWSxHQUFHO0lBQ25CO1FBQ0UsTUFBTSxFQUFFLHFCQUFxQjtRQUM3QixJQUFJLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLGlCQUFpQixDQUFDO1FBQ3pDLGFBQWEsRUFBRSxLQUFLO1FBQ3BCLFFBQVEsRUFBRSxRQUF5QjtRQUNuQyxPQUFPLEVBQUUsNEVBQTRFO1FBQ3JGLFdBQVcsRUFBRSx1RkFBdUY7S0FDckc7SUFDRDtRQUNFLE1BQU0sRUFBRSx3QkFBd0I7UUFDaEMsSUFBSSxFQUFFLENBQUMsTUFBTSxFQUFFLFNBQVMsRUFBRSxrQkFBa0IsQ0FBQztRQUM3QyxhQUFhLEVBQUUsS0FBSztRQUNwQixRQUFRLEVBQUUsTUFBdUI7UUFDakMsT0FBTyxFQUFFLGdGQUFnRjtRQUN6RixXQUFXLEVBQUUsNENBQTRDO0tBQzFE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ZXNDb250ZW50IjpbIi8qKlxuICogQ29uZmlndXJhdGlvbiBTY2FubmVyIC0gV2Fsa3MgY29uZmlndXJhdGlvbiBmaWxlcyAoSlNPTi9ZQU1MLy5lbnYpIGFuZCBmbGFnc1xuICogaW5zZWN1cmUgZGVmYXVsdHMuIFJ1bGVzIGFyZSBpbnRlbnRpb25hbGx5IGNvbnNlcnZhdGl2ZSB0byBhdm9pZCBmYWxzZVxuICogcG9zaXRpdmVzIHdoaWxlIHN0aWxsIGNhdGNoaW5nIGNvbW1vbiBkZXBsb3ltZW50IHJpc2tzIGNhbGxlZCBvdXQgaW4gdGhlXG4gKiBwcm9kdWN0aW9uIHJlYWRpbmVzcyByZXZpZXcuXG4gKi9cblxuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzL3Byb21pc2VzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeWFtbCBmcm9tICdqcy15YW1sJztcbmltcG9ydCB0eXBlIHsgU2VjdXJpdHlTY2FubmVyLCBTZWN1cml0eUZpbmRpbmcsIFNjYW5Db250ZXh0LCBTZXZlcml0eUxldmVsIH0gZnJvbSAnLi4vdHlwZXMuanMnO1xuXG5pbnRlcmZhY2UgQ29uZmlndXJhdGlvblNjYW5uZXJDb25maWcge1xuICBlbmFibGVkOiBib29sZWFuO1xuICBjaGVja0ZpbGVzOiBzdHJpbmdbXTtcbn1cblxudHlwZSBDb25maWdPYmplY3QgPSBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuXG5jb25zdCBTVVNQSUNJT1VTX1BBVFRFUk5TID0gW1xuICB7XG4gICAgbWF0Y2g6IChsaW5lOiBzdHJpbmcpID0+IC9ET0xMSE9VU0VfVEVMRU1FVFJZXFxzKj1cXHMqdHJ1ZS9pLnRlc3QobGluZSksXG4gICAgcnVsZUlkOiAnQ09ORklHLVRFTEVNRVRSWS1PUFQtSU4nLFxuICAgIHNldmVyaXR5OiAnbWVkaXVtJyBhcyBTZXZlcml0eUxldmVsLFxuICAgIG1lc3NhZ2U6ICdUZWxlbWV0cnkgc2hvdWxkIHJlbWFpbiBvcHQtaW4gYnkgZGVmYXVsdC4gUmVtb3ZlIERPTExIT1VTRV9URUxFTUVUUlk9dHJ1ZSBmcm9tIHNoYXJlZCBjb25maWdzLicsXG4gICAgcmVtZWRpYXRpb246ICdTZXQgRE9MTEhPVVNFX1RFTEVNRVRSWT1mYWxzZSBvciBkb2N1bWVudCB3aHkgb3B0LWluIGJlaGF2aW91ciBpcyBvdmVycmlkZGVuLidcbiAgfVxuXTtcblxuY29uc3QgQ09ORklHX1JVTEVTID0gW1xuICB7XG4gICAgcnVsZUlkOiAnQ09ORklHLUJVTEstUFJFVklFVycsXG4gICAgcGF0aDogWydzeW5jJywgJ2J1bGsnLCAncmVxdWlyZV9wcmV2aWV3J10sXG4gICAgaW5zZWN1cmVWYWx1ZTogZmFsc2UsXG4gICAgc2V2ZXJpdHk6ICdtZWRpdW0nIGFzIFNldmVyaXR5TGV2ZWwsXG4gICAgbWVzc2FnZTogJ0J1bGsgc3luYyBwcmV2aWV3IGlzIGRpc2FibGVkLiBVc2VycyBjb3VsZCB1cGxvYWQvZG93bmxvYWQgd2l0aG91dCByZXZpZXcuJyxcbiAgICByZW1lZGlhdGlvbjogJ1NldCBzeW5jLmJ1bGsucmVxdWlyZV9wcmV2aWV3IHRvIHRydWUgdG8gcmVxdWlyZSBjb25maXJtYXRpb24gYmVmb3JlIGJ1bGsgb3BlcmF0aW9ucy4nXG4gIH0sXG4gIHtcbiAgICBydWxlSWQ6ICdDT05GSUctU0VDUkVULVNDQU5OSU5HJyxcbiAgICBwYXRoOiBbJ3N5bmMnLCAncHJpdmFjeScsICdzY2FuX2Zvcl9zZWNyZXRzJ10sXG4gICAgaW5zZWN1cmVWYWx1ZTogZmFsc2UsXG4gICAgc2V2ZXJpdHk6ICdoaWdoJyBhcyBTZXZlcml0eUxldmVsLFxuICAgIG1lc3NhZ2U6ICdTZWNyZXQgc2Nhbm5pbmcgaXMgZGlzYWJsZWQgaW4gc3luYy4gVGhpcyBtYXkgbGVhayBjcmVkZW50aWFscyB3aGVuIHVwbG9hZGluZy4nLFxuICAgIHJlbWVkaWF0aW9uOiAnU2V0IHN5bmMucHJpdmFjeS5zY2FuX2Zvcl9zZWNyZXRzIHRvIHRydWUuJ1xuICB9LFxuICB7XG4gICAgcnVsZUlkOiAnQ09ORklHLVRFTEVNRVRSWS1ERUZBVUxUJyxcbiAgICBwYXRoOiBbJ2VsZW1lbnRzJywgJ2VuaGFuY2VkX2luZGV4JywgJ3RlbGVtZXRyeScsICdlbmFibGVkJ10sXG4gICAgaW5zZWN1cmVWYWx1ZTogdHJ1ZSxcbiAgICBzZXZlcml0eTogJ21lZGl1bScgYXMgU2V2ZXJpdHlMZXZlbCxcbiAgICBtZXNzYWdlOiAnRW5oYW5jZWQgaW5kZXggdGVsZW1ldHJ5IGlzIGVuYWJsZWQgYnkgZGVmYXVsdC4gSXQgc2hvdWxkIHJlcXVpcmUgZXhwbGljaXQgb3B0LWluLicsXG4gICAgcmVtZWRpYXRpb246ICdTZXQgZWxlbWVudHMuZW5oYW5jZWRfaW5kZXgudGVsZW1ldHJ5LmVuYWJsZWQgdG8gZmFsc2UgdW5sZXNzIHRoZSBkZXBsb3ltZW50IGhhcyBvYnRhaW5lZCBjb25zZW50LidcbiAgfVxuXTtcblxuZXhwb3J0IGNsYXNzIENvbmZpZ3VyYXRpb25TY2FubmVyIGltcGxlbWVudHMgU2VjdXJpdHlTY2FubmVyIHtcbiAgbmFtZSA9ICdDb25maWd1cmF0aW9uU2Nhbm5lcic7XG4gIHByaXZhdGUgY29uZmlnOiBDb25maWd1cmF0aW9uU2Nhbm5lckNvbmZpZztcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IENvbmZpZ3VyYXRpb25TY2FubmVyQ29uZmlnKSB7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gIH1cblxuICBhc3luYyBzY2FuKGNvbnRleHQ6IFNjYW5Db250ZXh0KTogUHJvbWlzZTxTZWN1cml0eUZpbmRpbmdbXT4ge1xuICAgIGlmICghdGhpcy5pc0VuYWJsZWQoKSkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cblxuICAgIGNvbnN0IGZpbmRpbmdzOiBTZWN1cml0eUZpbmRpbmdbXSA9IFtdO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBmaWxlcyA9IGF3YWl0IHRoaXMuY29sbGVjdENhbmRpZGF0ZUZpbGVzKGNvbnRleHQucHJvamVjdFJvb3QpO1xuICAgICAgZm9yIChjb25zdCBmaWxlUGF0aCBvZiBmaWxlcykge1xuICAgICAgICBjb25zdCBmaWxlRmluZGluZ3MgPSBhd2FpdCB0aGlzLmV2YWx1YXRlRmlsZShmaWxlUGF0aCk7XG4gICAgICAgIGZpbmRpbmdzLnB1c2goLi4uZmlsZUZpbmRpbmdzKTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdbQ29uZmlndXJhdGlvblNjYW5uZXJdIEZhaWxlZCB0byBzY2FuIGNvbmZpZ3VyYXRpb24nLCB7XG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4gZmluZGluZ3M7XG4gIH1cblxuICBpc0VuYWJsZWQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIEJvb2xlYW4odGhpcy5jb25maWc/LmVuYWJsZWQpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBjb2xsZWN0Q2FuZGlkYXRlRmlsZXMocHJvamVjdFJvb3Q6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nW10+IHtcbiAgICBjb25zdCBmaWxlczogc3RyaW5nW10gPSBbXTtcbiAgICBjb25zdCBleGNsdWRlRGlycyA9IG5ldyBTZXQoWydub2RlX21vZHVsZXMnLCAnZGlzdCcsICcuZ2l0J10pO1xuXG4gICAgY29uc3Qgd2FsayA9IGFzeW5jIChkaXI6IHN0cmluZykgPT4ge1xuICAgICAgY29uc3QgZW50cmllcyA9IGF3YWl0IGZzLnJlYWRkaXIoZGlyLCB7IHdpdGhGaWxlVHlwZXM6IHRydWUgfSk7XG4gICAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGVudHJpZXMpIHtcbiAgICAgICAgY29uc3QgZnVsbFBhdGggPSBwYXRoLmpvaW4oZGlyLCBlbnRyeS5uYW1lKTtcbiAgICAgICAgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgICAgICBpZiAoZXhjbHVkZURpcnMuaGFzKGVudHJ5Lm5hbWUpKSBjb250aW51ZTtcbiAgICAgICAgICBhd2FpdCB3YWxrKGZ1bGxQYXRoKTtcbiAgICAgICAgfSBlbHNlIGlmICh0aGlzLm1hdGNoZXNQYXR0ZXJucyhlbnRyeS5uYW1lKSkge1xuICAgICAgICAgIGZpbGVzLnB1c2goZnVsbFBhdGgpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIGF3YWl0IHdhbGsocHJvamVjdFJvb3QpO1xuICAgIHJldHVybiBmaWxlcztcbiAgfVxuXG4gIHByaXZhdGUgbWF0Y2hlc1BhdHRlcm5zKGZpbGVOYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICBpZiAoIXRoaXMuY29uZmlnLmNoZWNrRmlsZXMgfHwgdGhpcy5jb25maWcuY2hlY2tGaWxlcy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5jb25maWcuY2hlY2tGaWxlcy5zb21lKHBhdHRlcm4gPT4ge1xuICAgICAgY29uc3Qgbm9ybWFsaXplZFBhdHRlcm4gPSBwYXR0ZXJuLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuICAgICAgaWYgKG5vcm1hbGl6ZWRQYXR0ZXJuLnN0YXJ0c1dpdGgoJyonKSkge1xuICAgICAgICByZXR1cm4gZmlsZU5hbWUudG9Mb3dlckNhc2UoKS5lbmRzV2l0aChub3JtYWxpemVkUGF0dGVybi5zbGljZSgxKSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gZmlsZU5hbWUudG9Mb3dlckNhc2UoKSA9PT0gbm9ybWFsaXplZFBhdHRlcm47XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGV2YWx1YXRlRmlsZShmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTxTZWN1cml0eUZpbmRpbmdbXT4ge1xuICAgIGNvbnN0IGZpbmRpbmdzOiBTZWN1cml0eUZpbmRpbmdbXSA9IFtdO1xuICAgIGNvbnN0IGV4dGVuc2lvbiA9IHBhdGguZXh0bmFtZShmaWxlUGF0aCkudG9Mb3dlckNhc2UoKTtcbiAgICBjb25zdCBiYXNlTmFtZSA9IHBhdGguYmFzZW5hbWUoZmlsZVBhdGgpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGNvbnRlbnQgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgJ3V0Zi04Jyk7XG5cbiAgICAgIGlmIChiYXNlTmFtZS5zdGFydHNXaXRoKCcuZW52JykpIHtcbiAgICAgICAgZmluZGluZ3MucHVzaCguLi50aGlzLmV2YWx1YXRlRW52RmlsZShjb250ZW50LCBmaWxlUGF0aCkpO1xuICAgICAgfSBlbHNlIGlmIChleHRlbnNpb24gPT09ICcuanNvbicpIHtcbiAgICAgICAgY29uc3QgcGFyc2VkID0gSlNPTi5wYXJzZShjb250ZW50KSBhcyBDb25maWdPYmplY3Q7XG4gICAgICAgIGZpbmRpbmdzLnB1c2goLi4udGhpcy5ldmFsdWF0ZUNvbmZpZ09iamVjdChwYXJzZWQsIGZpbGVQYXRoKSk7XG4gICAgICB9IGVsc2UgaWYgKGV4dGVuc2lvbiA9PT0gJy55bWwnIHx8IGV4dGVuc2lvbiA9PT0gJy55YW1sJykge1xuICAgICAgICAvLyBVc2UgRkFJTFNBRkVfU0NIRU1BIGZvciBzYWZlIFlBTUwgcGFyc2luZyAobm8gYXJiaXRyYXJ5IG9iamVjdCBpbnN0YW50aWF0aW9uKVxuICAgICAgICBjb25zdCBwYXJzZWQgPSB5YW1sLmxvYWQoY29udGVudCwgeyBzY2hlbWE6IHlhbWwuRkFJTFNBRkVfU0NIRU1BIH0pIGFzIENvbmZpZ09iamVjdDtcbiAgICAgICAgaWYgKHBhcnNlZCAmJiB0eXBlb2YgcGFyc2VkID09PSAnb2JqZWN0Jykge1xuICAgICAgICAgIGZpbmRpbmdzLnB1c2goLi4udGhpcy5ldmFsdWF0ZUNvbmZpZ09iamVjdChwYXJzZWQsIGZpbGVQYXRoKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdbQ29uZmlndXJhdGlvblNjYW5uZXJdIEZhaWxlZCB0byBwYXJzZSBjb25maWcgZmlsZScsIHtcbiAgICAgICAgZmlsZTogZmlsZVBhdGgsXG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIHJldHVybiBmaW5kaW5ncztcbiAgfVxuXG4gIHByaXZhdGUgZXZhbHVhdGVFbnZGaWxlKGNvbnRlbnQ6IHN0cmluZywgZmlsZVBhdGg6IHN0cmluZyk6IFNlY3VyaXR5RmluZGluZ1tdIHtcbiAgICBjb25zdCBmaW5kaW5nczogU2VjdXJpdHlGaW5kaW5nW10gPSBbXTtcbiAgICBjb25zdCBsaW5lcyA9IGNvbnRlbnQuc3BsaXQoL1xccj9cXG4vKTtcbiAgICBmb3IgKGNvbnN0IHBhdHRlcm4gb2YgU1VTUElDSU9VU19QQVRURVJOUykge1xuICAgICAgaWYgKGxpbmVzLnNvbWUobGluZSA9PiBwYXR0ZXJuLm1hdGNoKGxpbmUpKSkge1xuICAgICAgICBmaW5kaW5ncy5wdXNoKHtcbiAgICAgICAgICBydWxlSWQ6IHBhdHRlcm4ucnVsZUlkLFxuICAgICAgICAgIHNldmVyaXR5OiBwYXR0ZXJuLnNldmVyaXR5LFxuICAgICAgICAgIG1lc3NhZ2U6IHBhdHRlcm4ubWVzc2FnZSxcbiAgICAgICAgICBmaWxlOiBwYXRoLnJlbGF0aXZlKHByb2Nlc3MuY3dkKCksIGZpbGVQYXRoKSxcbiAgICAgICAgICByZW1lZGlhdGlvbjogcGF0dGVybi5yZW1lZGlhdGlvbixcbiAgICAgICAgICBjb25maWRlbmNlOiAnaGlnaCdcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBmaW5kaW5ncztcbiAgfVxuXG4gIHByaXZhdGUgZXZhbHVhdGVDb25maWdPYmplY3QoY29uZmlnOiBDb25maWdPYmplY3QsIGZpbGVQYXRoOiBzdHJpbmcpOiBTZWN1cml0eUZpbmRpbmdbXSB7XG4gICAgY29uc3QgZmluZGluZ3M6IFNlY3VyaXR5RmluZGluZ1tdID0gW107XG4gICAgZm9yIChjb25zdCBydWxlIG9mIENPTkZJR19SVUxFUykge1xuICAgICAgY29uc3QgdmFsdWUgPSB0aGlzLmdldFZhbHVlKGNvbmZpZywgcnVsZS5wYXRoKTtcbiAgICAgIGlmICh2YWx1ZSA9PT0gdW5kZWZpbmVkKSBjb250aW51ZTtcblxuICAgICAgLy8gRklYOiBGQUlMU0FGRV9TQ0hFTUEgcGFyc2VzIGJvb2xlYW5zIGFzIHN0cmluZ3MgKFwidHJ1ZVwiL1wiZmFsc2VcIilcbiAgICAgIC8vIE5vcm1hbGl6ZSBmb3IgY29tcGFyaXNvbjogY29udmVydCBzdHJpbmcgYm9vbGVhbnMgdG8gYWN0dWFsIGJvb2xlYW5zXG4gICAgICBjb25zdCBub3JtYWxpemVkVmFsdWUgPSB0aGlzLm5vcm1hbGl6ZVZhbHVlKHZhbHVlKTtcblxuICAgICAgaWYgKG5vcm1hbGl6ZWRWYWx1ZSA9PT0gcnVsZS5pbnNlY3VyZVZhbHVlKSB7XG4gICAgICAgIGZpbmRpbmdzLnB1c2goe1xuICAgICAgICAgIHJ1bGVJZDogcnVsZS5ydWxlSWQsXG4gICAgICAgICAgc2V2ZXJpdHk6IHJ1bGUuc2V2ZXJpdHksXG4gICAgICAgICAgbWVzc2FnZTogcnVsZS5tZXNzYWdlLFxuICAgICAgICAgIGZpbGU6IHBhdGgucmVsYXRpdmUocHJvY2Vzcy5jd2QoKSwgZmlsZVBhdGgpLFxuICAgICAgICAgIHJlbWVkaWF0aW9uOiBydWxlLnJlbWVkaWF0aW9uLFxuICAgICAgICAgIGNvbmZpZGVuY2U6ICdtZWRpdW0nXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmluZGluZ3M7XG4gIH1cblxuICAvKipcbiAgICogTm9ybWFsaXplIFlBTUwgRkFJTFNBRkVfU0NIRU1BIHZhbHVlcyBmb3IgY29tcGFyaXNvbi5cbiAgICogRkFJTFNBRkVfU0NIRU1BIHBhcnNlcyBldmVyeXRoaW5nIGFzIHN0cmluZ3MsIHNvIFwiZmFsc2VcIiAtPiBmYWxzZSwgXCJ0cnVlXCIgLT4gdHJ1ZS5cbiAgICovXG4gIHByaXZhdGUgbm9ybWFsaXplVmFsdWUodmFsdWU6IGFueSk6IGFueSB7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGNvbnN0IGxvd2VyID0gdmFsdWUudG9Mb3dlckNhc2UoKTtcbiAgICAgIGlmIChsb3dlciA9PT0gJ3RydWUnKSByZXR1cm4gdHJ1ZTtcbiAgICAgIGlmIChsb3dlciA9PT0gJ2ZhbHNlJykgcmV0dXJuIGZhbHNlO1xuICAgICAgLy8gVHJ5IHRvIHBhcnNlIGFzIG51bWJlclxuICAgICAgY29uc3QgbnVtID0gTnVtYmVyKHZhbHVlKTtcbiAgICAgIGlmICghaXNOYU4obnVtKSAmJiB2YWx1ZS50cmltKCkgIT09ICcnKSByZXR1cm4gbnVtO1xuICAgIH1cbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cblxuICBwcml2YXRlIGdldFZhbHVlKGNvbmZpZzogQ29uZmlnT2JqZWN0LCBwYXRoU2VnbWVudHM6IHN0cmluZ1tdKTogYW55IHtcbiAgICByZXR1cm4gcGF0aFNlZ21lbnRzLnJlZHVjZSgodmFsdWUsIGtleSkgPT4ge1xuICAgICAgaWYgKHZhbHVlICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYga2V5IGluIHZhbHVlKSB7XG4gICAgICAgIHJldHVybiB2YWx1ZVtrZXldO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9LCBjb25maWcgYXMgYW55KTtcbiAgfVxufVxuIl19