@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.
673 lines • 98.5 kB
JavaScript
/**
* Template element class implementing IElement interface.
* Represents reusable content structures with variable substitution and dynamic content.
*
* SECURITY FIXES IMPLEMENTED (Following PR #319 patterns):
* 1. CRITICAL: Template injection prevention - no eval() or Function() constructor
* 2. CRITICAL: Path traversal prevention for template includes
* 3. HIGH: Input validation and sanitization for all template variables
* 4. MEDIUM: Memory limits to prevent resource exhaustion (100KB templates, 100 variables)
* 5. MEDIUM: Audit logging for all security operations via SecurityMonitor
* 6. MEDIUM: Unicode normalization to prevent homograph attacks
*/
import { BaseElement } from '../BaseElement.js';
import { ElementType } from '../../portfolio/types.js';
import { logger } from '../../utils/logger.js';
import { sanitizeInput } from '../../security/InputValidator.js';
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
import { SecurityMonitor } from '../../security/securityMonitor.js';
import * as path from 'path';
export class Template extends BaseElement {
content;
compiledTemplate;
// SECURITY FIX #4: Memory management constants
// Prevents unbounded template size and variable count that could exhaust memory
MAX_TEMPLATE_SIZE = 100 * 1024; // 100KB max template size
MAX_VARIABLE_COUNT = 100; // Max variables per template
MAX_INCLUDE_DEPTH = 5; // Prevent infinite include loops
MAX_STRING_LENGTH = 10000; // Max length for string variables
constructor(metadata, content = '') {
// SECURITY FIX #3 & #6: Validate and sanitize ALL metadata fields
// Unicode normalization prevents homograph attacks
// Input sanitization prevents XSS and injection attacks
const sanitizedMetadata = {
...metadata,
name: metadata.name ? sanitizeInput(UnicodeValidator.normalize(metadata.name).normalizedContent, 100) : undefined,
description: metadata.description ? sanitizeInput(UnicodeValidator.normalize(metadata.description).normalizedContent, 500) : undefined,
category: metadata.category ? sanitizeInput(UnicodeValidator.normalize(metadata.category).normalizedContent, 50) : undefined,
output_format: metadata.output_format ? sanitizeInput(metadata.output_format, 20) : undefined
};
super(ElementType.TEMPLATE, sanitizedMetadata);
// SECURITY FIX #4: Enforce template size limit
if (content.length > this.MAX_TEMPLATE_SIZE) {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_SIZE_EXCEEDED',
severity: 'HIGH',
source: 'Template.constructor',
details: `Template size ${content.length} exceeds maximum ${this.MAX_TEMPLATE_SIZE}`
});
throw new Error(`Template content exceeds maximum size of ${this.MAX_TEMPLATE_SIZE} bytes`);
}
// SECURITY FIX #3: Sanitize template content
// Note: We preserve the template syntax but normalize Unicode
this.content = UnicodeValidator.normalize(content).normalizedContent;
// Ensure template-specific metadata
this.metadata = {
...this.metadata,
category: sanitizedMetadata.category || 'general',
output_format: sanitizedMetadata.output_format || 'markdown',
variables: metadata.variables || [],
includes: metadata.includes || [],
tags: metadata.tags || [],
usage_count: metadata.usage_count || 0,
examples: metadata.examples || []
};
// SECURITY FIX #3 & #4: Validate variables
if (this.metadata.variables) {
if (this.metadata.variables.length > this.MAX_VARIABLE_COUNT) {
throw new Error(`Variable count ${this.metadata.variables.length} exceeds maximum ${this.MAX_VARIABLE_COUNT}`);
}
this.metadata.variables = this.metadata.variables.map(variable => ({
...variable,
name: sanitizeInput(UnicodeValidator.normalize(variable.name).normalizedContent, 50),
description: variable.description ? sanitizeInput(UnicodeValidator.normalize(variable.description).normalizedContent, 200) : undefined,
validation: variable.validation ? sanitizeInput(variable.validation, 200) : undefined
}));
}
// SECURITY FIX #2: Validate include paths
if (this.metadata.includes) {
this.metadata.includes = this.metadata.includes.map(includePath => {
const sanitizedPath = sanitizeInput(includePath, 200);
// Prevent path traversal attacks
if (!this.isValidIncludePath(sanitizedPath)) {
SecurityMonitor.logSecurityEvent({
type: 'PATH_TRAVERSAL_ATTEMPT',
severity: 'CRITICAL',
source: 'Template.constructor',
details: `Invalid include path: ${sanitizedPath}`
});
throw new Error(`Invalid include path: ${sanitizedPath}`);
}
return sanitizedPath;
});
}
}
/**
* Validate include paths to prevent directory traversal
* SECURITY FIX #2: Prevents accessing files outside template directory
*/
isValidIncludePath(includePath) {
// Normalize the path
const normalized = path.normalize(includePath);
// Check for path traversal patterns
if (normalized.includes('..') || normalized.includes('~') || path.isAbsolute(normalized)) {
return false;
}
// Only allow alphanumeric, dash, underscore, forward slash, backslash (for Windows), and .md extension
// Note: We test against the original path to preserve cross-platform compatibility
const validPathPattern = /^[a-zA-Z0-9\-_\/\\]+\.md$/;
return validPathPattern.test(includePath);
}
/**
* Compile the template for efficient rendering
* SECURITY FIX #1: Safe template compilation without eval() or Function()
* Uses regex-based token replacement instead of dynamic code execution
*/
compile() {
if (this.compiledTemplate) {
return this.compiledTemplate;
}
// Extract all variable tokens from the template
const variablePattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*\}\}/g;
const tokens = [];
let match;
while ((match = variablePattern.exec(this.content)) !== null) {
tokens.push({
token: match[0],
variable: match[1],
position: match.index
});
}
this.compiledTemplate = {
content: this.content,
tokens,
variables: this.metadata.variables || []
};
return this.compiledTemplate;
}
/**
* Render the template with provided variables
* SECURITY FIX #1: Safe rendering without code execution
* SECURITY FIX #3: All variables are validated and sanitized
* TYPE SAFETY: Strong typing for variables with runtime validation
*/
async render(variables = {}, includeDepth = 0) {
// SECURITY FIX #4: Prevent infinite include loops
if (includeDepth > this.MAX_INCLUDE_DEPTH) {
SecurityMonitor.logSecurityEvent({
type: 'INCLUDE_DEPTH_EXCEEDED',
severity: 'HIGH',
source: 'Template.render',
details: `Include depth ${includeDepth} exceeds maximum ${this.MAX_INCLUDE_DEPTH}`
});
throw new Error('Maximum template include depth exceeded');
}
// Compile the template
const compiled = this.compile();
// Validate and sanitize all provided variables
const sanitizedVariables = await this.validateAndSanitizeVariables(variables);
// Start with the template content
let rendered = compiled.content;
// Replace tokens in reverse order to maintain positions
const sortedTokens = [...compiled.tokens].sort((a, b) => b.position - a.position);
for (const token of sortedTokens) {
const value = this.resolveVariable(token.variable, sanitizedVariables);
const stringValue = this.formatValue(value);
// Replace the token with the sanitized value
rendered = rendered.substring(0, token.position) +
stringValue +
rendered.substring(token.position + token.token.length);
}
// Process includes if any
if (this.metadata.includes && this.metadata.includes.length > 0) {
rendered = await this.processIncludes(rendered, sanitizedVariables, includeDepth);
}
// Update usage statistics
// NOTE: These updates are not atomic and may have race conditions under concurrent access
// This is acceptable for usage statistics which don't require perfect accuracy
// For production systems requiring atomic counters, consider using a database or atomic operations
this.metadata.usage_count = (this.metadata.usage_count || 0) + 1;
this.metadata.last_used = new Date().toISOString();
// SECURITY FIX #5: Log template usage for audit trail
SecurityMonitor.logSecurityEvent({
type: 'TEMPLATE_RENDERED',
severity: 'LOW',
source: 'Template.render',
details: `Template ${this.metadata.name} rendered with ${Object.keys(sanitizedVariables).length} variables`
});
return rendered;
}
/**
* Validate and sanitize variables according to their definitions
* SECURITY FIX #3: Comprehensive validation of all input variables
* TYPE SAFETY: Improved type safety for variable validation
*/
async validateAndSanitizeVariables(variables) {
const sanitized = {};
// Check required variables
for (const varDef of this.metadata.variables || []) {
if (varDef.required && !(varDef.name in variables)) {
if (varDef.default !== undefined) {
sanitized[varDef.name] = varDef.default;
}
else {
throw new Error(`Required variable '${varDef.name}' not provided`);
}
}
}
// Validate and sanitize provided variables
for (const [name, value] of Object.entries(variables)) {
const varDef = this.metadata.variables?.find(v => v.name === name);
if (!varDef) {
// Skip unknown variables (they won't be used anyway)
logger.warn(`Unknown variable '${name}' provided to template`);
continue;
}
// Type validation and sanitization
const sanitizedValue = await this.sanitizeVariableValue(value, varDef);
sanitized[name] = sanitizedValue;
}
// Apply defaults for missing optional variables
for (const varDef of this.metadata.variables || []) {
if (!varDef.required && !(varDef.name in sanitized) && varDef.default !== undefined) {
sanitized[varDef.name] = varDef.default;
}
}
return sanitized;
}
/**
* Sanitize a single variable value according to its definition
* SECURITY FIX #3 & #6: Type-specific validation and Unicode normalization
*/
async sanitizeVariableValue(value, varDef) {
switch (varDef.type) {
case 'string':
// SECURITY FIX #6: Unicode normalization
const normalized = UnicodeValidator.normalize(String(value));
let stringValue = sanitizeInput(normalized.normalizedContent, this.MAX_STRING_LENGTH);
// Apply regex validation if specified
if (varDef.validation) {
// SECURITY FIX: Validate regex complexity to prevent ReDoS attacks
// Previously: User-provided regex executed without limits
// Now: Check for dangerous patterns and limit execution time
try {
// Check for dangerous regex patterns
if (this.isDangerousRegex(varDef.validation)) {
throw new Error(`Variable '${varDef.name}' has potentially dangerous validation pattern`);
}
const regex = new RegExp(varDef.validation);
// Use a simple timeout mechanism - in production, consider using a worker thread
const startTime = Date.now();
const result = regex.test(stringValue);
const duration = Date.now() - startTime;
// If regex takes too long, it might be malicious
if (duration > 100) { // 100ms threshold
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_INJECTION_ATTEMPT',
severity: 'HIGH',
source: 'Template.sanitizeVariableValue',
details: `Regex validation took ${duration}ms for variable '${varDef.name}', possible ReDoS`
});
throw new Error(`Variable '${varDef.name}' validation pattern is too complex`);
}
if (!result) {
throw new Error(`Variable '${varDef.name}' does not match validation pattern`);
}
}
catch (e) {
if (e instanceof SyntaxError) {
throw new Error(`Variable '${varDef.name}' has invalid validation pattern`);
}
throw e;
}
}
// Check enum options if specified
if (varDef.options && !varDef.options.includes(stringValue)) {
throw new Error(`Variable '${varDef.name}' must be one of: ${varDef.options.join(', ')}`);
}
return stringValue;
case 'number':
const num = Number(value);
if (isNaN(num)) {
throw new Error(`Variable '${varDef.name}' must be a number`);
}
return num;
case 'boolean':
return Boolean(value);
case 'date':
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new Error(`Variable '${varDef.name}' must be a valid date`);
}
return date;
case 'array':
if (!Array.isArray(value)) {
throw new Error(`Variable '${varDef.name}' must be an array`);
}
// SECURITY FIX: Limit array size to prevent memory exhaustion attacks
// Previously: No limit on array size could lead to DoS
// Now: Enforces reasonable size limit with logging
const MAX_ARRAY_SIZE = 1000;
if (value.length > MAX_ARRAY_SIZE) {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_SIZE_EXCEEDED',
severity: 'MEDIUM',
source: 'Template.sanitizeVariableValue',
details: `Array variable '${varDef.name}' has ${value.length} items, limiting to ${MAX_ARRAY_SIZE}`
});
value = value.slice(0, MAX_ARRAY_SIZE);
}
// Sanitize string elements in arrays
return value.map((item) => typeof item === 'string' ? sanitizeInput(item, 1000) : item);
case 'object':
if (typeof value !== 'object' || value === null) {
throw new Error(`Variable '${varDef.name}' must be an object`);
}
// Deep sanitize string values in objects
return this.sanitizeObject(value);
default:
return value;
}
}
/**
* Recursively sanitize string values in objects
* SECURITY FIX: Truncate deep nesting instead of throwing to prevent DoS
*/
sanitizeObject(obj, depth = 0) {
// SECURITY FIX: Return safe default instead of throwing to prevent DoS attacks
// Previously: Threw error on deep nesting which could be exploited
// Now: Returns string representation for excessively nested objects
if (depth > 10) {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_SIZE_EXCEEDED',
severity: 'MEDIUM',
source: 'Template.sanitizeObject',
details: 'Object nesting depth exceeded, truncating to string representation'
});
return '[Object too deeply nested]';
}
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
// SECURITY FIX: Limit array size to prevent memory exhaustion
const MAX_ARRAY_SIZE = 1000;
if (obj.length > MAX_ARRAY_SIZE) {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_SIZE_EXCEEDED',
severity: 'MEDIUM',
source: 'Template.sanitizeObject',
details: `Array size ${obj.length} exceeds maximum ${MAX_ARRAY_SIZE}, truncating`
});
obj = obj.slice(0, MAX_ARRAY_SIZE);
}
return obj.map((item) => this.sanitizeObject(item, depth + 1));
}
const sanitized = {};
// SECURITY FIX: Limit number of object properties to prevent memory exhaustion
const MAX_OBJECT_KEYS = 100;
const entries = Object.entries(obj);
if (entries.length > MAX_OBJECT_KEYS) {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_SIZE_EXCEEDED',
severity: 'MEDIUM',
source: 'Template.sanitizeObject',
details: `Object has ${entries.length} keys, limiting to ${MAX_OBJECT_KEYS}`
});
}
for (let i = 0; i < Math.min(entries.length, MAX_OBJECT_KEYS); i++) {
const [key, value] = entries[i];
const sanitizedKey = sanitizeInput(key, 50);
if (typeof value === 'string') {
sanitized[sanitizedKey] = sanitizeInput(value, 1000);
}
else if (typeof value === 'object') {
sanitized[sanitizedKey] = this.sanitizeObject(value, depth + 1);
}
else {
sanitized[sanitizedKey] = value;
}
}
return sanitized;
}
/**
* Resolve nested variable paths (e.g., "user.name")
* TYPE SAFETY: Improved type safety for variable resolution
*/
resolveVariable(path, variables) {
const parts = path.split('.');
let value = variables;
for (const part of parts) {
if (value && typeof value === 'object' && value !== null && part in value) {
// Type assertion with runtime check - safe because we verified the property exists
value = value[part];
}
else {
return undefined;
}
}
return value;
}
/**
* Check if a regex pattern is potentially dangerous (ReDoS)
* SECURITY FIX: Detect patterns that could cause exponential backtracking
*/
isDangerousRegex(pattern) {
// Check for nested quantifiers which can cause exponential backtracking
const dangerousPatterns = [
/(\+|\*){2,}/, // Multiple quantifiers
/\([^)]*\+\)[+*]/, // Quantified groups with quantifiers inside
/\[[^\]]*\+\][+*]/, // Quantified character classes with quantifiers
/(\\[dws])\1{2,}/, // Repeated character classes
/\(\?\<[!=][^)]+\)/, // Complex lookbehinds
];
for (const dangerous of dangerousPatterns) {
if (dangerous.test(pattern)) {
return true;
}
}
// Check for excessive backtracking potential
// Count groups and quantifiers
const groups = (pattern.match(/\(/g) || []).length;
const quantifiers = (pattern.match(/[+*?{]/g) || []).length;
// If there are many groups and quantifiers, it's potentially dangerous
if (groups > 5 && quantifiers > 5) {
return true;
}
return false;
}
/**
* Format a value for template output
*/
formatValue(value) {
if (value === undefined || value === null) {
return '';
}
if (value instanceof Date) {
return value.toISOString();
}
if (Array.isArray(value)) {
return value.join(', ');
}
if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
}
return String(value);
}
/**
* Process template includes
* SECURITY FIX #2: Safe include processing with path validation
*
* TODO: Implement actual include processing functionality
* This is currently a placeholder that validates the security model
* but does not actually load and render included templates.
*
* Future implementation should:
* 1. Load templates from validated paths
* 2. Render them with current variables
* 3. Replace include markers in content
* 4. Respect includeDepth limit
*/
async processIncludes(content, variables, includeDepth) {
// TODO: Implement actual template include processing
// Current implementation only validates the security model
if (!this.metadata.includes || this.metadata.includes.length === 0) {
return content;
}
// Log security event for audit trail
SecurityMonitor.logSecurityEvent({
type: 'TEMPLATE_INCLUDE',
severity: 'LOW',
source: 'Template.processIncludes',
details: `Processing ${this.metadata.includes.length} includes at depth ${includeDepth}`
});
// TODO: Future implementation would:
// for (const includePath of this.metadata.includes) {
// const template = await this.loadIncludedTemplate(includePath);
// const rendered = await template.render(variables, includeDepth + 1);
// content = content.replace(`{{include:${includePath}}}`, rendered);
// }
return content;
}
/**
* Template-specific validation
*/
validate() {
const result = super.validate();
// Initialize arrays if not present
if (!result.errors)
result.errors = [];
if (!result.warnings)
result.warnings = [];
// Content validation
if (!this.content || this.content.trim().length === 0) {
result.errors.push({
field: 'content',
message: 'Template content cannot be empty',
code: 'EMPTY_CONTENT'
});
}
// Check for unmatched tokens
const openTokens = (this.content.match(/\{\{/g) || []).length;
const closeTokens = (this.content.match(/\}\}/g) || []).length;
if (openTokens !== closeTokens) {
result.errors.push({
field: 'content',
message: 'Template has unmatched variable tokens',
code: 'UNMATCHED_TOKENS'
});
}
// Validate output format
const validFormats = ['markdown', 'html', 'json', 'yaml', 'text', 'xml'];
if (this.metadata.output_format && !validFormats.includes(this.metadata.output_format)) {
result.warnings.push({
field: 'output_format',
message: `Unknown output format '${this.metadata.output_format}'. Common formats: ${validFormats.join(', ')}`,
severity: 'low'
});
}
// Validate variables
if (this.metadata.variables) {
const variableNames = new Set();
this.metadata.variables.forEach((variable, index) => {
// Check for duplicate names
if (variableNames.has(variable.name)) {
result.errors.push({
field: `variables[${index}].name`,
message: `Duplicate variable name '${variable.name}'`,
code: 'DUPLICATE_VARIABLE'
});
}
variableNames.add(variable.name);
// Validate regex patterns
if (variable.validation) {
try {
new RegExp(variable.validation);
}
catch (e) {
result.errors.push({
field: `variables[${index}].validation`,
message: `Invalid regex pattern: ${e}`,
code: 'INVALID_REGEX'
});
}
}
});
}
// Check if all tokens have corresponding variable definitions
const compiled = this.compile();
const definedVars = new Set(this.metadata.variables?.map(v => v.name) || []);
const usedVars = new Set(compiled.tokens.map(t => t.variable.split('.')[0]));
usedVars.forEach(varName => {
if (!definedVars.has(varName)) {
result.warnings.push({
field: 'variables',
message: `Template uses undefined variable '${varName}'`,
severity: 'medium'
});
}
});
// Warnings for best practices
if (!this.metadata.tags || this.metadata.tags.length === 0) {
result.warnings.push({
field: 'tags',
message: 'Consider adding tags for better searchability',
severity: 'low'
});
}
if (!this.metadata.examples || this.metadata.examples.length === 0) {
result.warnings.push({
field: 'examples',
message: 'Adding examples improves template usability',
severity: 'medium'
});
}
// Update the valid flag based on final errors
result.valid = (result.errors?.length || 0) === 0;
return result;
}
/**
* Serialize template to JSON format
*/
serialize() {
const data = {
id: this.id,
type: this.type,
version: this.version,
metadata: this.metadata,
content: this.content,
references: this.references,
extensions: this.extensions,
ratings: this.ratings
};
return JSON.stringify(data, null, 2);
}
/**
* Deserialize template from JSON format
*/
deserialize(data) {
try {
const parsed = JSON.parse(data);
// Update metadata
this.metadata = { ...this.metadata, ...parsed.metadata };
// Update content
this.content = parsed.content || '';
// Update other properties
this.references = parsed.references || [];
this.extensions = parsed.extensions || {};
this.ratings = parsed.ratings || this.ratings;
// Update ID and version if provided
if (parsed.id)
this.id = parsed.id;
if (parsed.version)
this.version = parsed.version;
// Clear compiled template cache
this.compiledTemplate = undefined;
this._isDirty = true;
logger.debug(`Deserialized template: ${this.metadata.name}`);
}
catch (error) {
logger.error(`Failed to deserialize template: ${error}`);
throw new Error(`Deserialization failed: ${error}`);
}
}
/**
* Get a preview of the template with sample data
*/
async preview() {
const sampleVars = {};
// Generate sample data for each variable
for (const varDef of this.metadata.variables || []) {
switch (varDef.type) {
case 'string':
sampleVars[varDef.name] = varDef.default || `[${varDef.name}]`;
break;
case 'number':
sampleVars[varDef.name] = varDef.default || 42;
break;
case 'boolean':
sampleVars[varDef.name] = varDef.default || true;
break;
case 'date':
sampleVars[varDef.name] = varDef.default || new Date();
break;
case 'array':
sampleVars[varDef.name] = varDef.default || ['item1', 'item2'];
break;
case 'object':
sampleVars[varDef.name] = varDef.default || { key: 'value' };
break;
}
}
return this.render(sampleVars);
}
/**
* Template activation lifecycle
*/
async activate() {
logger.info(`Activating template: ${this.metadata.name} (${this.id})`);
// Compile the template to check for errors
this.compile();
await super.activate?.();
}
/**
* Template deactivation lifecycle
*/
async deactivate() {
logger.info(`Deactivating template: ${this.metadata.name} (${this.id})`);
// Clear compiled template cache
this.compiledTemplate = undefined;
await super.deactivate?.();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVGVtcGxhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZWxlbWVudHMvdGVtcGxhdGVzL1RlbXBsYXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7OztHQVdHO0FBRUgsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRWhELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDL0MsT0FBTyxFQUFFLGFBQWEsRUFBZ0IsTUFBTSxrQ0FBa0MsQ0FBQztBQUMvRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDcEUsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFnQzdCLE1BQU0sT0FBTyxRQUFTLFNBQVEsV0FBVztJQUVoQyxPQUFPLENBQVM7SUFDZixnQkFBZ0IsQ0FBb0I7SUFFNUMsK0NBQStDO0lBQy9DLGdGQUFnRjtJQUMvRCxpQkFBaUIsR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDLENBQUMsMEJBQTBCO0lBQzFELGtCQUFrQixHQUFHLEdBQUcsQ0FBQyxDQUFPLDZCQUE2QjtJQUM3RCxpQkFBaUIsR0FBRyxDQUFDLENBQUMsQ0FBVSxpQ0FBaUM7SUFDakUsaUJBQWlCLEdBQUcsS0FBSyxDQUFDLENBQU0sa0NBQWtDO0lBRW5GLFlBQVksUUFBbUMsRUFBRSxVQUFrQixFQUFFO1FBQ25FLGtFQUFrRTtRQUNsRSxtREFBbUQ7UUFDbkQsd0RBQXdEO1FBQ3hELE1BQU0saUJBQWlCLEdBQUc7WUFDeEIsR0FBRyxRQUFRO1lBQ1gsSUFBSSxFQUFFLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsUUFB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