@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.
647 lines • 110 kB
JavaScript
/**
* Content Validator for DollhouseMCP
*
* Protects against prompt injection attacks in collection personas
* by detecting and sanitizing malicious content patterns.
*
* Security: SEC-001 - Critical vulnerability protection
*/
import { createHash } from 'node:crypto';
import { SecurityError } from './errors.js';
import { SecurityMonitor } from './securityMonitor.js';
import { logger } from '../utils/logger.js';
import { RegexValidator } from './regexValidator.js';
import { SECURITY_LIMITS } from './constants.js';
import { UnicodeValidator } from './validators/unicodeValidator.js';
export class ContentValidator {
/**
* SHA-256 hashes of bundled data/ elements verified against HASHES.json at seed time.
* Content whose hash is in this set bypasses injection-pattern checks — it was
* vetted by the DollhouseMCP team before being included in the npm package.
* Unicode and YAML-bomb checks still run regardless.
*
* Populated by DefaultElementProvider.registerBundledHashes() on startup.
* Only content that matches the published HASHES.json is registered — any
* modification to a bundled file after install breaks the hash and revokes trust.
*/
static bundledContentHashes = new Set();
/** Register a SHA-256 hex hash as trusted bundled content. */
static registerBundledHash(sha256hex) {
this.bundledContentHashes.add(sha256hex);
}
/** True if the given content hash belongs to a verified bundled element. */
static isBundledContent(content) {
if (this.bundledContentHashes.size === 0)
return false;
const hash = createHash('sha256').update(content).digest('hex');
return this.bundledContentHashes.has(hash);
}
static telemetryResolver;
static configureTelemetryResolver(resolver) {
this.telemetryResolver = resolver;
}
static getTelemetry() {
try {
return this.telemetryResolver ? this.telemetryResolver() : undefined;
}
catch {
return undefined;
}
}
/**
* Pattern-based detection system for prompt injection attacks.
*
* This approach was chosen over AI-based detection because:
* 1. Pattern matching cannot be socially engineered or confused
* 2. Deterministic results ensure consistent security
* 3. No additional API calls or latency
* 4. Can't be bypassed by clever prompt engineering
*
* The patterns below represent known attack vectors from security research
* and real-world exploit attempts against AI systems.
*/
// Prompt injection patterns that could compromise AI assistants.
// MAINTENANCE: Review periodically for false positives (run integration tests in
// tests/integration/security-audit-batch-a.integration.test.ts) and new attack vectors.
static INJECTION_PATTERNS = [
// System prompt override attempts
{ pattern: /\[SYSTEM:\s*.*?\]/gi, severity: 'critical', description: 'System prompt override' },
{ pattern: /\[ADMIN:\s*.*?\]/gi, severity: 'critical', description: 'Admin prompt override' },
{ pattern: /\[ASSISTANT:\s*.*?\]/gi, severity: 'critical', description: 'Assistant prompt override' },
{ pattern: /\[USER:\s*.*?\]/gi, severity: 'high', description: 'User prompt override' },
// Instruction manipulation
{ pattern: /ignore\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /ignore\s+(all\s+)?prior\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /disregard\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /disregard\s+everything\s+above/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /forget\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /forget\s+your\s+training/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /override\s+your\s+programming/gi, severity: 'critical', description: 'Instruction override' },
{ pattern: /you\s+are\s+now\s+(in\s+)?(admin|root|system|sudo|developer|debug|test|DAN)\s*(mode)?/gi, severity: 'critical', description: 'Role elevation attempt' },
// Specific dangerous roles only — "act as \w+" would false-positive on persona
// content like "act as a helpful teacher" (#1782-4, review feedback)
{ pattern: /act\s+as\s+(admin|root|system|sudo|superuser|DAN)\b/gi, severity: 'critical', description: 'Role elevation attempt' },
{ pattern: /pretend\s+you\s+have\s+no\s+(guidelines|restrictions|rules|limits)/gi, severity: 'critical', description: 'Guideline bypass attempt' },
{ pattern: /\b(jailbreak|do\s+anything\s+now|DAN\s+mode)\b/gi, severity: 'critical', description: 'Jailbreak attempt' },
// Data exfiltration attempts
{ pattern: /export\s+all\s+(files|data|personas|tokens|credentials|api\s+keys)/gi, severity: 'critical', description: 'Data exfiltration' },
{ pattern: /send\s+all\s+(files|data|personas|tokens|credentials|api\s+keys)\s+to/gi, severity: 'critical', description: 'Data exfiltration' },
{ pattern: /list\s+all\s+(files|tokens|credentials|secrets|api\s+keys)/gi, severity: 'high', description: 'Information disclosure' },
{ pattern: /show\s+me\s+all\s+(tokens|credentials|secrets|api\s+keys)/gi, severity: 'high', description: 'Credential disclosure' },
// Command execution patterns
{ pattern: /curl\s+[^\s]{1,500}/gi, severity: 'critical', description: 'External command execution' },
{ pattern: /wget\s+[^\s]{1,500}/gi, severity: 'critical', description: 'External command execution' },
{ pattern: /\$\([^)]+\)/g, severity: 'critical', description: 'Command substitution' },
// SECURITY: Backtick command detection with ReDoS mitigation
// FIX (PR #1313): Fixed ReDoS vulnerabilities by replacing .* with [^`]*
// FIX (PR #1313 - SonarCloud): Added explicit bounds {0,200} to prevent backtracking
// Multiple unbounded quantifiers in same pattern can still cause backtracking even with [^`]*
// Bounded quantifiers prevent exponential time complexity while matching realistic commands
{ pattern: /`[^`]{0,200}(?:rm\s+-rf?\s+[/~]|sudo\s+rm|chmod\s+777|chown\s+root)[^`]{0,200}`/gi, severity: 'critical', description: 'Dangerous shell command in backticks' },
{ pattern: /`[^`]{0,200}(?:cat|ls)\s+\/etc\/[^`]{0,200}`/gi, severity: 'critical', description: 'Sensitive file access in backticks' },
{ pattern: /`[^`]{0,200}(?:bash|sh)\s+-c\s+['"][^`]{0,200}`/gi, severity: 'critical', description: 'Shell execution in backticks' },
{ pattern: /`[^`]{0,200}(?:passwd|shadow|nc\s+-l|netcat\s+-l|ssh\s+root@)[^`]{0,200}`/gi, severity: 'critical', description: 'Dangerous command in backticks' },
{ pattern: /`[^`]{0,200}(?:curl|wget)\s+[^`]{0,200}\|\s*(?:sh|bash)[^`]{0,200}`/gi, severity: 'critical', description: 'Pipe to shell in backticks' },
{ pattern: /`[^`]{0,200}(?:\/etc\/passwd|\/etc\/shadow|\.ssh\/id_|sudo\s+su)[^`]{0,200}`/gi, severity: 'critical', description: 'Sensitive file or privilege escalation in backticks' },
{ pattern: /`[^`]{0,200}(?:python|perl|ruby|php|node)\s+(?:-e|-c)\s+[^`]{0,200}(?:exec|eval|system|subprocess)[^`]{0,200}`/gi, severity: 'critical', description: 'Script interpreter with dangerous function in backticks' },
{ pattern: /eval\s*\(/gi, severity: 'critical', description: 'Code evaluation' },
{ pattern: /exec\s*\(/gi, severity: 'critical', description: 'Code execution' },
{ pattern: /os\.system\s*\(/gi, severity: 'critical', description: 'System command execution' },
{ pattern: /subprocess\.(call|run|Popen)/gi, severity: 'critical', description: 'Subprocess execution' },
// Token/credential patterns
{ pattern: /GITHUB_TOKEN/gi, severity: 'high', description: 'Token reference' },
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, severity: 'critical', description: 'GitHub token exposure' },
{ pattern: /gho_[a-zA-Z0-9]{36}/g, severity: 'critical', description: 'GitHub OAuth token exposure' },
// Path traversal in content
{ pattern: /\.\.\/\.\.\/\.\.\//g, severity: 'high', description: 'Path traversal attempt' },
{ pattern: /\/etc\/passwd/gi, severity: 'high', description: 'Sensitive file access' },
{ pattern: /\/\.ssh\//gi, severity: 'high', description: 'SSH key access attempt' },
// HTML/XSS patterns — defense-in-depth for community-sourced content
// DOMPurify on the client is the primary defense; these catch threats at ingest
{ pattern: /<script[\s>]/gi, severity: 'critical', description: 'HTML script injection' },
{ pattern: /<\/script>/gi, severity: 'critical', description: 'HTML script injection' },
{ pattern: /<iframe[\s>]/gi, severity: 'critical', description: 'HTML iframe injection' },
{ pattern: /<object[\s>]/gi, severity: 'high', description: 'HTML object injection' },
{ pattern: /<embed[\s>]/gi, severity: 'high', description: 'HTML embed injection' },
{ pattern: /\bon\w+=\s*["']/gi, severity: 'critical', description: 'HTML event handler injection' },
{ pattern: /javascript[ \t]*:[ \t]*\S/gi, severity: 'critical', description: 'JavaScript protocol injection' },
// Entity-encoded variants: javascript, javascript, javascript, etc.
{ pattern: /&#x?[0-9a-f]+;?\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t/gi, severity: 'critical', description: 'Encoded JavaScript protocol injection' },
// Fully/partially entity-encoded: detects &#...script pattern (covers multi-entity encoding)
{ pattern: /(?:&#x?[0-9a-f]+;?\s*){2,}s\s*c\s*r\s*i\s*p\s*t/gi, severity: 'critical', description: 'Encoded JavaScript protocol injection' },
];
// Malicious YAML patterns
// SECURITY FIX #364: YAML bomb detection patterns
// SECURITY FIX (PR #552 review): Simplified patterns to reduce ReDoS risk
static YAML_BOMB_PATTERNS = [
// Detects recursive anchor references that could cause exponential expansion
// Example: &a [*a] or &bomb ["test", *bomb]
/&(\w+)\s*\[[^\]]*\*\1[^\]]*\]/, // Direct recursion in array
/&(\w+)\s*\{[^}]*\*\1[^}]*\}/, // Direct recursion in object
/^\s*\w+:\s*&(\w+)\s*\n\s*\w+:\s*\*\1/m, // Multi-line value recursion (data: &ref / value: *ref)
// Simplified pattern to detect deeply nested anchors (less ReDoS risk)
// Looks for 3+ anchor definitions in close proximity
/&\w+[^&]*&\w+[^&]*&\w+/, // 3+ anchors (simplified, less backtracking)
// Detects excessive aliases in close proximity (potential amplification)
// Example: [*a, *b, *c, *d, *e, *f, *g, *h, *i, *j]
/\*\w+(?:[,\s]+\*\w+){9,}/, // 10+ aliases in sequence (non-capturing group)
];
static MALICIOUS_YAML_PATTERNS = [
// Language-specific deserialization attacks
/!!python\/object/,
/!!python\/module/,
/!!python\/name/,
/!!ruby\/object/,
/!!ruby\/hash/,
/!!ruby\/struct/,
/!!ruby\/marshal/,
/!!java/,
/!!javax/,
/!!com\.sun/,
/!!perl\/hash/,
/!!perl\/code/,
/!!php\/object/,
// Constructor/function injection
/!!exec/,
/!!eval/,
/!!new/,
/!!construct/,
/!!apply/,
/!!call/,
/!!invoke/,
// Code execution patterns - more specific to avoid false positives
/subprocess\./,
/os\.system/,
/eval\s*\(/,
/exec\s*\(/,
/__import__\s*\(/,
/require\s*\(/,
/import\s+(?:os|sys|subprocess|eval|exec)/,
/include\s+["'].*\.(?:php|sh|py|js|rb)["']/,
// Command execution variants - more specific patterns
/popen\s*\(/,
/spawn\s*\(/,
/system\s*\(/,
/backtick\s*\(/,
/shell_exec\s*\(/,
/passthru\s*\(/,
/proc_open\s*\(/,
// Network operations - require suspicious context
/socket\.connect/, // Detects socket connection attempts
/urllib\.request/, // Python HTTP library usage
/requests\.(?:get|post|put|delete)\s*\(/, // Detects HTTP requests with method calls
/fetch\s*\(\s*["']https?:\/\//, // Detects fetch calls to external URLs
/new\s+XMLHttpRequest/, // JavaScript AJAX object creation
/\.(?:get|post|put|delete)\s*\(\s*["']https?:\/\//, // Method chaining with HTTP requests
// File system operations - require suspicious context
/(?:fs\.|file\.|)\s*open\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // File open with suspicious paths
/file_get_contents\s*\(/, // PHP file reading function
/file_put_contents\s*\(/, // PHP file writing function
/fopen\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // File open with dangerous system paths
/(?:fs\.)?\s*readFile\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // Node.js file read with path traversal
/(?:fs\.)?\s*writeFile\s*\(\s*["'](?:\/(?:bin|etc|tmp)\/|\.\.\/)/, // Node.js file write to system dirs
// Protocol handlers
/file:\/\//,
/data:\/\//,
/expect:\/\//,
/php:\/\//,
/phar:\/\//,
/zip:\/\//,
/ssh2:\/\//,
/ogg:\/\//,
// YAML-specific dangerous features
/&\w+\s*!!/, // Anchor with tag combination
/\*\w+\s*!!/, // Alias with tag combination
/!!merge/,
/!!binary/,
/!!timestamp/,
// Unicode/encoding bypass attempts - prevent visual spoofing attacks
/\\[uU]0*(?:22|27|60|3[cC])/, // Unicode escapes for quotes (") and brackets (<>)
/[\u202A-\u202E\u2066-\u2069]/, // Direction override chars (RLO, LRO, isolates)
/[\u200B-\u200F\u2028-\u202F]/, // Zero-width spaces, line/paragraph separators
/[\uFEFF\uFFFE\uFFFF]/, // BOM, non-characters for payload hiding
];
/**
* Content contexts where code execution patterns are legitimate and should
* not trigger security blocks. Skills contain exemplar code; templates contain
* code snippets that are rendered, never executed; agent definitions describe
* technical workflows that may reference code. Prompt injection, actual token
* exposure (ghp_/gho_), data exfiltration, and HTML/XSS patterns remain
* active for ALL contexts.
* @since Issue #456
*/
static CODE_EXEMPT_CONTEXTS = new Set([
'skill', // Exemplar code patterns the LLM should follow
'template', // Code snippets rendered into output, never executed
'agent', // Technical workflow definitions — without this, agents would need
// to pull in a skill or template just to reference code, adding
// coupling without security value. Agent definitions are authored
// content read as LLM context, same as skills and templates.
]);
/**
* Pattern descriptions that are exempt for CODE_EXEMPT_CONTEXTS.
* These patterns match legitimate code documentation, not threats.
* @since Issue #456
*/
static CODE_EXECUTION_PATTERNS = new Set([
'Code evaluation',
'Code execution',
'System command execution',
'Subprocess execution',
]);
/**
* Security documentation patterns exempt for CODE_EXEMPT_CONTEXTS.
* Skills/agents that teach penetration testing, threat modeling, etc.
* legitimately reference shell commands, file paths, and credential names.
*
* DISTINCTION FROM ACTIVE THREAT PATTERNS:
* These patterns describe attacks (educational) — they appear in element
* definitions that an author wrote, not in runtime user input. Patterns
* that remain active even in exempt contexts are actual threats:
* - Prompt injection (system/admin override, instruction manipulation)
* - Real token formats (ghp_*, gho_* — not just the word "GITHUB_TOKEN")
* - Data exfiltration commands (export/send all credentials)
* - HTML/XSS injection (renders in the web console)
*
* @since Issue #1725
*/
static SECURITY_DOC_PATTERNS = new Set([
'Command substitution',
'External command execution',
'Sensitive file access',
'Path traversal attempt',
'SSH key access attempt',
'Token reference',
'Dangerous shell command in backticks',
'Sensitive file access in backticks',
'Shell execution in backticks',
'Dangerous command in backticks',
'Pipe to shell in backticks',
'Sensitive file or privilege escalation in backticks',
'Script interpreter with dangerous function in backticks',
]);
/**
* HTML/XSS pattern descriptions exempt for template context.
* Templates use <template>, <style>, <script> as section delimiters.
* @since Issue #803
*/
static HTML_SECTION_PATTERNS = new Set([
'HTML script injection',
'HTML object injection',
'HTML embed injection',
]);
/**
* Handles Unicode validation and threat detection
* REFACTOR: Extracted from validateAndSanitize() to reduce cognitive complexity
* Returns normalized content and Unicode severity without aborting early
*/
static handleUnicodeValidation(content, detectedPatterns) {
const unicodeResult = UnicodeValidator.normalize(content);
const sanitized = unicodeResult.normalizedContent;
let highestSeverity = 'low';
if (!unicodeResult.isValid && unicodeResult.detectedIssues) {
detectedPatterns.push(...unicodeResult.detectedIssues.map(issue => `Unicode: ${issue}`));
if (unicodeResult.severity) {
highestSeverity = unicodeResult.severity;
}
// Log high/critical Unicode attacks
if (unicodeResult.severity === 'critical' || unicodeResult.severity === 'high') {
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_INJECTION_ATTEMPT',
severity: unicodeResult.severity.toUpperCase(),
source: 'content_validation',
details: `Unicode attack detected: ${unicodeResult.detectedIssues.join(', ')}`,
});
ContentValidator.getTelemetry()?.recordBlockedAttack('UNICODE_ATTACK', unicodeResult.detectedIssues.join(', '), unicodeResult.severity.toUpperCase(), 'unicode_validation', { issues: unicodeResult.detectedIssues });
}
}
return { sanitized, highestSeverity };
}
/**
* Checks content for injection patterns and logs/sanitizes threats
* REFACTOR: Extracted from validateAndSanitize() to reduce cognitive complexity
*
* @param originalContent - Original content to check patterns against
* @param normalizedContent - Normalized content to apply replacements to
* @param detectedPatterns - Array to accumulate detected pattern descriptions
* @param currentSeverity - Current highest severity level
* @param maxLength - Maximum allowed content length for regex validation
*/
static checkInjectionPatterns(originalContent, normalizedContent, detectedPatterns, currentSeverity, maxLength, contentContext) {
let sanitized = normalizedContent;
let highestSeverity = currentSeverity;
for (const { pattern, severity, description } of this.INJECTION_PATTERNS) {
// Fix #456/#1725: Skip code execution and security documentation patterns
// for element types that legitimately contain code and attack descriptions.
// Prompt injection, actual token exposure, and HTML/XSS remain active.
if (contentContext && this.CODE_EXEMPT_CONTEXTS.has(contentContext) &&
(this.CODE_EXECUTION_PATTERNS.has(description) || this.SECURITY_DOC_PATTERNS.has(description))) {
continue;
}
// Fix #803: Skip HTML section tag patterns for templates (use <script>/<style> as section delimiters)
if (contentContext === 'template' && this.HTML_SECTION_PATTERNS.has(description)) {
continue;
}
// Check pattern on original content (before normalization) to catch encoded attacks
if (RegexValidator.validate(originalContent, pattern, {
maxLength,
rejectDangerousPatterns: false,
logEvents: false
})) {
detectedPatterns.push(description);
logger.debug(`Content injection blocked: ${description} (${severity}) — pattern: ${pattern.source}`);
// Update highest severity
if (severity === 'critical' || (severity === 'high' && highestSeverity !== 'critical')) {
highestSeverity = severity;
}
// Log security event
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_INJECTION_ATTEMPT',
severity: severity.toUpperCase(),
source: 'content_validation',
details: `Detected pattern: ${description}`,
});
// Record in telemetry
ContentValidator.getTelemetry()?.recordBlockedAttack('CONTENT_INJECTION', description, severity.toUpperCase(), 'content_validation', { pattern: pattern.source });
// Apply replacement to normalized content
sanitized = sanitized.replace(pattern, '[CONTENT_BLOCKED]');
}
}
return { sanitized, highestSeverity };
}
/**
* Validates and sanitizes persona content for security threats
* FIX #1269: Added options to support large memory content
* REFACTOR: Reduced cognitive complexity by extracting helper methods
*
* SECURITY FIX (DMCP-SEC-004): Length checks now performed on NORMALIZED content
* to prevent bypass attacks using Unicode combining characters or zero-width chars.
* A pre-check with generous multiplier prevents DoS from huge payloads.
*/
static validateAndSanitize(content, options = {}) {
// Determine max length for validation
const maxLength = options.maxLength || SECURITY_LIMITS.MAX_CONTENT_LENGTH;
// SECURITY FIX (DMCP-SEC-004): Two-phase length validation
// Phase 1: DoS prevention pre-check on raw content (generous 2x multiplier)
// This prevents huge payloads from hitting the normalization code path
// while still allowing legitimate content with some Unicode overhead
const DOS_PREVENTION_MULTIPLIER = 2;
if (!options.skipSizeCheck) {
if (content.length > maxLength * DOS_PREVENTION_MULTIPLIER) {
throw new SecurityError(`Content exceeds maximum length of ${maxLength} characters (${content.length} provided)`);
}
}
const detectedPatterns = [];
// Handle Unicode validation (normalizes content but doesn't abort)
const unicodeCheck = this.handleUnicodeValidation(content, detectedPatterns);
// SECURITY FIX (DMCP-SEC-004): Phase 2 - Check length on NORMALIZED content
// This prevents bypass attacks using combining characters or zero-width chars
// that would inflate raw length but collapse after normalization
if (!options.skipSizeCheck) {
if (unicodeCheck.sanitized.length > maxLength) {
throw new SecurityError(`Content exceeds maximum length of ${maxLength} characters after normalization (${unicodeCheck.sanitized.length} provided)`);
}
}
// Skip injection-pattern scanning for verified bundled elements.
// Content whose SHA-256 matches HASHES.json was reviewed by the DollhouseMCP
// team before being included in the npm package — false positives from
// legitimate YAML keys (javascript:) or educational payloads (wget in pentest
// templates) should not fire CRITICAL alerts at every install.
// Unicode and YAML-bomb checks above still run unconditionally.
if (this.isBundledContent(content)) {
logger.debug('[ContentValidator] Skipping injection scan for verified bundled element');
return {
isValid: true,
sanitizedContent: unicodeCheck.sanitized,
detectedPatterns: [],
severity: 'low'
};
}
// Check for injection patterns on ORIGINAL content (to catch encoded attacks)
// but apply replacements to NORMALIZED content (to preserve normalization)
const injectionCheck = this.checkInjectionPatterns(content, unicodeCheck.sanitized, detectedPatterns, unicodeCheck.highestSeverity, maxLength, options.contentContext);
// Use highest severity from either Unicode or injection checks
const finalSeverity = injectionCheck.highestSeverity;
// Abort if high/critical threats detected
if (finalSeverity === 'high' || finalSeverity === 'critical') {
return {
isValid: false,
sanitizedContent: injectionCheck.sanitized,
detectedPatterns,
severity: finalSeverity
};
}
return {
isValid: detectedPatterns.length === 0,
sanitizedContent: injectionCheck.sanitized,
detectedPatterns,
severity: finalSeverity
};
}
/**
* Validates YAML frontmatter for malicious content
* SECURITY FIX #364: Added YAML bomb detection to prevent denial of service
*/
static validateYamlContent(yamlContent) {
// Length validation before pattern matching
if (yamlContent.length > SECURITY_LIMITS.MAX_YAML_LENGTH) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_INJECTION_ATTEMPT',
severity: 'HIGH',
source: 'yaml_validation',
details: `YAML content exceeds maximum length: ${yamlContent.length} > ${SECURITY_LIMITS.MAX_YAML_LENGTH}`
});
return false;
}
// SECURITY FIX #364: Check for YAML bombs before other validation
// SECURITY FIX (PR #552 review): Use RegexValidator for ReDoS protection
for (const pattern of this.YAML_BOMB_PATTERNS) {
// Use RegexValidator to safely check patterns with timeout protection
// This prevents ReDoS attacks from maliciously crafted YAML
const isMatch = RegexValidator.validate(yamlContent, pattern, {
maxLength: SECURITY_LIMITS.MAX_YAML_LENGTH,
rejectDangerousPatterns: false, // Our patterns are trusted
logEvents: false // We handle logging ourselves
});
if (isMatch) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_INJECTION_ATTEMPT',
severity: 'CRITICAL',
source: 'yaml_bomb_detection',
details: `YAML bomb pattern detected: ${pattern.source}`,
metadata: {
patternType: 'YAML_BOMB',
contentLength: yamlContent.length
}
});
// Record in telemetry
ContentValidator.getTelemetry()?.recordBlockedAttack('YAML_BOMB', `YAML bomb pattern: ${pattern.source}`, 'CRITICAL', 'yaml_validation', { patternType: 'YAML_BOMB', contentLength: yamlContent.length });
return false;
}
}
// SECURITY FIX #364: Count anchor/alias ratio for amplification detection
// SECURITY FIX #1298: Use configurable threshold for easier tuning
const anchorMatches = yamlContent.match(/&\w+/g) || [];
// Fix #906: Use negative lookbehind to exclude markdown bold (**word**) from
// matching as YAML aliases. Without this, markdown bold inside YAML strings
// triggers false-positive amplification detection.
const aliasMatches = yamlContent.match(/(?<!\*)\*\w+/g) || [];
const amplificationRatio = anchorMatches.length > 0 ? aliasMatches.length / anchorMatches.length : 0;
if (amplificationRatio > SECURITY_LIMITS.YAML_BOMB_AMPLIFICATION_THRESHOLD) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_INJECTION_ATTEMPT',
severity: 'HIGH',
source: 'yaml_amplification_detection',
details: `Excessive alias amplification detected: ${aliasMatches.length} aliases for ${anchorMatches.length} anchors (ratio: ${amplificationRatio.toFixed(2)})`,
metadata: {
anchors: anchorMatches.length,
aliases: aliasMatches.length,
ratio: amplificationRatio
}
});
return false;
}
// SECURITY FIX #364: Detect circular reference chains
// SECURITY FIX (PR #552 review): Optimized from O(n²) to O(n) using Set-based lookups
const anchorRefs = new Map();
const lines = yamlContent.split('\n');
// First pass: Build reference map efficiently
for (let i = 0; i < lines.length; i++) {
const anchorMatch = lines[i].match(/&(\w+)/);
if (anchorMatch) {
const anchorName = anchorMatch[1];
// Get references in next 5 lines
const contextEnd = Math.min(i + 5, lines.length);
const references = new Set();
for (let j = i; j < contextEnd; j++) {
const aliasMatches = lines[j].match(/\*(\w+)/g);
if (aliasMatches) {
aliasMatches.forEach(alias => {
references.add(alias.substring(1)); // Remove * prefix
});
}
}
anchorRefs.set(anchorName, references);
}
}
// Second pass: Check for circular references (O(n) with Set lookups)
for (const [anchor1, refs1] of anchorRefs) {
for (const refAnchor of refs1) {
const refs2 = anchorRefs.get(refAnchor);
// Check if the referenced anchor references back to the original
if (refs2 && refs2.has(anchor1)) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_INJECTION_ATTEMPT',
severity: 'CRITICAL',
source: 'yaml_bomb_detection',
details: `Circular reference chain detected between anchors: &${anchor1} and &${refAnchor}`,
metadata: {
patternType: 'CIRCULAR_REFERENCE',
anchors: [anchor1, refAnchor]
}
});
return false;
}
}
}
// Unicode normalization preprocessing for YAML content
const unicodeResult = UnicodeValidator.normalize(yamlContent);
const normalizedYaml = unicodeResult.normalizedContent;
if (!unicodeResult.isValid && unicodeResult.detectedIssues) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_UNICODE_ATTACK',
severity: (unicodeResult.severity?.toUpperCase() || 'MEDIUM'),
source: 'yaml_validation',
details: `Unicode attack detected in YAML: ${unicodeResult.detectedIssues.join(', ')}`
});
return false;
}
for (const pattern of this.MALICIOUS_YAML_PATTERNS) {
// These are trusted internal patterns, so we disable ReDoS rejection
if (RegexValidator.validate(normalizedYaml, pattern, {
maxLength: SECURITY_LIMITS.MAX_CONTENT_LENGTH,
rejectDangerousPatterns: false,
logEvents: false // Don't log our own security patterns as dangerous
})) {
SecurityMonitor.logSecurityEvent({
type: 'YAML_INJECTION_ATTEMPT',
severity: 'CRITICAL',
source: 'yaml_validation',
details: `Malicious YAML pattern detected: ${pattern}`,
});
// Early exit on first match for performance
return false;
}
}
return true;
}
/**
* Validates persona metadata fields
*/
static validateMetadata(metadata) {
const detectedPatterns = [];
// Check all string fields in metadata
const checkField = (fieldName, value) => {
if (typeof value === 'string') {
// Check field length first
if (value.length > SECURITY_LIMITS.MAX_METADATA_FIELD_LENGTH) {
detectedPatterns.push(`${fieldName}: Field exceeds maximum length of ${SECURITY_LIMITS.MAX_METADATA_FIELD_LENGTH} characters`);
return;
}
const result = this.validateAndSanitize(value);
if (!result.isValid || result.detectedPatterns?.length) {
detectedPatterns.push(`${fieldName}: ${result.detectedPatterns?.join(', ')}`);
}
}
};
// Validate standard persona fields
checkField('name', metadata.name);
checkField('description', metadata.description);
checkField('category', metadata.category);
checkField('author', metadata.author);
// Check any custom fields
for (const [key, value] of Object.entries(metadata)) {
if (!['name', 'description', 'category', 'author'].includes(key)) {
checkField(key, value);
}
}
return {
isValid: detectedPatterns.length === 0,
detectedPatterns,
severity: detectedPatterns.length > 0 ? 'high' : 'low'
};
}
/**
* Sanitizes a complete persona file (frontmatter + content)
*/
static sanitizePersonaContent(content) {
// Extract frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!frontmatterMatch) {
// No frontmatter, just validate content
const result = this.validateAndSanitize(content);
if (!result.isValid && result.severity === 'critical') {
// FIX: Include specific patterns that triggered the rejection for debugging
const patterns = result.detectedPatterns?.join(', ') || 'unknown patterns';
throw new SecurityError(`Critical security threat detected in persona content: ${patterns}`);
}
return result.sanitizedContent || content;
}
const yamlContent = frontmatterMatch[1];
const markdownContent = content.substring(frontmatterMatch[0].length);
// Validate YAML
if (!this.validateYamlContent(yamlContent)) {
throw new SecurityError('Malicious YAML detected in persona frontmatter');
}
// Validate markdown content
const contentResult = this.validateAndSanitize(markdownContent);
if (!contentResult.isValid && contentResult.severity === 'critical') {
// FIX: Include specific patterns that triggered the rejection for debugging
const patterns = contentResult.detectedPatterns?.join(', ') || 'unknown patterns';
throw new SecurityError(`Critical security threat detected in persona content: ${patterns}`);
}
// Return sanitized content
return `---\n${yamlContent}\n---${contentResult.sanitizedContent || markdownContent}`;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGVudFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZWN1cml0eS9jb250ZW50VmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O0dBT0c7QUFFSCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDNUMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDckQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBaUNwRSxNQUFNLE9BQU8sZ0JBQWdCO0lBQzNCOzs7Ozs7Ozs7T0FTRztJQUNLLE1BQU0sQ0FBVSxvQkFBb0IsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBRWpFLDhEQUE4RDtJQUM5RCxNQUFNLENBQUMsbUJBQW1CLENBQUMsU0FBaUI7UUFDMUMsSUFBSSxDQUFDLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzQyxDQUFDO0lBRUQsNEVBQTRFO0lBQzVFLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFlO1FBQ3JDLElBQUksSUFBSSxDQUFDLG9CQUFvQixDQUFDLElBQUksS0FBSyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDdkQsTUFBTSxJQUFJLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDaEUsT0FBTyxJQUFJLENBQUMsb0JBQW9CLENBQUMsR0FBRyxDQUFD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