@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.
271 lines (266 loc) • 34.5 kB
JavaScript
/**
* Security Telemetry for DollhouseMCP
*
* Tracks and aggregates security metrics for blocked attacks,
* providing insights into threat patterns and system defense effectiveness.
*
* Issue #1269: Enhanced telemetry for memory injection protection
*
* REFACTOR NOTE:
* Converted from static class to instance-based for DI architecture compatibility.
* Security Telemetry is now a singleton service managed by the DI container.
*/
import { EvictingQueue } from '../../utils/EvictingQueue.js';
import { EventDeduplicator } from '../../utils/EventDeduplicator.js';
/** Deduplication window: suppress identical log listener calls within this period */
const DEDUP_WINDOW_MS = 60_000;
/** Maximum dedup cache entries before LRU eviction */
const DEDUP_MAX_SIZE = 500;
/**
* Security Telemetry Service
*
* DI-COMPATIBLE: Instance-based service for dependency injection.
* Tracks security events, attack patterns, and generates metrics.
*/
export class SecurityTelemetry {
attackHistory = new EvictingQueue(10000);
METRIC_WINDOW_HOURS = 24; // Track last 24 hours
attackVectorMap = new Map();
logListener;
logDedup = new EventDeduplicator(DEDUP_WINDOW_MS, DEDUP_MAX_SIZE);
addLogListener(fn) {
this.logListener = fn;
return () => { this.logListener = undefined; };
}
/**
* Create a new SecurityTelemetry instance
*/
constructor() {
// Instance initialization
}
/**
* Records a blocked attack attempt
* FIX (PR #1313 review): Use UTC timestamps for consistency across timezones
*/
recordBlockedAttack(attackType, pattern, severity, source, metadata) {
const entry = {
timestamp: new Date().toISOString(), // ISO string is always UTC
attackType,
pattern,
severity,
source,
blocked: true,
metadata
};
// Bounded FIFO eviction — EvictingQueue handles capacity
this.attackHistory.push(entry);
// Deduplicate log listener calls — same attackType+pattern within 60s = suppress
if (!this.logDedup.shouldSuppress(`${attackType}\0${pattern}`)) {
this.logListener?.(entry);
}
// Update attack vector map
const vectorKey = `${attackType}:${pattern}`;
const existing = this.attackVectorMap.get(vectorKey);
if (existing) {
existing.count++;
existing.lastSeen = entry.timestamp;
if (!existing.blockedPatterns.includes(pattern)) {
existing.blockedPatterns.push(pattern);
}
}
else {
this.attackVectorMap.set(vectorKey, {
type: attackType,
count: 1,
lastSeen: entry.timestamp,
severity,
blockedPatterns: [pattern]
});
}
}
/**
* Get aggregated security metrics
*/
getMetrics() {
const now = new Date();
const windowStart = new Date(now.getTime() - this.METRIC_WINDOW_HOURS * 60 * 60 * 1000);
// Filter to recent attacks
const recentAttacks = this.attackHistory.toArray().filter(attack => new Date(attack.timestamp) >= windowStart);
// Count by severity
let criticalCount = 0;
let highCount = 0;
let mediumCount = 0;
let lowCount = 0;
for (const attack of recentAttacks) {
switch (attack.severity) {
case 'CRITICAL':
criticalCount++;
break;
case 'HIGH':
highCount++;
break;
case 'MEDIUM':
mediumCount++;
break;
case 'LOW':
lowCount++;
break;
}
}
// Calculate attacks per hour
// FIX (PR #1313 review): Ensure consistent UTC timezone handling for metrics
const attacksPerHour = new Array(24).fill(0);
const nowUTC = Date.now(); // Unix timestamp in UTC
for (const attack of recentAttacks) {
// attack.timestamp is ISO string (UTC), parse to get UTC time
const attackTimeUTC = new Date(attack.timestamp).getTime();
const hoursAgo = Math.floor((nowUTC - attackTimeUTC) / (60 * 60 * 1000));
if (hoursAgo >= 0 && hoursAgo < 24) {
attacksPerHour[23 - hoursAgo]++;
}
}
// Get top attack vectors
const vectorArray = Array.from(this.attackVectorMap.values());
// FIX: Create copy before sorting to avoid mutation (SonarCloud S4043)
const topVectors = [...vectorArray]
.sort((a, b) => b.count - a.count)
.slice(0, 10);
return {
totalBlockedAttempts: recentAttacks.length,
uniqueAttackVectors: new Set(recentAttacks.map(a => a.attackType)).size,
criticalAttacksBlocked: criticalCount,
highSeverityBlocked: highCount,
mediumSeverityBlocked: mediumCount,
lowSeverityBlocked: lowCount,
topAttackVectors: topVectors,
attacksPerHour,
deduplication: this.getDeduplicationStats(),
lastUpdated: new Date().toISOString()
};
}
/**
* Returns deduplication statistics for observability.
* Tracks how many repeated log listener calls were suppressed
* vs. how many unique events passed through.
*/
getDeduplicationStats() {
const stats = this.logDedup.getStats();
return {
suppressedEvents: stats.suppressedCount,
uniqueEvents: stats.processedCount,
cacheSize: stats.cacheSize,
};
}
/**
* Get attack patterns by type
*/
getAttackPatternsByType(attackType) {
const patterns = new Set();
for (const attack of this.attackHistory.toArray()) {
if (attack.attackType === attackType) {
patterns.add(attack.pattern);
}
}
return Array.from(patterns);
}
/**
* Get attack timeline for visualization
*/
getAttackTimeline(hours = 24) {
const now = new Date();
const timeline = [];
for (let i = hours - 1; i >= 0; i--) {
const hourStart = new Date(now.getTime() - (i + 1) * 60 * 60 * 1000);
const hourEnd = new Date(now.getTime() - i * 60 * 60 * 1000);
const hourAttacks = this.attackHistory.toArray().filter(attack => {
const attackTime = new Date(attack.timestamp);
// For the most recent hour (i=0), include attacks up to and including "now"
return i === 0
? attackTime >= hourStart && attackTime <= now
: attackTime >= hourStart && attackTime < hourEnd;
});
const severityCounts = {
CRITICAL: 0,
HIGH: 0,
MEDIUM: 0,
LOW: 0
};
for (const attack of hourAttacks) {
severityCounts[attack.severity]++;
}
timeline.push({
hour: hourStart.toISOString().split('T')[1].split(':')[0] + ':00',
count: hourAttacks.length,
severity: severityCounts
});
}
return timeline;
}
/**
* Get summary report for security audits
*/
generateReport() {
const metrics = this.getMetrics();
const report = `
=== Security Telemetry Report ===
Generated: ${new Date().toISOString()}
Total Blocked Attacks (24h): ${metrics.totalBlockedAttempts}
Unique Attack Vectors: ${metrics.uniqueAttackVectors}
Severity Breakdown:
- Critical: ${metrics.criticalAttacksBlocked}
- High: ${metrics.highSeverityBlocked}
- Medium: ${metrics.mediumSeverityBlocked}
- Low: ${metrics.lowSeverityBlocked}
Deduplication:
- Suppressed (repeated): ${metrics.deduplication.suppressedEvents}
- Unique (passed through): ${metrics.deduplication.uniqueEvents}
- Cache entries: ${metrics.deduplication.cacheSize}
Top Attack Vectors:
${metrics.topAttackVectors.map((v, i) => `${i + 1}. ${v.type} (${v.count} attempts, severity: ${v.severity})`).join('\n')}
Hourly Distribution (last 24h):
${metrics.attacksPerHour.map((count, i) => `Hour ${23 - i}: ${count} attacks`).join(', ')}
`;
return report;
}
/**
* Clear old telemetry data
*/
clearOldData(daysToKeep = 30) {
if (daysToKeep === 0) {
// Clear all data immediately
this.attackHistory.clear();
this.attackVectorMap.clear();
return;
}
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const cutoffTimestamp = cutoffDate.toISOString();
const remaining = this.attackHistory.toArray().filter(attack => attack.timestamp >= cutoffTimestamp);
this.attackHistory.reset([...remaining]);
// Clean up old vectors that haven't been seen recently
for (const [key, vector] of this.attackVectorMap.entries()) {
if (new Date(vector.lastSeen) < cutoffDate) {
this.attackVectorMap.delete(key);
}
}
}
/**
* Export telemetry data for external analysis
*/
exportData() {
return {
history: this.attackHistory.toJSON(),
vectors: Array.from(this.attackVectorMap.values()),
metrics: this.getMetrics()
};
}
/**
* Dispose of the telemetry service and clean up resources
* Implements cleanup for proper DI lifecycle management
*/
async dispose() {
this.attackHistory.clear();
this.attackVectorMap.clear();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VjdXJpdHlUZWxlbWV0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VjdXJpdHkvdGVsZW1ldHJ5L1NlY3VyaXR5VGVsZW1ldHJ5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7OztHQVdHO0FBR0gsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzdELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBMENyRSxxRkFBcUY7QUFDckYsTUFBTSxlQUFlLEdBQUcsTUFBTSxDQUFDO0FBRS9CLHNEQUFzRDtBQUN0RCxNQUFNLGNBQWMsR0FBRyxHQUFHLENBQUM7QUFFM0I7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8saUJBQWlCO0lBQ3BCLGFBQWEsR0FBRyxJQUFJLGFBQWEsQ0FBdUIsS0FBSyxDQUFDLENBQUM7SUFDdEQsbUJBQW1CLEdBQUcsRUFBRSxDQUFDLENBQUMsc0JBQXNCO0lBQ2hELGVBQWUsR0FBOEIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNoRSxXQUFXLENBQXlDO0lBQzNDLFFBQVEsR0FBRyxJQUFJLGlCQUFpQixDQUFDLGVBQWUsRUFBRSxjQUFjLENBQUMsQ0FBQztJQUVuRixjQUFjLENBQUMsRUFBeUM7UUFDdEQsSUFBSSxDQUFDLFdBQVcsR0FBRyxFQUFFLENBQUM7UUFDdEIsT0FBTyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqRCxDQUFDO0lBRUQ7O09BRUc7SUFDSDtRQUNFLDBCQUEwQjtJQUM1QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsbUJBQW1CLENBQ2pCLFVBQWtCLEVBQ2xCLE9BQWUsRUFDZixRQUFtQyxFQUNuQyxNQUFjLEVBQ2QsUUFBOEI7UUFFOUIsTUFBTSxLQUFLLEdBQXlCO1lBQ2xDLFNBQVMsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLFdB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O0lBQ0osQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxPQUFPO1FBQ1gsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQy9CLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2VjdXJpdHkgVGVsZW1ldHJ5IGZvciBEb2xsaG91c2VNQ1BcbiAqXG4gKiBUcmFja3MgYW5kIGFnZ3JlZ2F0ZXMgc2VjdXJpdHkgbWV0cmljcyBmb3IgYmxvY2tlZCBhdHRhY2tzLFxuICogcHJvdmlkaW5nIGluc2lnaHRzIGludG8gdGhyZWF0IHBhdHRlcm5zIGFuZCBzeXN0ZW0gZGVmZW5zZSBlZmZlY3RpdmVuZXNzLlxuICpcbiAqIElzc3VlICMxMjY5OiBFbmhhbmNlZCB0ZWxlbWV0cnkgZm9yIG1lbW9yeSBpbmplY3Rpb24gcHJvdGVjdGlvblxuICpcbiAqIFJFRkFDVE9SIE5PVEU6XG4gKiBDb252ZXJ0ZWQgZnJvbSBzdGF0aWMgY2xhc3MgdG8gaW5zdGFuY2UtYmFzZWQgZm9yIERJIGFyY2hpdGVjdHVyZSBjb21wYXRpYmlsaXR5LlxuICogU2VjdXJpdHkgVGVsZW1ldHJ5IGlzIG5vdyBhIHNpbmdsZXRvbiBzZXJ2aWNlIG1hbmFnZWQgYnkgdGhlIERJIGNvbnRhaW5lci5cbiAqL1xuXG5pbXBvcnQgeyBTZWN1cml0eUV2ZW50IH0gZnJvbSAnLi4vc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IEV2aWN0aW5nUXVldWUgfSBmcm9tICcuLi8uLi91dGlscy9FdmljdGluZ1F1ZXVlLmpzJztcbmltcG9ydCB7IEV2ZW50RGVkdXBsaWNhdG9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvRXZlbnREZWR1cGxpY2F0b3IuanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIEF0dGFja1ZlY3RvciB7XG4gIHR5cGU6IHN0cmluZztcbiAgY291bnQ6IG51bWJlcjtcbiAgbGFzdFNlZW46IHN0cmluZztcbiAgc2V2ZXJpdHk6ICdMT1cnIHwgJ01FRElVTScgfCAnSElHSCcgfCAnQ1JJVElDQUwnO1xuICBibG9ja2VkUGF0dGVybnM6IHN0cmluZ1tdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIERlZHVwbGljYXRpb25TdGF0cyB7XG4gIC8qKiBOdW1iZXIgb2YgcmVwZWF0ZWQgZXZlbnRzIHRoYXQgd2VyZSBzdXBwcmVzc2VkICovXG4gIHN1cHByZXNzZWRFdmVudHM6IG51bWJlcjtcbiAgLyoqIE51bWJlciBvZiB1bmlxdWUgZXZlbnRzIHRoYXQgcGFzc2VkIHRocm91Z2ggKi9cbiAgdW5pcXVlRXZlbnRzOiBudW1iZXI7XG4gIC8qKiBDdXJyZW50IG51bWJlciBvZiBrZXlzIGluIHRoZSBkZWR1cCBjYWNoZSAqL1xuICBjYWNoZVNpemU6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBTZWN1cml0eU1ldHJpY3Mge1xuICB0b3RhbEJsb2NrZWRBdHRlbXB0czogbnVtYmVyO1xuICB1bmlxdWVBdHRhY2tWZWN0b3JzOiBudW1iZXI7XG4gIGNyaXRpY2FsQXR0YWNrc0Jsb2NrZWQ6IG51bWJlcjtcbiAgaGlnaFNldmVyaXR5QmxvY2tlZDogbnVtYmVyO1xuICBtZWRpdW1TZXZlcml0eUJsb2NrZWQ6IG51bWJlcjtcbiAgbG93U2V2ZXJpdHlCbG9ja2VkOiBudW1iZXI7XG4gIHRvcEF0dGFja1ZlY3RvcnM6IEF0dGFja1ZlY3RvcltdO1xuICBhdHRhY2tzUGVySG91cjogbnVtYmVyW107XG4gIGRlZHVwbGljYXRpb246IERlZHVwbGljYXRpb25TdGF0cztcbiAgbGFzdFVwZGF0ZWQ6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBBdHRhY2tUZWxlbWV0cnlFbnRyeSB7XG4gIHRpbWVzdGFtcDogc3RyaW5nO1xuICBhdHRhY2tUeXBlOiBzdHJpbmc7XG4gIHBhdHRlcm46IHN0cmluZztcbiAgc2V2ZXJpdHk6ICdMT1cnIHwgJ01FRElVTScgfCAnSElHSCcgfCAnQ1JJVElDQUwnO1xuICBzb3VyY2U6IHN0cmluZztcbiAgYmxvY2tlZDogYm9vbGVhbjtcbiAgbWV0YWRhdGE/OiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xufVxuXG4vKiogRGVkdXBsaWNhdGlvbiB3aW5kb3c6IHN1cHByZXNzIGlkZW50aWNhbCBsb2cgbGlzdGVuZXIgY2FsbHMgd2l0aGluIHRoaXMgcGVyaW9kICovXG5jb25zdCBERURVUF9XSU5ET1dfTVMgPSA2MF8wMDA7XG5cbi8qKiBNYXhpbXVtIGRlZHVwIGNhY2hlIGVudHJpZXMgYmVmb3JlIExSVSBldmljdGlvbiAqL1xuY29uc3QgREVEVVBfTUFYX1NJWkUgPSA1MDA7XG5cbi8qKlxuICogU2VjdXJpdHkgVGVsZW1ldHJ5IFNlcnZpY2VcbiAqXG4gKiBESS1DT01QQVRJQkxFOiBJbnN0YW5jZS1iYXNlZCBzZXJ2aWNlIGZvciBkZXBlbmRlbmN5IGluamVjdGlvbi5cbiAqIFRyYWNrcyBzZWN1cml0eSBldmVudHMsIGF0dGFjayBwYXR0ZXJucywgYW5kIGdlbmVyYXRlcyBtZXRyaWNzLlxuICovXG5leHBvcnQgY2xhc3MgU2VjdXJpdHlUZWxlbWV0cnkge1xuICBwcml2YXRlIGF0dGFja0hpc3RvcnkgPSBuZXcgRXZpY3RpbmdRdWV1ZTxBdHRhY2tUZWxlbWV0cnlFbnRyeT4oMTAwMDApO1xuICBwcml2YXRlIHJlYWRvbmx5IE1FVFJJQ19XSU5ET1dfSE9VUlMgPSAyNDsgLy8gVHJhY2sgbGFzdCAyNCBob3Vyc1xuICBwcml2YXRlIHJlYWRvbmx5IGF0dGFja1ZlY3Rvck1hcDogTWFwPHN0cmluZywgQXR0YWNrVmVjdG9yPiA9IG5ldyBNYXAoKTtcbiAgcHJpdmF0ZSBsb2dMaXN0ZW5lcj86IChlbnRyeTogQXR0YWNrVGVsZW1ldHJ5RW50cnkpID0+IHZvaWQ7XG4gIHByaXZhdGUgcmVhZG9ubHkgbG9nRGVkdXAgPSBuZXcgRXZlbnREZWR1cGxpY2F0b3IoREVEVVBfV0lORE9XX01TLCBERURVUF9NQVhfU0laRSk7XG5cbiAgYWRkTG9nTGlzdGVuZXIoZm46IChlbnRyeTogQXR0YWNrVGVsZW1ldHJ5RW50cnkpID0+IHZvaWQpOiAoKSA9PiB2b2lkIHtcbiAgICB0aGlzLmxvZ0xpc3RlbmVyID0gZm47XG4gICAgcmV0dXJuICgpID0+IHsgdGhpcy5sb2dMaXN0ZW5lciA9IHVuZGVmaW5lZDsgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBuZXcgU2VjdXJpdHlUZWxlbWV0cnkgaW5zdGFuY2VcbiAgICovXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIC8vIEluc3RhbmNlIGluaXRpYWxpemF0aW9uXG4gIH1cblxuICAvKipcbiAgICogUmVjb3JkcyBhIGJsb2NrZWQgYXR0YWNrIGF0dGVtcHRcbiAgICogRklYIChQUiAjMTMxMyByZXZpZXcpOiBVc2UgVVRDIHRpbWVzdGFtcHMgZm9yIGNvbnNpc3RlbmN5IGFjcm9zcyB0aW1lem9uZXNcbiAgICovXG4gIHJlY29yZEJsb2NrZWRBdHRhY2soXG4gICAgYXR0YWNrVHlwZTogc3RyaW5nLFxuICAgIHBhdHRlcm46IHN0cmluZyxcbiAgICBzZXZlcml0eTogU2VjdXJpdHlFdmVudFsnc2V2ZXJpdHknXSxcbiAgICBzb3VyY2U6IHN0cmluZyxcbiAgICBtZXRhZGF0YT86IFJlY29yZDxzdHJpbmcsIGFueT5cbiAgKTogdm9pZCB7XG4gICAgY29uc3QgZW50cnk6IEF0dGFja1RlbGVtZXRyeUVudHJ5ID0ge1xuICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksIC8vIElTTyBzdHJpbmcgaXMgYWx3YXlzIFVUQ1xuICAgICAgYXR0YWNrVHlwZSxcbiAgICAgIHBhdHRlcm4sXG4gICAgICBzZXZlcml0eSxcbiAgICAgIHNvdXJjZSxcbiAgICAgIGJsb2NrZWQ6IHRydWUsXG4gICAgICBtZXRhZGF0YVxuICAgIH07XG5cbiAgICAvLyBCb3VuZGVkIEZJRk8gZXZpY3Rpb24g4oCUIEV2aWN0aW5nUXVldWUgaGFuZGxlcyBjYXBhY2l0eVxuICAgIHRoaXMuYXR0YWNrSGlzdG9yeS5wdXNoKGVudHJ5KTtcblxuICAgIC8vIERlZHVwbGljYXRlIGxvZyBsaXN0ZW5lciBjYWxscyDigJQgc2FtZSBhdHRhY2tUeXBlK3BhdHRlcm4gd2l0aGluIDYwcyA9IHN1cHByZXNzXG4gICAgaWYgKCF0aGlzLmxvZ0RlZHVwLnNob3VsZFN1cHByZXNzKGAke2F0dGFja1R5cGV9XFwwJHtwYXR0ZXJufWApKSB7XG4gICAgICB0aGlzLmxvZ0xpc3RlbmVyPy4oZW50cnkpO1xuICAgIH1cblxuICAgIC8vIFVwZGF0ZSBhdHRhY2sgdmVjdG9yIG1hcFxuICAgIGNvbnN0IHZlY3RvcktleSA9IGAke2F0dGFja1R5cGV9OiR7cGF0dGVybn1gO1xuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5hdHRhY2tWZWN0b3JNYXAuZ2V0KHZlY3RvcktleSk7XG5cbiAgICBpZiAoZXhpc3RpbmcpIHtcbiAgICAgIGV4aXN0aW5nLmNvdW50Kys7XG4gICAgICBleGlzdGluZy5sYXN0U2VlbiA9IGVudHJ5LnRpbWVzdGFtcDtcbiAgICAgIGlmICghZXhpc3RpbmcuYmxvY2tlZFBhdHRlcm5zLmluY2x1ZGVzKHBhdHRlcm4pKSB7XG4gICAgICAgIGV4aXN0aW5nLmJsb2NrZWRQYXR0ZXJucy5wdXNoKHBhdHRlcm4pO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmF0dGFja1ZlY3Rvck1hcC5zZXQodmVjdG9yS2V5LCB7XG4gICAgICAgIHR5cGU6IGF0dGFja1R5cGUsXG4gICAgICAgIGNvdW50OiAxLFxuICAgICAgICBsYXN0U2VlbjogZW50cnkudGltZXN0YW1wLFxuICAgICAgICBzZXZlcml0eSxcbiAgICAgICAgYmxvY2tlZFBhdHRlcm5zOiBbcGF0dGVybl1cbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgYWdncmVnYXRlZCBzZWN1cml0eSBtZXRyaWNzXG4gICAqL1xuICBnZXRNZXRyaWNzKCk6IFNlY3VyaXR5TWV0cmljcyB7XG4gICAgY29uc3Qgbm93ID0gbmV3IERhdGUoKTtcbiAgICBjb25zdCB3aW5kb3dTdGFydCA9IG5ldyBEYXRlKG5vdy5nZXRUaW1lKCkgLSB0aGlzLk1FVFJJQ19XSU5ET1dfSE9VUlMgKiA2MCAqIDYwICogMTAwMCk7XG5cbiAgICAvLyBGaWx0ZXIgdG8gcmVjZW50IGF0dGFja3NcbiAgICBjb25zdCByZWNlbnRBdHRhY2tzID0gdGhpcy5hdHRhY2tIaXN0b3J5LnRvQXJyYXkoKS5maWx0ZXIoXG4gICAgICBhdHRhY2sgPT4gbmV3IERhdGUoYXR0YWNrLnRpbWVzdGFtcCkgPj0gd2luZG93U3RhcnRcbiAgICApO1xuXG4gICAgLy8gQ291bnQgYnkgc2V2ZXJpdHlcbiAgICBsZXQgY3JpdGljYWxDb3VudCA9IDA7XG4gICAgbGV0IGhpZ2hDb3VudCA9IDA7XG4gICAgbGV0IG1lZGl1bUNvdW50ID0gMDtcbiAgICBsZXQgbG93Q291bnQgPSAwO1xuXG4gICAgZm9yIChjb25zdCBhdHRhY2sgb2YgcmVjZW50QXR0YWNrcykge1xuICAgICAgc3dpdGNoIChhdHRhY2suc2V2ZXJpdHkpIHtcbiAgICAgICAgY2FzZSAnQ1JJVElDQUwnOlxuICAgICAgICAgIGNyaXRpY2FsQ291bnQrKztcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnSElHSCc6XG4gICAgICAgICAgaGlnaENvdW50Kys7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ01FRElVTSc6XG4gICAgICAgICAgbWVkaXVtQ291bnQrKztcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnTE9XJzpcbiAgICAgICAgICBsb3dDb3VudCsrO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIENhbGN1bGF0ZSBhdHRhY2tzIHBlciBob3VyXG4gICAgLy8gRklYIChQUiAjMTMxMyByZXZpZXcpOiBFbnN1cmUgY29uc2lzdGVudCBVVEMgdGltZXpvbmUgaGFuZGxpbmcgZm9yIG1ldHJpY3NcbiAgICBjb25zdCBhdHRhY2tzUGVySG91cjogbnVtYmVyW10gPSBuZXcgQXJyYXkoMjQpLmZpbGwoMCk7XG4gICAgY29uc3Qgbm93VVRDID0gRGF0ZS5ub3coKTsgLy8gVW5peCB0aW1lc3RhbXAgaW4gVVRDXG4gICAgZm9yIChjb25zdCBhdHRhY2sgb2YgcmVjZW50QXR0YWNrcykge1xuICAgICAgLy8gYXR0YWNrLnRpbWVzdGFtcCBpcyBJU08gc3RyaW5nIChVVEMpLCBwYXJzZSB0byBnZXQgVVRDIHRpbWVcbiAgICAgIGNvbnN0IGF0dGFja1RpbWVVVEMgPSBuZXcgRGF0ZShhdHRhY2sudGltZXN0YW1wKS5nZXRUaW1lKCk7XG4gICAgICBjb25zdCBob3Vyc0FnbyA9IE1hdGguZmxvb3IoKG5vd1VUQyAtIGF0dGFja1RpbWVVVEMpIC8gKDYwICogNjAgKiAxMDAwKSk7XG4gICAgICBpZiAoaG91cnNBZ28gPj0gMCAmJiBob3Vyc0FnbyA8IDI0KSB7XG4gICAgICAgIGF0dGFja3NQZXJIb3VyWzIzIC0gaG91cnNBZ29dKys7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gR2V0IHRvcCBhdHRhY2sgdmVjdG9yc1xuICAgIGNvbnN0IHZlY3RvckFycmF5ID0gQXJyYXkuZnJvbSh0aGlzLmF0dGFja1ZlY3Rvck1hcC52YWx1ZXMoKSk7XG4gICAgLy8gRklYOiBDcmVhdGUgY29weSBiZWZvcmUgc29ydGluZyB0byBhdm9pZCBtdXRhdGlvbiAoU29uYXJDbG91ZCBTNDA0MylcbiAgICBjb25zdCB0b3BWZWN0b3JzID0gWy4uLnZlY3RvckFycmF5XVxuICAgICAgLnNvcnQoKGEsIGIpID0+IGIuY291bnQgLSBhLmNvdW50KVxuICAgICAgLnNsaWNlKDAsIDEwKTtcblxuICAgIHJldHVybiB7XG4gICAgICB0b3RhbEJsb2NrZWRBdHRlbXB0czogcmVjZW50QXR0YWNrcy5sZW5ndGgsXG4gICAgICB1bmlxdWVBdHRhY2tWZWN0b3JzOiBuZXcgU2V0KHJlY2VudEF0dGFja3MubWFwKGEgPT4gYS5hdHRhY2tUeXBlKSkuc2l6ZSxcbiAgICAgIGNyaXRpY2FsQXR0YWNrc0Jsb2NrZWQ6IGNyaXRpY2FsQ291bnQsXG4gICAgICBoaWdoU2V2ZXJpdHlCbG9ja2VkOiBoaWdoQ291bnQsXG4gICAgICBtZWRpdW1TZXZlcml0eUJsb2NrZWQ6IG1lZGl1bUNvdW50LFxuICAgICAgbG93U2V2ZXJpdHlCbG9ja2VkOiBsb3dDb3VudCxcbiAgICAgIHRvcEF0dGFja1ZlY3RvcnM6IHRvcFZlY3RvcnMsXG4gICAgICBhdHRhY2tzUGVySG91cixcbiAgICAgIGRlZHVwbGljYXRpb246IHRoaXMuZ2V0RGVkdXBsaWNhdGlvblN0YXRzKCksXG4gICAgICBsYXN0VXBkYXRlZDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGRlZHVwbGljYXRpb24gc3RhdGlzdGljcyBmb3Igb2JzZXJ2YWJpbGl0eS5cbiAgICogVHJhY2tzIGhvdyBtYW55IHJlcGVhdGVkIGxvZyBsaXN0ZW5lciBjYWxscyB3ZXJlIHN1cHByZXNzZWRcbiAgICogdnMuIGhvdyBtYW55IHVuaXF1ZSBldmVudHMgcGFzc2VkIHRocm91Z2guXG4gICAqL1xuICBnZXREZWR1cGxpY2F0aW9uU3RhdHMoKTogRGVkdXBsaWNhdGlvblN0YXRzIHtcbiAgICBjb25zdCBzdGF0cyA9IHRoaXMubG9nRGVkdXAuZ2V0U3RhdHMoKTtcbiAgICByZXR1cm4ge1xuICAgICAgc3VwcHJlc3NlZEV2ZW50czogc3RhdHMuc3VwcHJlc3NlZENvdW50LFxuICAgICAgdW5pcXVlRXZlbnRzOiBzdGF0cy5wcm9jZXNzZWRDb3VudCxcbiAgICAgIGNhY2hlU2l6ZTogc3RhdHMuY2FjaGVTaXplLFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogR2V0IGF0dGFjayBwYXR0ZXJucyBieSB0eXBlXG4gICAqL1xuICBnZXRBdHRhY2tQYXR0ZXJuc0J5VHlwZShhdHRhY2tUeXBlOiBzdHJpbmcpOiBzdHJpbmdbXSB7XG4gICAgY29uc3QgcGF0dGVybnMgPSBuZXcgU2V0PHN0cmluZz4oKTtcblxuICAgIGZvciAoY29uc3QgYXR0YWNrIG9mIHRoaXMuYXR0YWNrSGlzdG9yeS50b0FycmF5KCkpIHtcbiAgICAgIGlmIChhdHRhY2suYXR0YWNrVHlwZSA9PT0gYXR0YWNrVHlwZSkge1xuICAgICAgICBwYXR0ZXJucy5hZGQoYXR0YWNrLnBhdHRlcm4pO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBBcnJheS5mcm9tKHBhdHRlcm5zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgYXR0YWNrIHRpbWVsaW5lIGZvciB2aXN1YWxpemF0aW9uXG4gICAqL1xuICBnZXRBdHRhY2tUaW1lbGluZShob3VyczogbnVtYmVyID0gMjQpOiB7IGhvdXI6IHN0cmluZzsgY291bnQ6IG51bWJlcjsgc2V2ZXJpdHk6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gfVtdIHtcbiAgICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpO1xuICAgIGNvbnN0IHRpbWVsaW5lOiB7IGhvdXI6IHN0cmluZzsgY291bnQ6IG51bWJlcjsgc2V2ZXJpdHk6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gfVtdID0gW107XG5cbiAgICBmb3IgKGxldCBpID0gaG91cnMgLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgY29uc3QgaG91clN0YXJ0ID0gbmV3IERhdGUobm93LmdldFRpbWUoKSAtIChpICsgMSkgKiA2MCAqIDYwICogMTAwMCk7XG4gICAgICBjb25zdCBob3VyRW5kID0gbmV3IERhdGUobm93LmdldFRpbWUoKSAtIGkgKiA2MCAqIDYwICogMTAwMCk7XG5cbiAgICAgIGNvbnN0IGhvdXJBdHRhY2tzID0gdGhpcy5hdHRhY2tIaXN0b3J5LnRvQXJyYXkoKS5maWx0ZXIoYXR0YWNrID0+IHtcbiAgICAgICAgY29uc3QgYXR0YWNrVGltZSA9IG5ldyBEYXRlKGF0dGFjay50aW1lc3RhbXApO1xuICAgICAgICAvLyBGb3IgdGhlIG1vc3QgcmVjZW50IGhvdXIgKGk9MCksIGluY2x1ZGUgYXR0YWNrcyB1cCB0byBhbmQgaW5jbHVkaW5nIFwibm93XCJcbiAgICAgICAgcmV0dXJuIGkgPT09IDBcbiAgICAgICAgICA/IGF0dGFja1RpbWUgPj0gaG91clN0YXJ0ICYmIGF0dGFja1RpbWUgPD0gbm93XG4gICAgICAgICAgOiBhdHRhY2tUaW1lID49IGhvdXJTdGFydCAmJiBhdHRhY2tUaW1lIDwgaG91ckVuZDtcbiAgICAgIH0pO1xuXG4gICAgICBjb25zdCBzZXZlcml0eUNvdW50czogUmVjb3JkPHN0cmluZywgbnVtYmVyPiA9IHtcbiAgICAgICAgQ1JJVElDQUw6IDAsXG4gICAgICAgIEhJR0g6IDAsXG4gICAgICAgIE1FRElVTTogMCxcbiAgICAgICAgTE9XOiAwXG4gICAgICB9O1xuXG4gICAgICBmb3IgKGNvbnN0IGF0dGFjayBvZiBob3VyQXR0YWNrcykge1xuICAgICAgICBzZXZlcml0eUNvdW50c1thdHRhY2suc2V2ZXJpdHldKys7XG4gICAgICB9XG5cbiAgICAgIHRpbWVsaW5lLnB1c2goe1xuICAgICAgICBob3VyOiBob3VyU3RhcnQudG9JU09TdHJpbmcoKS5zcGxpdCgnVCcpWzFdLnNwbGl0KCc6JylbMF0gKyAnOjAwJyxcbiAgICAgICAgY291bnQ6IGhvdXJBdHRhY2tzLmxlbmd0aCxcbiAgICAgICAgc2V2ZXJpdHk6IHNldmVyaXR5Q291bnRzXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGltZWxpbmU7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHN1bW1hcnkgcmVwb3J0IGZvciBzZWN1cml0eSBhdWRpdHNcbiAgICovXG4gIGdlbmVyYXRlUmVwb3J0KCk6IHN0cmluZyB7XG4gICAgY29uc3QgbWV0cmljcyA9IHRoaXMuZ2V0TWV0cmljcygpO1xuXG4gICAgY29uc3QgcmVwb3J0ID0gYFxuPT09IFNlY3VyaXR5IFRlbGVtZXRyeSBSZXBvcnQgPT09XG5HZW5lcmF0ZWQ6ICR7bmV3IERhdGUoKS50b0lTT1N0cmluZygpfVxuXG5Ub3RhbCBCbG9ja2VkIEF0dGFja3MgKDI0aCk6ICR7bWV0cmljcy50b3RhbEJsb2NrZWRBdHRlbXB0c31cblVuaXF1ZSBBdHRhY2sgVmVjdG9yczogJHttZXRyaWNzLnVuaXF1ZUF0dGFja1ZlY3RvcnN9XG5cblNldmVyaXR5IEJyZWFrZG93bjpcbi0gQ3JpdGljYWw6ICR7bWV0cmljcy5jcml0aWNhbEF0dGFja3NCbG9ja2VkfVxuLSBIaWdoOiAke21ldHJpY3MuaGlnaFNldmVyaXR5QmxvY2tlZH1cbi0gTWVkaXVtOiAke21ldHJpY3MubWVkaXVtU2V2ZXJpdHlCbG9ja2VkfVxuLSBMb3c6ICR7bWV0cmljcy5sb3dTZXZlcml0eUJsb2NrZWR9XG5cbkRlZHVwbGljYXRpb246XG4tIFN1cHByZXNzZWQgKHJlcGVhdGVkKTogJHttZXRyaWNzLmRlZHVwbGljYXRpb24uc3VwcHJlc3NlZEV2ZW50c31cbi0gVW5pcXVlIChwYXNzZWQgdGhyb3VnaCk6ICR7bWV0cmljcy5kZWR1cGxpY2F0aW9uLnVuaXF1ZUV2ZW50c31cbi0gQ2FjaGUgZW50cmllczogJHttZXRyaWNzLmRlZHVwbGljYXRpb24uY2FjaGVTaXplfVxuXG5Ub3AgQXR0YWNrIFZlY3RvcnM6XG4ke21ldHJpY3MudG9wQXR0YWNrVmVjdG9ycy5tYXAoKHYsIGkpID0+XG4gIGAke2kgKyAxfS4gJHt2LnR5cGV9ICgke3YuY291bnR9IGF0dGVtcHRzLCBzZXZlcml0eTogJHt2LnNldmVyaXR5fSlgXG4pLmpvaW4oJ1xcbicpfVxuXG5Ib3VybHkgRGlzdHJpYnV0aW9uIChsYXN0IDI0aCk6XG4ke21ldHJpY3MuYXR0YWNrc1BlckhvdXIubWFwKChjb3VudCwgaSkgPT5cbiAgYEhvdXIgJHsyMyAtIGl9OiAke2NvdW50fSBhdHRhY2tzYFxuKS5qb2luKCcsICcpfVxuYDtcblxuICAgIHJldHVybiByZXBvcnQ7XG4gIH1cblxuICAvKipcbiAgICogQ2xlYXIgb2xkIHRlbGVtZXRyeSBkYXRhXG4gICAqL1xuICBjbGVhck9sZERhdGEoZGF5c1RvS2VlcDogbnVtYmVyID0gMzApOiB2b2lkIHtcbiAgICBpZiAoZGF5c1RvS2VlcCA9PT0gMCkge1xuICAgICAgLy8gQ2xlYXIgYWxsIGRhdGEgaW1tZWRpYXRlbHlcbiAgICAgIHRoaXMuYXR0YWNrSGlzdG9yeS5jbGVhcigpO1xuICAgICAgdGhpcy5hdHRhY2tWZWN0b3JNYXAuY2xlYXIoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCBjdXRvZmZEYXRlID0gbmV3IERhdGUoKTtcbiAgICBjdXRvZmZEYXRlLnNldERhdGUoY3V0b2ZmRGF0ZS5nZXREYXRlKCkgLSBkYXlzVG9LZWVwKTtcbiAgICBjb25zdCBjdXRvZmZUaW1lc3RhbXAgPSBjdXRvZmZEYXRlLnRvSVNPU3RyaW5nKCk7XG5cbiAgICBjb25zdCByZW1haW5pbmcgPSB0aGlzLmF0dGFja0hpc3RvcnkudG9BcnJheSgpLmZpbHRlcihcbiAgICAgIGF0dGFjayA9PiBhdHRhY2sudGltZXN0YW1wID49IGN1dG9mZlRpbWVzdGFtcFxuICAgICk7XG4gICAgdGhpcy5hdHRhY2tIaXN0b3J5LnJlc2V0KFsuLi5yZW1haW5pbmddKTtcblxuICAgIC8vIENsZWFuIHVwIG9sZCB2ZWN0b3JzIHRoYXQgaGF2ZW4ndCBiZWVuIHNlZW4gcmVjZW50bHlcbiAgICBmb3IgKGNvbnN0IFtrZXksIHZlY3Rvcl0gb2YgdGhpcy5hdHRhY2tWZWN0b3JNYXAuZW50cmllcygpKSB7XG4gICAgICBpZiAobmV3IERhdGUodmVjdG9yLmxhc3RTZWVuKSA8IGN1dG9mZkRhdGUpIHtcbiAgICAgICAgdGhpcy5hdHRhY2tWZWN0b3JNYXAuZGVsZXRlKGtleSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4cG9ydCB0ZWxlbWV0cnkgZGF0YSBmb3IgZXh0ZXJuYWwgYW5hbHlzaXNcbiAgICovXG4gIGV4cG9ydERhdGEoKToge1xuICAgIGhpc3Rvcnk6IEF0dGFja1RlbGVtZXRyeUVudHJ5W107XG4gICAgdmVjdG9yczogQXR0YWNrVmVjdG9yW107XG4gICAgbWV0cmljczogU2VjdXJpdHlNZXRyaWNzO1xuICB9IHtcbiAgICByZXR1cm4ge1xuICAgICAgaGlzdG9yeTogdGhpcy5hdHRhY2tIaXN0b3J5LnRvSlNPTigpLFxuICAgICAgdmVjdG9yczogQXJyYXkuZnJvbSh0aGlzLmF0dGFja1ZlY3Rvck1hcC52YWx1ZXMoKSksXG4gICAgICBtZXRyaWNzOiB0aGlzLmdldE1ldHJpY3MoKVxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogRGlzcG9zZSBvZiB0aGUgdGVsZW1ldHJ5IHNlcnZpY2UgYW5kIGNsZWFuIHVwIHJlc291cmNlc1xuICAgKiBJbXBsZW1lbnRzIGNsZWFudXAgZm9yIHByb3BlciBESSBsaWZlY3ljbGUgbWFuYWdlbWVudFxuICAgKi9cbiAgYXN5bmMgZGlzcG9zZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0aGlzLmF0dGFja0hpc3RvcnkuY2xlYXIoKTtcbiAgICB0aGlzLmF0dGFja1ZlY3Rvck1hcC5jbGVhcigpO1xuICB9XG59XG4iXX0=