@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.
274 lines • 36.4 kB
JavaScript
/**
* Security Auditor - Core orchestrator for security scanning
* Implements automated security auditing for DollhouseMCP (Issue #53)
*/
import { CodeScanner } from './scanners/CodeScanner.js';
import { DependencyScanner } from './scanners/DependencyScanner.js';
import { ConfigurationScanner } from './scanners/ConfigurationScanner.js';
import { ConsoleReporter } from './reporters/ConsoleReporter.js';
import { MarkdownReporter } from './reporters/MarkdownReporter.js';
import { JsonReporter } from './reporters/JsonReporter.js';
import { shouldSuppress } from './config/suppressions.js';
import fs from 'fs/promises';
export class SecurityAuditor {
config;
scanners = [];
suppressions = new Map();
constructor(config) {
this.config = config;
this.initializeScanners();
this.loadSuppressions();
}
/**
* Initialize enabled scanners based on configuration
*/
initializeScanners() {
if (this.config.scanners.code.enabled) {
this.scanners.push(new CodeScanner(this.config.scanners.code));
}
if (this.config.scanners.dependencies.enabled) {
this.scanners.push(new DependencyScanner(this.config.scanners.dependencies));
}
if (this.config.scanners.configuration.enabled) {
this.scanners.push(new ConfigurationScanner(this.config.scanners.configuration));
}
// Audit logging would go here if SecurityMonitor supported audit events
console.log(`SecurityAuditor: Initialized ${this.scanners.length} security scanners`);
}
/**
* Load suppression rules from configuration
*/
loadSuppressions() {
if (!this.config.suppressions)
return;
for (const suppression of this.config.suppressions) {
const key = suppression.file || '*';
if (!this.suppressions.has(key)) {
this.suppressions.set(key, new Set());
}
this.suppressions.get(key).add(suppression.rule);
}
}
/**
* Run security audit on the project
*/
async audit(projectRoot = process.cwd()) {
const startTime = Date.now();
const context = { projectRoot };
const allFindings = [];
const errors = [];
const scannedFilesSet = new Set();
console.log(`SecurityAuditor: Starting security audit of ${projectRoot}`);
// Run all enabled scanners
for (const scanner of this.scanners) {
try {
const findings = await scanner.scan(context);
const filteredFindings = this.filterSuppressions(findings);
allFindings.push(...filteredFindings);
// Track unique files that were scanned
for (const finding of findings) {
if (finding.file) {
scannedFilesSet.add(finding.file);
}
}
}
catch (error) {
const errorMessage = `Scanner ${scanner.name} failed: ${error instanceof Error ? error.message : String(error)}`;
errors.push(errorMessage);
console.error(`SecurityAuditor: ${errorMessage}`);
}
}
const duration = Date.now() - startTime;
const result = this.createScanResult(allFindings, duration, scannedFilesSet.size, errors);
// Log audit completion
console.log(`SecurityAuditor: Audit completed: ${result.summary.total} findings in ${duration}ms`);
// Generate reports
await this.generateReports(result);
// Check if build should fail
if (this.shouldFailBuild(result)) {
throw new Error(`Security audit failed: ${result.summary.bySeverity.critical} critical, ${result.summary.bySeverity.high} high severity issues found`);
}
return result;
}
/**
* Filter out suppressed findings
*/
filterSuppressions(findings) {
const suppressedFindings = [];
const filtered = findings.filter(finding => {
try {
// Check comprehensive suppressions (includes both file-based and pattern-based)
if (shouldSuppress(finding.ruleId, finding.file)) {
// Log suppression for audit trail if verbose mode is enabled
if (this.config.reporting?.verbose) {
suppressedFindings.push({
rule: finding.ruleId,
file: finding.file
});
}
return false;
}
// Check legacy config-based suppressions if they exist
// This maintains backward compatibility with existing configs
if (this.config.suppressions && this.config.suppressions.length > 0) {
const globalSuppressions = this.suppressions.get('*');
if (globalSuppressions?.has(finding.ruleId)) {
if (this.config.reporting?.verbose) {
suppressedFindings.push({
rule: finding.ruleId,
file: finding.file,
reason: 'Config-based global suppression'
});
}
return false;
}
if (finding.file) {
const fileSuppressions = this.suppressions.get(finding.file);
if (fileSuppressions?.has(finding.ruleId)) {
if (this.config.reporting?.verbose) {
suppressedFindings.push({
rule: finding.ruleId,
file: finding.file,
reason: 'Config-based file suppression'
});
}
return false;
}
}
}
return true;
}
catch (error) {
// If suppression check fails, log error but don't suppress the finding
console.error(`Error checking suppression for ${finding.ruleId} in ${finding.file}:`, error);
return true;
}
});
// Log suppression summary if verbose and suppressions were applied
if (this.config.reporting?.verbose && suppressedFindings.length > 0) {
console.log(`\nSecurityAuditor: Suppressed ${suppressedFindings.length} findings:`);
suppressedFindings.forEach(s => {
console.log(` - ${s.rule} in ${s.file || 'global'}${s.reason ? ` (${s.reason})` : ''}`);
});
}
return filtered;
}
/**
* Create scan result summary
*/
createScanResult(findings, duration, scannedFiles, errors) {
const bySeverity = {
info: 0,
low: 0,
medium: 0,
high: 0,
critical: 0
};
const byCategory = {};
for (const finding of findings) {
bySeverity[finding.severity]++;
// Extract category from ruleId (e.g., SEC-CODE-001 -> CODE)
const category = finding.ruleId.split('-')[1] || 'OTHER';
byCategory[category] = (byCategory[category] || 0) + 1;
}
return {
timestamp: new Date(),
duration,
scannedFiles,
findings,
summary: {
total: findings.length,
bySeverity,
byCategory
},
errors: errors.length > 0 ? errors : undefined
};
}
/**
* Generate reports in configured formats
*/
async generateReports(result) {
for (const format of this.config.reporting.formats) {
try {
switch (format) {
case 'console':
const consoleReporter = new ConsoleReporter(result);
console.log(consoleReporter.generate());
break;
case 'markdown':
const markdownReporter = new MarkdownReporter(result);
const mdReport = markdownReporter.generate();
await fs.writeFile('security-audit-report.md', mdReport);
break;
case 'json':
const jsonReporter = new JsonReporter(result);
const jsonReport = JSON.stringify(jsonReporter.generate(), null, 2);
await fs.writeFile('security-audit-report.json', jsonReport);
break;
// SARIF format would be implemented similarly
}
}
catch (error) {
console.error(`SecurityAuditor: Failed to generate ${format} report: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Determine if the build should fail based on findings
*/
shouldFailBuild(result) {
const thresholds = {
info: 5,
low: 4,
medium: 3,
high: 2,
critical: 1
};
const failThreshold = thresholds[this.config.reporting.failOnSeverity];
for (const [severity, count] of Object.entries(result.summary.bySeverity)) {
if (count > 0 && thresholds[severity] <= failThreshold) {
return true;
}
}
return false;
}
/**
* Get default configuration
*/
static getDefaultConfig() {
return {
enabled: true,
scanners: {
code: {
enabled: true,
rules: ['OWASP-Top-10', 'CWE-Top-25', 'DollhouseMCP-Security'],
exclude: ['node_modules/**', 'dist/**', 'coverage/**']
},
dependencies: {
enabled: true,
severityThreshold: 'high',
checkLicenses: true,
allowedLicenses: ['MIT', 'Apache-2.0', 'BSD-3-Clause', 'ISC', 'AGPL-3.0']
},
configuration: {
enabled: true,
checkFiles: ['*.yml', '*.yaml', '*.json', '.env.example']
}
},
reporting: {
formats: ['console', 'markdown'],
createIssues: true,
commentOnPr: true,
failOnSeverity: 'high'
},
suppressions: [
{
rule: 'SEC-TEST-001',
file: '__tests__/**/*',
reason: 'Test files may contain security test patterns'
}
]
};
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VjdXJpdHlBdWRpdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlY3VyaXR5L2F1ZGl0L1NlY3VyaXR5QXVkaXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFXSCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDeEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDcEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDMUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFMUQsT0FBTyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRTdCLE1BQU0sT0FBTyxlQUFlO0lBQ2xCLE1BQU0sQ0FBc0I7SUFDNUIsUUFBUSxHQUFzQixFQUFFLENBQUM7SUFDakMsWUFBWSxHQUE2QixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRTNELFlBQVksTUFBMkI7UUFDckMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFDMUIsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7SUFDMUIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssa0JBQWtCO1FBQ3hCLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3RDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FB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MUQ7YUFDRjtZQUNELFNBQVMsRUFBRTtnQkFDVCxPQUFPLEVBQUUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxDQUFDO2dCQUNoQyxZQUFZLEVBQUUsSUFBSTtnQkFDbEIsV0FBVyxFQUFFLElBQUk7Z0JBQ2pCLGNBQWMsRUFBRSxNQUFNO2FBQ3ZCO1lBQ0QsWUFBWSxFQUFFO2dCQUNaO29CQUNFLElBQUksRUFBRSxjQUFjO29CQUNwQixJQUFJLEVBQUUsZ0JBQWdCO29CQUN0QixNQUFNLEVBQUUsK0NBQStDO2lCQUN4RDthQUNGO1NBQ0YsQ0FBQztJQUNKLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2VjdXJpdHkgQXVkaXRvciAtIENvcmUgb3JjaGVzdHJhdG9yIGZvciBzZWN1cml0eSBzY2FubmluZ1xuICogSW1wbGVtZW50cyBhdXRvbWF0ZWQgc2VjdXJpdHkgYXVkaXRpbmcgZm9yIERvbGxob3VzZU1DUCAoSXNzdWUgIzUzKVxuICovXG5cbi8vIGltcG9ydCB7IFNlY3VyaXR5TW9uaXRvciB9IGZyb20gJy4uL3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgdHlwZSB7IFxuICBTZWN1cml0eUF1ZGl0Q29uZmlnLCBcbiAgU2NhbkNvbnRleHQsIFxuICBTY2FuUmVzdWx0LCBcbiAgU2VjdXJpdHlGaW5kaW5nLCBcbiAgU2VjdXJpdHlTY2FubmVyLFxuICBTZXZlcml0eUxldmVsIFxufSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7IENvZGVTY2FubmVyIH0gZnJvbSAnLi9zY2FubmVycy9Db2RlU2Nhbm5lci5qcyc7XG5pbXBvcnQgeyBEZXBlbmRlbmN5U2Nhbm5lciB9IGZyb20gJy4vc2Nhbm5lcnMvRGVwZW5kZW5jeVNjYW5uZXIuanMnO1xuaW1wb3J0IHsgQ29uZmlndXJhdGlvblNjYW5uZXIgfSBmcm9tICcuL3NjYW5uZXJzL0NvbmZpZ3VyYXRpb25TY2FubmVyLmpzJztcbmltcG9ydCB7IENvbnNvbGVSZXBvcnRlciB9IGZyb20gJy4vcmVwb3J0ZXJzL0NvbnNvbGVSZXBvcnRlci5qcyc7XG5pbXBvcnQgeyBNYXJrZG93blJlcG9ydGVyIH0gZnJvbSAnLi9yZXBvcnRlcnMvTWFya2Rvd25SZXBvcnRlci5qcyc7XG5pbXBvcnQgeyBKc29uUmVwb3J0ZXIgfSBmcm9tICcuL3JlcG9ydGVycy9Kc29uUmVwb3J0ZXIuanMnO1xuaW1wb3J0IHsgc2hvdWxkU3VwcHJlc3MgfSBmcm9tICcuL2NvbmZpZy9zdXBwcmVzc2lvbnMuanMnO1xuaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuXG5leHBvcnQgY2xhc3MgU2VjdXJpdHlBdWRpdG9yIHtcbiAgcHJpdmF0ZSBjb25maWc6IFNlY3VyaXR5QXVkaXRDb25maWc7XG4gIHByaXZhdGUgc2Nhbm5lcnM6IFNlY3VyaXR5U2Nhbm5lcltdID0gW107XG4gIHByaXZhdGUgc3VwcHJlc3Npb25zOiBNYXA8c3RyaW5nLCBTZXQ8c3RyaW5nPj4gPSBuZXcgTWFwKCk7XG5cbiAgY29uc3RydWN0b3IoY29uZmlnOiBTZWN1cml0eUF1ZGl0Q29uZmlnKSB7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gICAgdGhpcy5pbml0aWFsaXplU2Nhbm5lcnMoKTtcbiAgICB0aGlzLmxvYWRTdXBwcmVzc2lvbnMoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIGVuYWJsZWQgc2Nhbm5lcnMgYmFzZWQgb24gY29uZmlndXJhdGlvblxuICAgKi9cbiAgcHJpdmF0ZSBpbml0aWFsaXplU2Nhbm5lcnMoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY29uZmlnLnNjYW5uZXJzLmNvZGUuZW5hYmxlZCkge1xuICAgICAgdGhpcy5zY2FubmVycy5wdXNoKG5ldyBDb2RlU2Nhbm5lcih0aGlzLmNvbmZpZy5zY2FubmVycy5jb2RlKSk7XG4gICAgfVxuICAgIFxuICAgIGlmICh0aGlzLmNvbmZpZy5zY2FubmVycy5kZXBlbmRlbmNpZXMuZW5hYmxlZCkge1xuICAgICAgdGhpcy5zY2FubmVycy5wdXNoKG5ldyBEZXBlbmRlbmN5U2Nhbm5lcih0aGlzLmNvbmZpZy5zY2FubmVycy5kZXBlbmRlbmNpZXMpKTtcbiAgICB9XG4gICAgXG4gICAgaWYgKHRoaXMuY29uZmlnLnNjYW5uZXJzLmNvbmZpZ3VyYXRpb24uZW5hYmxlZCkge1xuICAgICAgdGhpcy5zY2FubmVycy5wdXNoKG5ldyBDb25maWd1cmF0aW9uU2Nhbm5lcih0aGlzLmNvbmZpZy5zY2FubmVycy5jb25maWd1cmF0aW9uKSk7XG4gICAgfVxuXG4gICAgLy8gQXVkaXQgbG9nZ2luZyB3b3VsZCBnbyBoZXJlIGlmIFNlY3VyaXR5TW9uaXRvciBzdXBwb3J0ZWQgYXVkaXQgZXZlbnRzXG4gICAgY29uc29sZS5sb2coYFNlY3VyaXR5QXVkaXRvcjogSW5pdGlhbGl6ZWQgJHt0aGlzLnNjYW5uZXJzLmxlbmd0aH0gc2VjdXJpdHkgc2Nhbm5lcnNgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBMb2FkIHN1cHByZXNzaW9uIHJ1bGVzIGZyb20gY29uZmlndXJhdGlvblxuICAgKi9cbiAgcHJpdmF0ZSBsb2FkU3VwcHJlc3Npb25zKCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5jb25maWcuc3VwcHJlc3Npb25zKSByZXR1cm47XG5cbiAgICBmb3IgKGNvbnN0IHN1cHByZXNzaW9uIG9mIHRoaXMuY29uZmlnLnN1cHByZXNzaW9ucykge1xuICAgICAgY29uc3Qga2V5ID0gc3VwcHJlc3Npb24uZmlsZSB8fCAnKic7XG4gICAgICBpZiAoIXRoaXMuc3VwcHJlc3Npb25zLmhhcyhrZXkpKSB7XG4gICAgICAgIHRoaXMuc3VwcHJlc3Npb25zLnNldChrZXksIG5ldyBTZXQoKSk7XG4gICAgICB9XG4gICAgICB0aGlzLnN1cHByZXNzaW9ucy5nZXQoa2V5KSEuYWRkKHN1cHByZXNzaW9uLnJ1bGUpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSdW4gc2VjdXJpdHkgYXVkaXQgb24gdGhlIHByb2plY3RcbiAgICovXG4gIGFzeW5jIGF1ZGl0KHByb2plY3RSb290OiBzdHJpbmcgPSBwcm9jZXNzLmN3ZCgpKTogUHJvbWlzZTxTY2FuUmVzdWx0PiB7XG4gICAgY29uc3Qgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcbiAgICBjb25zdCBjb250ZXh0OiBTY2FuQ29udGV4dCA9IHsgcHJvamVjdFJvb3QgfTtcbiAgICBjb25zdCBhbGxGaW5kaW5nczogU2VjdXJpdHlGaW5kaW5nW10gPSBbXTtcbiAgICBjb25zdCBlcnJvcnM6IHN0cmluZ1tdID0gW107XG4gICAgY29uc3Qgc2Nhbm5lZEZpbGVzU2V0ID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cbiAgICBjb25zb2xlLmxvZyhgU2VjdXJpdHlBdWRpdG9yOiBTdGFydGluZyBzZWN1cml0eSBhdWRpdCBvZiAke3Byb2plY3RSb290fWApO1xuXG4gICAgLy8gUnVuIGFsbCBlbmFibGVkIHNjYW5uZXJzXG4gICAgZm9yIChjb25zdCBzY2FubmVyIG9mIHRoaXMuc2Nhbm5lcnMpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGZpbmRpbmdzID0gYXdhaXQgc2Nhbm5lci5zY2FuKGNvbnRleHQpO1xuICAgICAgICBjb25zdCBmaWx0ZXJlZEZpbmRpbmdzID0gdGhpcy5maWx0ZXJTdXBwcmVzc2lvbnMoZmluZGluZ3MpO1xuICAgICAgICBhbGxGaW5kaW5ncy5wdXNoKC4uLmZpbHRlcmVkRmluZGluZ3MpO1xuICAgICAgICAvLyBUcmFjayB1bmlxdWUgZmlsZXMgdGhhdCB3ZXJlIHNjYW5uZWRcbiAgICAgICAgZm9yIChjb25zdCBmaW5kaW5nIG9mIGZpbmRpbmdzKSB7XG4gICAgICAgICAgaWYgKGZpbmRpbmcuZmlsZSkge1xuICAgICAgICAgICAgc2Nhbm5lZEZpbGVzU2V0LmFkZChmaW5kaW5nLmZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc3QgZXJyb3JNZXNzYWdlID0gYFNjYW5uZXIgJHtzY2FubmVyLm5hbWV9IGZhaWxlZDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YDtcbiAgICAgICAgZXJyb3JzLnB1c2goZXJyb3JNZXNzYWdlKTtcbiAgICAgICAgY29uc29sZS5lcnJvcihgU2VjdXJpdHlBdWRpdG9yOiAke2Vycm9yTWVzc2FnZX1gKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydFRpbWU7XG4gICAgY29uc3QgcmVzdWx0ID0gdGhpcy5jcmVhdGVTY2FuUmVzdWx0KGFsbEZpbmRpbmdzLCBkdXJhdGlvbiwgc2Nhbm5lZEZpbGVzU2V0LnNpemUsIGVycm9ycyk7XG5cbiAgICAvLyBMb2cgYXVkaXQgY29tcGxldGlvblxuICAgIGNvbnNvbGUubG9nKGBTZWN1cml0eUF1ZGl0b3I6IEF1ZGl0IGNvbXBsZXRlZDogJHtyZXN1bHQuc3VtbWFyeS50b3RhbH0gZmluZGluZ3MgaW4gJHtkdXJhdGlvbn1tc2ApO1xuXG4gICAgLy8gR2VuZXJhdGUgcmVwb3J0c1xuICAgIGF3YWl0IHRoaXMuZ2VuZXJhdGVSZXBvcnRzKHJlc3VsdCk7XG5cbiAgICAvLyBDaGVjayBpZiBidWlsZCBzaG91bGQgZmFpbFxuICAgIGlmICh0aGlzLnNob3VsZEZhaWxCdWlsZChyZXN1bHQpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFNlY3VyaXR5IGF1ZGl0IGZhaWxlZDogJHtyZXN1bHQuc3VtbWFyeS5ieVNldmVyaXR5LmNyaXRpY2FsfSBjcml0aWNhbCwgJHtyZXN1bHQuc3VtbWFyeS5ieVNldmVyaXR5LmhpZ2h9IGhpZ2ggc2V2ZXJpdHkgaXNzdWVzIGZvdW5kYCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBGaWx0ZXIgb3V0IHN1cHByZXNzZWQgZmluZGluZ3NcbiAgICovXG4gIHByaXZhdGUgZmlsdGVyU3VwcHJlc3Npb25zKGZpbmRpbmdzOiBTZWN1cml0eUZpbmRpbmdbXSk6IFNlY3VyaXR5RmluZGluZ1tdIHtcbiAgICBjb25zdCBzdXBwcmVzc2VkRmluZGluZ3M6IEFycmF5PHtydWxlOiBzdHJpbmc7IGZpbGU/OiBzdHJpbmc7IHJlYXNvbj86IHN0cmluZ30+ID0gW107XG4gICAgXG4gICAgY29uc3QgZmlsdGVyZWQgPSBmaW5kaW5ncy5maWx0ZXIoZmluZGluZyA9PiB7XG4gICAgICB0cnkge1xuICAgICAgICAvLyBDaGVjayBjb21wcmVoZW5zaXZlIHN1cHByZXNzaW9ucyAoaW5jbHVkZXMgYm90aCBmaWxlLWJhc2VkIGFuZCBwYXR0ZXJuLWJhc2VkKVxuICAgICAgICBpZiAoc2hvdWxkU3VwcHJlc3MoZmluZGluZy5ydWxlSWQsIGZpbmRpbmcuZmlsZSkpIHtcbiAgICAgICAgICAvLyBMb2cgc3VwcHJlc3Npb24gZm9yIGF1ZGl0IHRyYWlsIGlmIHZlcmJvc2UgbW9kZSBpcyBlbmFibGVkXG4gICAgICAgICAgaWYgKHRoaXMuY29uZmlnLnJlcG9ydGluZz8udmVyYm9zZSkge1xuICAgICAgICAgICAgc3VwcHJlc3NlZEZpbmRpbmdzLnB1c2goe1xuICAgICAgICAgICAgICBydWxlOiBmaW5kaW5nLnJ1bGVJZCxcbiAgICAgICAgICAgICAgZmlsZTogZmluZGluZy5maWxlXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBDaGVjayBsZWdhY3kgY29uZmlnLWJhc2VkIHN1cHByZXNzaW9ucyBpZiB0aGV5IGV4aXN0XG4gICAgICAgIC8vIFRoaXMgbWFpbnRhaW5zIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkgd2l0aCBleGlzdGluZyBjb25maWdzXG4gICAgICAgIGlmICh0aGlzLmNvbmZpZy5zdXBwcmVzc2lvbnMgJiYgdGhpcy5jb25maWcuc3VwcHJlc3Npb25zLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBjb25zdCBnbG9iYWxTdXBwcmVzc2lvbnMgPSB0aGlzLnN1cHByZXNzaW9ucy5nZXQoJyonKTtcbiAgICAgICAgICBpZiAoZ2xvYmFsU3VwcHJlc3Npb25zPy5oYXMoZmluZGluZy5ydWxlSWQpKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5jb25maWcucmVwb3J0aW5nPy52ZXJib3NlKSB7XG4gICAgICAgICAgICAgIHN1cHByZXNzZWRGaW5kaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgICBydWxlOiBmaW5kaW5nLnJ1bGVJZCxcbiAgICAgICAgICAgICAgICBmaWxlOiBmaW5kaW5nLmZpbGUsXG4gICAgICAgICAgICAgICAgcmVhc29uOiAnQ29uZmlnLWJhc2VkIGdsb2JhbCBzdXBwcmVzc2lvbidcbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKGZpbmRpbmcuZmlsZSkge1xuICAgICAgICAgICAgY29uc3QgZmlsZVN1cHByZXNzaW9ucyA9IHRoaXMuc3VwcHJlc3Npb25zLmdldChmaW5kaW5nLmZpbGUpO1xuICAgICAgICAgICAgaWYgKGZpbGVTdXBwcmVzc2lvbnM/LmhhcyhmaW5kaW5nLnJ1bGVJZCkpIHtcbiAgICAgICAgICAgICAgaWYgKHRoaXMuY29uZmlnLnJlcG9ydGluZz8udmVyYm9zZSkge1xuICAgICAgICAgICAgICAgIHN1cHByZXNzZWRGaW5kaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgICAgIHJ1bGU6IGZpbmRpbmcucnVsZUlkLFxuICAgICAgICAgICAgICAgICAgZmlsZTogZmluZGluZy5maWxlLFxuICAgICAgICAgICAgICAgICAgcmVhc29uOiAnQ29uZmlnLWJhc2VkIGZpbGUgc3VwcHJlc3Npb24nXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgLy8gSWYgc3VwcHJlc3Npb24gY2hlY2sgZmFpbHMsIGxvZyBlcnJvciBidXQgZG9uJ3Qgc3VwcHJlc3MgdGhlIGZpbmRpbmdcbiAgICAgICAgY29uc29sZS5lcnJvcihgRXJyb3IgY2hlY2tpbmcgc3VwcHJlc3Npb24gZm9yICR7ZmluZGluZy5ydWxlSWR9IGluICR7ZmluZGluZy5maWxlfTpgLCBlcnJvcik7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH0pO1xuICAgIFxuICAgIC8vIExvZyBzdXBwcmVzc2lvbiBzdW1tYXJ5IGlmIHZlcmJvc2UgYW5kIHN1cHByZXNzaW9ucyB3ZXJlIGFwcGxpZWRcbiAgICBpZiAodGhpcy5jb25maWcucmVwb3J0aW5nPy52ZXJib3NlICYmIHN1cHByZXNzZWRGaW5kaW5ncy5sZW5ndGggPiAwKSB7XG4gICAgICBjb25zb2xlLmxvZyhgXFxuU2VjdXJpdHlBdWRpdG9yOiBTdXBwcmVzc2VkICR7c3VwcHJlc3NlZEZpbmRpbmdzLmxlbmd0aH0gZmluZGluZ3M6YCk7XG4gICAgICBzdXBwcmVzc2VkRmluZGluZ3MuZm9yRWFjaChzID0+IHtcbiAgICAgICAgY29uc29sZS5sb2coYCAgLSAke3MucnVsZX0gaW4gJHtzLmZpbGUgfHwgJ2dsb2JhbCd9JHtzLnJlYXNvbiA/IGAgKCR7cy5yZWFzb259KWAgOiAnJ31gKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgICBcbiAgICByZXR1cm4gZmlsdGVyZWQ7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIHNjYW4gcmVzdWx0IHN1bW1hcnlcbiAgICovXG4gIHByaXZhdGUgY3JlYXRlU2NhblJlc3VsdChcbiAgICBmaW5kaW5nczogU2VjdXJpdHlGaW5kaW5nW10sIFxuICAgIGR1cmF0aW9uOiBudW1iZXIsIFxuICAgIHNjYW5uZWRGaWxlczogbnVtYmVyLFxuICAgIGVycm9yczogc3RyaW5nW11cbiAgKTogU2NhblJlc3VsdCB7XG4gICAgY29uc3QgYnlTZXZlcml0eTogUmVjb3JkPFNldmVyaXR5TGV2ZWwsIG51bWJlcj4gPSB7XG4gICAgICBpbmZvOiAwLFxuICAgICAgbG93OiAwLFxuICAgICAgbWVkaXVtOiAwLFxuICAgICAgaGlnaDogMCxcbiAgICAgIGNyaXRpY2FsOiAwXG4gICAgfTtcblxuICAgIGNvbnN0IGJ5Q2F0ZWdvcnk6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gPSB7fTtcblxuICAgIGZvciAoY29uc3QgZmluZGluZyBvZiBmaW5kaW5ncykge1xuICAgICAgYnlTZXZlcml0eVtmaW5kaW5nLnNldmVyaXR5XSsrO1xuICAgICAgXG4gICAgICAvLyBFeHRyYWN0IGNhdGVnb3J5IGZyb20gcnVsZUlkIChlLmcuLCBTRUMtQ09ERS0wMDEgLT4gQ09ERSlcbiAgICAgIGNvbnN0IGNhdGVnb3J5ID0gZmluZGluZy5ydWxlSWQuc3BsaXQoJy0nKVsxXSB8fCAnT1RIRVInO1xuICAgICAgYnlDYXRlZ29yeVtjYXRlZ29yeV0gPSAoYnlDYXRlZ29yeVtjYXRlZ29yeV0gfHwgMCkgKyAxO1xuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICB0aW1lc3RhbXA6IG5ldyBEYXRlKCksXG4gICAgICBkdXJhdGlvbixcbiAgICAgIHNjYW5uZWRGaWxlcyxcbiAgICAgIGZpbmRpbmdzLFxuICAgICAgc3VtbWFyeToge1xuICAgICAgICB0b3RhbDogZmluZGluZ3MubGVuZ3RoLFxuICAgICAgICBieVNldmVyaXR5LFxuICAgICAgICBieUNhdGVnb3J5XG4gICAgICB9LFxuICAgICAgZXJyb3JzOiBlcnJvcnMubGVuZ3RoID4gMCA/IGVycm9ycyA6IHVuZGVmaW5lZFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogR2VuZXJhdGUgcmVwb3J0cyBpbiBjb25maWd1cmVkIGZvcm1hdHNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZ2VuZXJhdGVSZXBvcnRzKHJlc3VsdDogU2NhblJlc3VsdCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGZvciAoY29uc3QgZm9ybWF0IG9mIHRoaXMuY29uZmlnLnJlcG9ydGluZy5mb3JtYXRzKSB7XG4gICAgICB0cnkge1xuICAgICAgICBzd2l0Y2ggKGZvcm1hdCkge1xuICAgICAgICAgIGNhc2UgJ2NvbnNvbGUnOlxuICAgICAgICAgICAgY29uc3QgY29uc29sZVJlcG9ydGVyID0gbmV3IENvbnNvbGVSZXBvcnRlcihyZXN1bHQpO1xuICAgICAgICAgICAgY29uc29sZS5sb2coY29uc29sZVJlcG9ydGVyLmdlbmVyYXRlKCkpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBcbiAgICAgICAgICBjYXNlICdtYXJrZG93bic6XG4gICAgICAgICAgICBjb25zdCBtYXJrZG93blJlcG9ydGVyID0gbmV3IE1hcmtkb3duUmVwb3J0ZXIocmVzdWx0KTtcbiAgICAgICAgICAgIGNvbnN0IG1kUmVwb3J0ID0gbWFya2Rvd25SZXBvcnRlci5nZW5lcmF0ZSgpIGFzIHN0cmluZztcbiAgICAgICAgICAgIGF3YWl0IGZzLndyaXRlRmlsZSgnc2VjdXJpdHktYXVkaXQtcmVwb3J0Lm1kJywgbWRSZXBvcnQpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBcbiAgICAgICAgICBjYXNlICdqc29uJzpcbiAgICAgICAgICAgIGNvbnN0IGpzb25SZXBvcnRlciA9IG5ldyBKc29uUmVwb3J0ZXIocmVzdWx0KTtcbiAgICAgICAgICAgIGNvbnN0IGpzb25SZXBvcnQgPSBKU09OLnN0cmluZ2lmeShqc29uUmVwb3J0ZXIuZ2VuZXJhdGUoKSwgbnVsbCwgMik7XG4gICAgICAgICAgICBhd2FpdCBmcy53cml0ZUZpbGUoJ3NlY3VyaXR5LWF1ZGl0LXJlcG9ydC5qc29uJywganNvblJlcG9ydCk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIFxuICAgICAgICAgIC8vIFNBUklGIGZvcm1hdCB3b3VsZCBiZSBpbXBsZW1lbnRlZCBzaW1pbGFybHlcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgU2VjdXJpdHlBdWRpdG9yOiBGYWlsZWQgdG8gZ2VuZXJhdGUgJHtmb3JtYXR9IHJlcG9ydDogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YCk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIERldGVybWluZSBpZiB0aGUgYnVpbGQgc2hvdWxkIGZhaWwgYmFzZWQgb24gZmluZGluZ3NcbiAgICovXG4gIHByaXZhdGUgc2hvdWxkRmFpbEJ1aWxkKHJlc3VsdDogU2NhblJlc3VsdCk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IHRocmVzaG9sZHM6IFJlY29yZDxTZXZlcml0eUxldmVsLCBudW1iZXI+ID0ge1xuICAgICAgaW5mbzogNSxcbiAgICAgIGxvdzogNCxcbiAgICAgIG1lZGl1bTogMyxcbiAgICAgIGhpZ2g6IDIsXG4gICAgICBjcml0aWNhbDogMVxuICAgIH07XG5cbiAgICBjb25zdCBmYWlsVGhyZXNob2xkID0gdGhyZXNob2xkc1t0aGlzLmNvbmZpZy5yZXBvcnRpbmcuZmFpbE9uU2V2ZXJpdHldO1xuICAgIFxuICAgIGZvciAoY29uc3QgW3NldmVyaXR5LCBjb3VudF0gb2YgT2JqZWN0LmVudHJpZXMocmVzdWx0LnN1bW1hcnkuYnlTZXZlcml0eSkpIHtcbiAgICAgIGlmIChjb3VudCA+IDAgJiYgdGhyZXNob2xkc1tzZXZlcml0eSBhcyBTZXZlcml0eUxldmVsXSA8PSBmYWlsVGhyZXNob2xkKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgZGVmYXVsdCBjb25maWd1cmF0aW9uXG4gICAqL1xuICBzdGF0aWMgZ2V0RGVmYXVsdENvbmZpZygpOiBTZWN1cml0eUF1ZGl0Q29uZmlnIHtcbiAgICByZXR1cm4ge1xuICAgICAgZW5hYmxlZDogdHJ1ZSxcbiAgICAgIHNjYW5uZXJzOiB7XG4gICAgICAgIGNvZGU6IHtcbiAgICAgICAgICBlbmFibGVkOiB0cnVlLFxuICAgICAgICAgIHJ1bGVzOiBbJ09XQVNQLVRvcC0xMCcsICdDV0UtVG9wLTI1JywgJ0RvbGxob3VzZU1DUC1TZWN1cml0eSddLFxuICAgICAgICAgIGV4Y2x1ZGU6IFsnbm9kZV9tb2R1bGVzLyoqJywgJ2Rpc3QvKionLCAnY292ZXJhZ2UvKionXVxuICAgICAgICB9LFxuICAgICAgICBkZXBlbmRlbmNpZXM6IHtcbiAgICAgICAgICBlbmFibGVkOiB0cnVlLFxuICAgICAgICAgIHNldmVyaXR5VGhyZXNob2xkOiAnaGlnaCcsXG4gICAgICAgICAgY2hlY2tMaWNlbnNlczogdHJ1ZSxcbiAgICAgICAgICBhbGxvd2VkTGljZW5zZXM6IFsnTUlUJywgJ0FwYWNoZS0yLjAnLCAnQlNELTMtQ2xhdXNlJywgJ0lTQycsICdBR1BMLTMuMCddXG4gICAgICAgIH0sXG4gICAgICAgIGNvbmZpZ3VyYXRpb246IHtcbiAgICAgICAgICBlbmFibGVkOiB0cnVlLFxuICAgICAgICAgIGNoZWNrRmlsZXM6IFsnKi55bWwnLCAnKi55YW1sJywgJyouanNvbicsICcuZW52LmV4YW1wbGUnXVxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgcmVwb3J0aW5nOiB7XG4gICAgICAgIGZvcm1hdHM6IFsnY29uc29sZScsICdtYXJrZG93biddLFxuICAgICAgICBjcmVhdGVJc3N1ZXM6IHRydWUsXG4gICAgICAgIGNvbW1lbnRPblByOiB0cnVlLFxuICAgICAgICBmYWlsT25TZXZlcml0eTogJ2hpZ2gnXG4gICAgICB9LFxuICAgICAgc3VwcHJlc3Npb25zOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBydWxlOiAnU0VDLVRFU1QtMDAxJyxcbiAgICAgICAgICBmaWxlOiAnX190ZXN0c19fLyoqLyonLFxuICAgICAgICAgIHJlYXNvbjogJ1Rlc3QgZmlsZXMgbWF5IGNvbnRhaW4gc2VjdXJpdHkgdGVzdCBwYXR0ZXJucydcbiAgICAgICAgfVxuICAgICAgXVxuICAgIH07XG4gIH1cbn0iXX0=