@dollhousemcp/mcp-server 1.7.2 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md.backup +0 -8
  3. package/dist/auth/GitHubAuthManager.js +2 -2
  4. package/dist/config/ConfigManager.d.ts +158 -25
  5. package/dist/config/ConfigManager.d.ts.map +1 -1
  6. package/dist/config/ConfigManager.js +627 -88
  7. package/dist/config/ConfigWizard.d.ts +78 -0
  8. package/dist/config/ConfigWizard.d.ts.map +1 -0
  9. package/dist/config/ConfigWizard.js +370 -0
  10. package/dist/config/ConfigWizardCheck.d.ts +47 -0
  11. package/dist/config/ConfigWizardCheck.d.ts.map +1 -0
  12. package/dist/config/ConfigWizardCheck.js +208 -0
  13. package/dist/config/ConfigWizardDisplay.d.ts +64 -0
  14. package/dist/config/ConfigWizardDisplay.d.ts.map +1 -0
  15. package/dist/config/ConfigWizardDisplay.js +150 -0
  16. package/dist/config/WizardFirstResponse.d.ts +25 -0
  17. package/dist/config/WizardFirstResponse.d.ts.map +1 -0
  18. package/dist/config/WizardFirstResponse.js +118 -0
  19. package/dist/config/portfolioConfig.d.ts +40 -0
  20. package/dist/config/portfolioConfig.d.ts.map +1 -0
  21. package/dist/config/portfolioConfig.js +58 -0
  22. package/dist/config/wizardTemplates.d.ts +84 -0
  23. package/dist/config/wizardTemplates.d.ts.map +1 -0
  24. package/dist/config/wizardTemplates.js +195 -0
  25. package/dist/elements/BaseElement.d.ts +15 -0
  26. package/dist/elements/BaseElement.d.ts.map +1 -1
  27. package/dist/elements/BaseElement.js +38 -5
  28. package/dist/generated/version.d.ts +2 -2
  29. package/dist/generated/version.js +3 -3
  30. package/dist/handlers/ConfigHandler.d.ts +32 -0
  31. package/dist/handlers/ConfigHandler.d.ts.map +1 -0
  32. package/dist/handlers/ConfigHandler.js +202 -0
  33. package/dist/handlers/PortfolioPullHandler.d.ts +69 -0
  34. package/dist/handlers/PortfolioPullHandler.d.ts.map +1 -0
  35. package/dist/handlers/PortfolioPullHandler.js +340 -0
  36. package/dist/handlers/SyncHandlerV2.d.ts +42 -0
  37. package/dist/handlers/SyncHandlerV2.d.ts.map +1 -0
  38. package/dist/handlers/SyncHandlerV2.js +231 -0
  39. package/dist/index.d.ts +18 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +19 -3
  42. package/dist/portfolio/GitHubPortfolioIndexer.d.ts +0 -1
  43. package/dist/portfolio/GitHubPortfolioIndexer.d.ts.map +1 -1
  44. package/dist/portfolio/GitHubPortfolioIndexer.js +36 -16
  45. package/dist/portfolio/PortfolioRepoManager.d.ts +2 -1
  46. package/dist/portfolio/PortfolioRepoManager.d.ts.map +1 -1
  47. package/dist/portfolio/PortfolioRepoManager.js +2 -1
  48. package/dist/portfolio/PortfolioSyncManager.d.ts +127 -0
  49. package/dist/portfolio/PortfolioSyncManager.d.ts.map +1 -0
  50. package/dist/portfolio/PortfolioSyncManager.js +818 -0
  51. package/dist/scripts/scripts/run-config-wizard.js +57 -0
  52. package/dist/scripts/src/config/ConfigManager.js +799 -0
  53. package/dist/scripts/src/config/ConfigWizard.js +368 -0
  54. package/dist/scripts/src/errors/SecurityError.js +47 -0
  55. package/dist/scripts/src/security/constants.js +28 -0
  56. package/dist/scripts/src/security/contentValidator.js +415 -0
  57. package/dist/scripts/src/security/errors.js +32 -0
  58. package/dist/scripts/src/security/regexValidator.js +217 -0
  59. package/dist/scripts/src/security/secureYamlParser.js +272 -0
  60. package/dist/scripts/src/security/securityMonitor.js +111 -0
  61. package/dist/scripts/src/security/validators/unicodeValidator.js +315 -0
  62. package/dist/scripts/src/utils/logger.js +288 -0
  63. package/dist/security/audit/config/suppressions.d.ts.map +1 -1
  64. package/dist/security/audit/config/suppressions.js +54 -2
  65. package/dist/security/secureYamlParser.d.ts +46 -2
  66. package/dist/security/secureYamlParser.d.ts.map +1 -1
  67. package/dist/security/secureYamlParser.js +47 -3
  68. package/dist/server/ServerSetup.d.ts.map +1 -1
  69. package/dist/server/ServerSetup.js +16 -10
  70. package/dist/server/tools/ConfigToolsV2.d.ts +10 -0
  71. package/dist/server/tools/ConfigToolsV2.d.ts.map +1 -0
  72. package/dist/server/tools/ConfigToolsV2.js +110 -0
  73. package/dist/server/types.d.ts +2 -0
  74. package/dist/server/types.d.ts.map +1 -1
  75. package/dist/server/types.js +1 -1
  76. package/dist/sync/PortfolioDownloader.d.ts +27 -0
  77. package/dist/sync/PortfolioDownloader.d.ts.map +1 -0
  78. package/dist/sync/PortfolioDownloader.js +120 -0
  79. package/dist/sync/PortfolioSyncComparer.d.ts +50 -0
  80. package/dist/sync/PortfolioSyncComparer.d.ts.map +1 -0
  81. package/dist/sync/PortfolioSyncComparer.js +158 -0
  82. package/dist/tools/getWelcomeMessage.d.ts +41 -0
  83. package/dist/tools/getWelcomeMessage.d.ts.map +1 -0
  84. package/dist/tools/getWelcomeMessage.js +109 -0
  85. package/dist/utils/TemplateRenderer.d.ts +63 -0
  86. package/dist/utils/TemplateRenderer.d.ts.map +1 -0
  87. package/dist/utils/TemplateRenderer.js +154 -0
  88. package/package.json +1 -1
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ /**
3
+ * Content Validator for DollhouseMCP
4
+ *
5
+ * Protects against prompt injection attacks in collection personas
6
+ * by detecting and sanitizing malicious content patterns.
7
+ *
8
+ * Security: SEC-001 - Critical vulnerability protection
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.ContentValidator = void 0;
12
+ const errors_js_1 = require("./errors.js");
13
+ const securityMonitor_js_1 = require("./securityMonitor.js");
14
+ const regexValidator_js_1 = require("./regexValidator.js");
15
+ const constants_js_1 = require("./constants.js");
16
+ const unicodeValidator_js_1 = require("./validators/unicodeValidator.js");
17
+ class ContentValidator {
18
+ /**
19
+ * Validates and sanitizes persona content for security threats
20
+ */
21
+ static validateAndSanitize(content) {
22
+ // Length validation before pattern matching
23
+ if (content.length > constants_js_1.SECURITY_LIMITS.MAX_CONTENT_LENGTH) {
24
+ throw new errors_js_1.SecurityError(`Content exceeds maximum length of ${constants_js_1.SECURITY_LIMITS.MAX_CONTENT_LENGTH} characters (${content.length} provided)`);
25
+ }
26
+ const detectedPatterns = [];
27
+ let sanitized = content;
28
+ let highestSeverity = 'low';
29
+ // Unicode normalization preprocessing to prevent bypass attacks
30
+ const unicodeResult = unicodeValidator_js_1.UnicodeValidator.normalize(sanitized);
31
+ sanitized = unicodeResult.normalizedContent;
32
+ if (!unicodeResult.isValid && unicodeResult.detectedIssues) {
33
+ detectedPatterns.push(...unicodeResult.detectedIssues.map(issue => `Unicode: ${issue}`));
34
+ if (unicodeResult.severity) {
35
+ highestSeverity = unicodeResult.severity;
36
+ }
37
+ }
38
+ // Check for injection patterns
39
+ for (const { pattern, severity, description } of this.INJECTION_PATTERNS) {
40
+ // These are trusted internal patterns, so we disable ReDoS rejection
41
+ if (regexValidator_js_1.RegexValidator.validate(content, pattern, {
42
+ maxLength: 50000,
43
+ rejectDangerousPatterns: false,
44
+ logEvents: false // Don't log our own security patterns as dangerous
45
+ })) {
46
+ detectedPatterns.push(description);
47
+ // Update highest severity
48
+ if (severity === 'critical' || (severity === 'high' && highestSeverity !== 'critical')) {
49
+ highestSeverity = severity;
50
+ }
51
+ // Log security event
52
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
53
+ type: 'CONTENT_INJECTION_ATTEMPT',
54
+ severity: severity.toUpperCase(),
55
+ source: 'content_validation',
56
+ details: `Detected pattern: ${description}`,
57
+ });
58
+ // Sanitize by replacing with safe placeholder
59
+ sanitized = sanitized.replace(pattern, '[CONTENT_BLOCKED]');
60
+ }
61
+ }
62
+ return {
63
+ isValid: detectedPatterns.length === 0,
64
+ sanitizedContent: sanitized,
65
+ detectedPatterns,
66
+ severity: highestSeverity
67
+ };
68
+ }
69
+ /**
70
+ * Validates YAML frontmatter for malicious content
71
+ * SECURITY FIX #364: Added YAML bomb detection to prevent denial of service
72
+ */
73
+ static validateYamlContent(yamlContent) {
74
+ // Length validation before pattern matching
75
+ if (yamlContent.length > constants_js_1.SECURITY_LIMITS.MAX_YAML_LENGTH) {
76
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
77
+ type: 'YAML_INJECTION_ATTEMPT',
78
+ severity: 'HIGH',
79
+ source: 'yaml_validation',
80
+ details: `YAML content exceeds maximum length: ${yamlContent.length} > ${constants_js_1.SECURITY_LIMITS.MAX_YAML_LENGTH}`
81
+ });
82
+ return false;
83
+ }
84
+ // SECURITY FIX #364: Check for YAML bombs before other validation
85
+ // SECURITY FIX (PR #552 review): Use RegexValidator for ReDoS protection
86
+ for (const pattern of this.YAML_BOMB_PATTERNS) {
87
+ // Use RegexValidator to safely check patterns with timeout protection
88
+ // This prevents ReDoS attacks from maliciously crafted YAML
89
+ const isMatch = regexValidator_js_1.RegexValidator.validate(yamlContent, pattern, {
90
+ maxLength: constants_js_1.SECURITY_LIMITS.MAX_YAML_LENGTH,
91
+ rejectDangerousPatterns: false, // Our patterns are trusted
92
+ logEvents: false // We handle logging ourselves
93
+ });
94
+ if (isMatch) {
95
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
96
+ type: 'YAML_INJECTION_ATTEMPT',
97
+ severity: 'CRITICAL',
98
+ source: 'yaml_bomb_detection',
99
+ details: `YAML bomb pattern detected: ${pattern.source}`,
100
+ metadata: {
101
+ patternType: 'YAML_BOMB',
102
+ contentLength: yamlContent.length
103
+ }
104
+ });
105
+ return false;
106
+ }
107
+ }
108
+ // SECURITY FIX #364: Count anchor/alias ratio for amplification detection
109
+ const anchorMatches = yamlContent.match(/&\w+/g) || [];
110
+ const aliasMatches = yamlContent.match(/\*\w+/g) || [];
111
+ const amplificationRatio = anchorMatches.length > 0 ? aliasMatches.length / anchorMatches.length : 0;
112
+ if (amplificationRatio > 10) { // More than 10 aliases per anchor is suspicious
113
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
114
+ type: 'YAML_INJECTION_ATTEMPT',
115
+ severity: 'HIGH',
116
+ source: 'yaml_amplification_detection',
117
+ details: `Excessive alias amplification detected: ${aliasMatches.length} aliases for ${anchorMatches.length} anchors (ratio: ${amplificationRatio.toFixed(2)})`,
118
+ metadata: {
119
+ anchors: anchorMatches.length,
120
+ aliases: aliasMatches.length,
121
+ ratio: amplificationRatio
122
+ }
123
+ });
124
+ return false;
125
+ }
126
+ // SECURITY FIX #364: Detect circular reference chains
127
+ // SECURITY FIX (PR #552 review): Optimized from O(n²) to O(n) using Set-based lookups
128
+ const anchorRefs = new Map();
129
+ const lines = yamlContent.split('\n');
130
+ // First pass: Build reference map efficiently
131
+ for (let i = 0; i < lines.length; i++) {
132
+ const anchorMatch = lines[i].match(/&(\w+)/);
133
+ if (anchorMatch) {
134
+ const anchorName = anchorMatch[1];
135
+ // Get references in next 5 lines
136
+ const contextEnd = Math.min(i + 5, lines.length);
137
+ const references = new Set();
138
+ for (let j = i; j < contextEnd; j++) {
139
+ const aliasMatches = lines[j].match(/\*(\w+)/g);
140
+ if (aliasMatches) {
141
+ aliasMatches.forEach(alias => {
142
+ references.add(alias.substring(1)); // Remove * prefix
143
+ });
144
+ }
145
+ }
146
+ anchorRefs.set(anchorName, references);
147
+ }
148
+ }
149
+ // Second pass: Check for circular references (O(n) with Set lookups)
150
+ for (const [anchor1, refs1] of anchorRefs) {
151
+ for (const refAnchor of refs1) {
152
+ const refs2 = anchorRefs.get(refAnchor);
153
+ // Check if the referenced anchor references back to the original
154
+ if (refs2 && refs2.has(anchor1)) {
155
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
156
+ type: 'YAML_INJECTION_ATTEMPT',
157
+ severity: 'CRITICAL',
158
+ source: 'yaml_bomb_detection',
159
+ details: `Circular reference chain detected between anchors: &${anchor1} and &${refAnchor}`,
160
+ metadata: {
161
+ patternType: 'CIRCULAR_REFERENCE',
162
+ anchors: [anchor1, refAnchor]
163
+ }
164
+ });
165
+ return false;
166
+ }
167
+ }
168
+ }
169
+ // Unicode normalization preprocessing for YAML content
170
+ const unicodeResult = unicodeValidator_js_1.UnicodeValidator.normalize(yamlContent);
171
+ const normalizedYaml = unicodeResult.normalizedContent;
172
+ if (!unicodeResult.isValid && unicodeResult.detectedIssues) {
173
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
174
+ type: 'YAML_UNICODE_ATTACK',
175
+ severity: (unicodeResult.severity?.toUpperCase() || 'MEDIUM'),
176
+ source: 'yaml_validation',
177
+ details: `Unicode attack detected in YAML: ${unicodeResult.detectedIssues.join(', ')}`
178
+ });
179
+ return false;
180
+ }
181
+ for (const pattern of this.MALICIOUS_YAML_PATTERNS) {
182
+ // These are trusted internal patterns, so we disable ReDoS rejection
183
+ if (regexValidator_js_1.RegexValidator.validate(normalizedYaml, pattern, {
184
+ maxLength: 10000,
185
+ rejectDangerousPatterns: false,
186
+ logEvents: false // Don't log our own security patterns as dangerous
187
+ })) {
188
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
189
+ type: 'YAML_INJECTION_ATTEMPT',
190
+ severity: 'CRITICAL',
191
+ source: 'yaml_validation',
192
+ details: `Malicious YAML pattern detected: ${pattern}`,
193
+ });
194
+ // Early exit on first match for performance
195
+ return false;
196
+ }
197
+ }
198
+ return true;
199
+ }
200
+ /**
201
+ * Validates persona metadata fields
202
+ */
203
+ static validateMetadata(metadata) {
204
+ const detectedPatterns = [];
205
+ // Check all string fields in metadata
206
+ const checkField = (fieldName, value) => {
207
+ if (typeof value === 'string') {
208
+ // Check field length first
209
+ if (value.length > constants_js_1.SECURITY_LIMITS.MAX_METADATA_FIELD_LENGTH) {
210
+ detectedPatterns.push(`${fieldName}: Field exceeds maximum length of ${constants_js_1.SECURITY_LIMITS.MAX_METADATA_FIELD_LENGTH} characters`);
211
+ return;
212
+ }
213
+ const result = this.validateAndSanitize(value);
214
+ if (!result.isValid || result.detectedPatterns?.length) {
215
+ detectedPatterns.push(`${fieldName}: ${result.detectedPatterns?.join(', ')}`);
216
+ }
217
+ }
218
+ };
219
+ // Validate standard persona fields
220
+ checkField('name', metadata.name);
221
+ checkField('description', metadata.description);
222
+ checkField('category', metadata.category);
223
+ checkField('author', metadata.author);
224
+ // Check any custom fields
225
+ for (const [key, value] of Object.entries(metadata)) {
226
+ if (!['name', 'description', 'category', 'author'].includes(key)) {
227
+ checkField(key, value);
228
+ }
229
+ }
230
+ return {
231
+ isValid: detectedPatterns.length === 0,
232
+ detectedPatterns,
233
+ severity: detectedPatterns.length > 0 ? 'high' : 'low'
234
+ };
235
+ }
236
+ /**
237
+ * Sanitizes a complete persona file (frontmatter + content)
238
+ */
239
+ static sanitizePersonaContent(content) {
240
+ // Extract frontmatter
241
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
242
+ if (!frontmatterMatch) {
243
+ // No frontmatter, just validate content
244
+ const result = this.validateAndSanitize(content);
245
+ if (!result.isValid && result.severity === 'critical') {
246
+ // FIX: Include specific patterns that triggered the rejection for debugging
247
+ const patterns = result.detectedPatterns?.join(', ') || 'unknown patterns';
248
+ throw new errors_js_1.SecurityError(`Critical security threat detected in persona content: ${patterns}`);
249
+ }
250
+ return result.sanitizedContent || content;
251
+ }
252
+ const yamlContent = frontmatterMatch[1];
253
+ const markdownContent = content.substring(frontmatterMatch[0].length);
254
+ // Validate YAML
255
+ if (!this.validateYamlContent(yamlContent)) {
256
+ throw new errors_js_1.SecurityError('Malicious YAML detected in persona frontmatter');
257
+ }
258
+ // Validate markdown content
259
+ const contentResult = this.validateAndSanitize(markdownContent);
260
+ if (!contentResult.isValid && contentResult.severity === 'critical') {
261
+ // FIX: Include specific patterns that triggered the rejection for debugging
262
+ const patterns = contentResult.detectedPatterns?.join(', ') || 'unknown patterns';
263
+ throw new errors_js_1.SecurityError(`Critical security threat detected in persona content: ${patterns}`);
264
+ }
265
+ // Return sanitized content
266
+ return `---\n${yamlContent}\n---${contentResult.sanitizedContent || markdownContent}`;
267
+ }
268
+ }
269
+ exports.ContentValidator = ContentValidator;
270
+ /**
271
+ * Pattern-based detection system for prompt injection attacks.
272
+ *
273
+ * This approach was chosen over AI-based detection because:
274
+ * 1. Pattern matching cannot be socially engineered or confused
275
+ * 2. Deterministic results ensure consistent security
276
+ * 3. No additional API calls or latency
277
+ * 4. Can't be bypassed by clever prompt engineering
278
+ *
279
+ * The patterns below represent known attack vectors from security research
280
+ * and real-world exploit attempts against AI systems.
281
+ */
282
+ // Prompt injection patterns that could compromise AI assistants
283
+ ContentValidator.INJECTION_PATTERNS = [
284
+ // System prompt override attempts
285
+ { pattern: /\[SYSTEM:\s*.*?\]/gi, severity: 'critical', description: 'System prompt override' },
286
+ { pattern: /\[ADMIN:\s*.*?\]/gi, severity: 'critical', description: 'Admin prompt override' },
287
+ { pattern: /\[ASSISTANT:\s*.*?\]/gi, severity: 'critical', description: 'Assistant prompt override' },
288
+ { pattern: /\[USER:\s*.*?\]/gi, severity: 'high', description: 'User prompt override' },
289
+ // Instruction manipulation
290
+ { pattern: /ignore\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
291
+ { pattern: /ignore\s+(all\s+)?prior\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
292
+ { pattern: /disregard\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
293
+ { pattern: /forget\s+(all\s+)?previous\s+instructions/gi, severity: 'critical', description: 'Instruction override' },
294
+ { pattern: /you\s+are\s+now\s+(admin|root|system|sudo)/gi, severity: 'critical', description: 'Role elevation attempt' },
295
+ { pattern: /act\s+as\s+(admin|root|system|sudo)/gi, severity: 'critical', description: 'Role elevation attempt' },
296
+ // Data exfiltration attempts
297
+ { pattern: /export\s+all\s+(files|data|personas|tokens|credentials)/gi, severity: 'critical', description: 'Data exfiltration' },
298
+ { pattern: /send\s+all\s+(files|data|personas|tokens|credentials)\s+to/gi, severity: 'critical', description: 'Data exfiltration' },
299
+ { pattern: /list\s+all\s+(files|tokens|credentials|secrets)/gi, severity: 'high', description: 'Information disclosure' },
300
+ { pattern: /show\s+me\s+all\s+(tokens|credentials|secrets|api\s+keys)/gi, severity: 'high', description: 'Credential disclosure' },
301
+ // Command execution patterns
302
+ { pattern: /curl\s+[^\s]+\.(com|net|org|io|dev)/gi, severity: 'critical', description: 'External command execution' },
303
+ { pattern: /wget\s+[^\s]+\.(com|net|org|io|dev)/gi, severity: 'critical', description: 'External command execution' },
304
+ { pattern: /\$\([^)]+\)/g, severity: 'critical', description: 'Command substitution' },
305
+ // SECURITY: More refined backtick pattern - distinguishes between dangerous commands and documentation
306
+ // Only block truly dangerous shell commands, not educational examples
307
+ { pattern: /`[^`]*(?:rm\s+-r[f]?|cat\s+\/etc\/|ls\s+\/etc\/|chmod\s+777|chown\s+root|bash\s+-c\s+[\"']|sh\s+-c\s+[\"']|sudo\s+rm|sudo\s+chmod|passwd\s+|shadow\s+|nc\s+-l|netcat\s+-l|ssh\s+root@)[^`]*`/gi, severity: 'critical', description: 'Dangerous shell command in backticks' },
308
+ // Block actual malicious file operations and network commands with pipes/redirects
309
+ { pattern: /`[^`]*(?:rm\s+-rf\s+\/|\/etc\/passwd|\/etc\/shadow|\.ssh\/id_|sudo\s+su|>\s*\/dev\/null.*\||curl\s+.*\|\s*sh|wget\s+.*\|\s*bash|bash\s+.*\.sh\s*\|)[^`]*`/gi, severity: 'critical', description: 'Malicious backtick command' },
310
+ // Only block actual script execution, not documentation showing syntax
311
+ { pattern: /`[^`]*(?:python|perl|ruby|php|node)\s+(?:-e|-c)\s+[\"'](?:import\s+os|exec|eval|system|subprocess)[^`]+`/gi, severity: 'critical', description: 'Malicious script evaluation in backticks' },
312
+ { pattern: /eval\s*\(/gi, severity: 'critical', description: 'Code evaluation' },
313
+ { pattern: /exec\s*\(/gi, severity: 'critical', description: 'Code execution' },
314
+ { pattern: /os\.system\s*\(/gi, severity: 'critical', description: 'System command execution' },
315
+ { pattern: /subprocess\.(call|run|Popen)/gi, severity: 'critical', description: 'Subprocess execution' },
316
+ // Token/credential patterns
317
+ { pattern: /GITHUB_TOKEN/gi, severity: 'high', description: 'Token reference' },
318
+ { pattern: /ghp_[a-zA-Z0-9]{36}/g, severity: 'critical', description: 'GitHub token exposure' },
319
+ { pattern: /gho_[a-zA-Z0-9]{36}/g, severity: 'critical', description: 'GitHub OAuth token exposure' },
320
+ // Path traversal in content
321
+ { pattern: /\.\.\/\.\.\/\.\.\//g, severity: 'high', description: 'Path traversal attempt' },
322
+ { pattern: /\/etc\/passwd/gi, severity: 'high', description: 'Sensitive file access' },
323
+ { pattern: /\/\.ssh\//gi, severity: 'high', description: 'SSH key access attempt' },
324
+ ];
325
+ // Malicious YAML patterns
326
+ // SECURITY FIX #364: YAML bomb detection patterns
327
+ // SECURITY FIX (PR #552 review): Simplified patterns to reduce ReDoS risk
328
+ ContentValidator.YAML_BOMB_PATTERNS = [
329
+ // Detects recursive anchor references that could cause exponential expansion
330
+ // Example: &a [*a] or &bomb ["test", *bomb]
331
+ /&(\w+)\s*\[[^\]]*\*\1[^\]]*\]/, // Direct recursion in array
332
+ /&(\w+)\s*\{[^}]*\*\1[^}]*\}/, // Direct recursion in object
333
+ /^\s*\w+:\s*&(\w+)\s*\n\s*\w+:\s*\*\1/m, // Multi-line value recursion (data: &ref / value: *ref)
334
+ // Simplified pattern to detect deeply nested anchors (less ReDoS risk)
335
+ // Looks for 3+ anchor definitions in close proximity
336
+ /&\w+[^&]*&\w+[^&]*&\w+/, // 3+ anchors (simplified, less backtracking)
337
+ // Detects excessive aliases in close proximity (potential amplification)
338
+ // Example: [*a, *b, *c, *d, *e, *f, *g, *h, *i, *j]
339
+ /\*\w+(?:[,\s]+\*\w+){9,}/, // 10+ aliases in sequence (non-capturing group)
340
+ ];
341
+ ContentValidator.MALICIOUS_YAML_PATTERNS = [
342
+ // Language-specific deserialization attacks
343
+ /!!python\/object/,
344
+ /!!python\/module/,
345
+ /!!python\/name/,
346
+ /!!ruby\/object/,
347
+ /!!ruby\/hash/,
348
+ /!!ruby\/struct/,
349
+ /!!ruby\/marshal/,
350
+ /!!java/,
351
+ /!!javax/,
352
+ /!!com\.sun/,
353
+ /!!perl\/hash/,
354
+ /!!perl\/code/,
355
+ /!!php\/object/,
356
+ // Constructor/function injection
357
+ /!!exec/,
358
+ /!!eval/,
359
+ /!!new/,
360
+ /!!construct/,
361
+ /!!apply/,
362
+ /!!call/,
363
+ /!!invoke/,
364
+ // Code execution patterns - more specific to avoid false positives
365
+ /subprocess\./,
366
+ /os\.system/,
367
+ /eval\s*\(/,
368
+ /exec\s*\(/,
369
+ /__import__\s*\(/,
370
+ /require\s*\(/,
371
+ /import\s+(?:os|sys|subprocess|eval|exec)/,
372
+ /include\s+["'].*\.(?:php|sh|py|js|rb)["']/,
373
+ // Command execution variants - more specific patterns
374
+ /popen\s*\(/,
375
+ /spawn\s*\(/,
376
+ /system\s*\(/,
377
+ /backtick\s*\(/,
378
+ /shell_exec\s*\(/,
379
+ /passthru\s*\(/,
380
+ /proc_open\s*\(/,
381
+ // Network operations - require suspicious context
382
+ /socket\.connect/, // Detects socket connection attempts
383
+ /urllib\.request/, // Python HTTP library usage
384
+ /requests\.(?:get|post|put|delete)\s*\(/, // Detects HTTP requests with method calls
385
+ /fetch\s*\(\s*["']https?:\/\//, // Detects fetch calls to external URLs
386
+ /new\s+XMLHttpRequest/, // JavaScript AJAX object creation
387
+ /\.(?:get|post|put|delete)\s*\(\s*["']https?:\/\//, // Method chaining with HTTP requests
388
+ // File system operations - require suspicious context
389
+ /(?:fs\.|file\.|)\s*open\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // File open with suspicious paths
390
+ /file_get_contents\s*\(/, // PHP file reading function
391
+ /file_put_contents\s*\(/, // PHP file writing function
392
+ /fopen\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // File open with dangerous system paths
393
+ /(?:fs\.)?\s*readFile\s*\(\s*["'](?:\/etc\/|\/bin\/|\.\.\/)/, // Node.js file read with path traversal
394
+ /(?:fs\.)?\s*writeFile\s*\(\s*["'](?:\/(?:bin|etc|tmp)\/|\.\.\/)/, // Node.js file write to system dirs
395
+ // Protocol handlers
396
+ /file:\/\//,
397
+ /data:\/\//,
398
+ /expect:\/\//,
399
+ /php:\/\//,
400
+ /phar:\/\//,
401
+ /zip:\/\//,
402
+ /ssh2:\/\//,
403
+ /ogg:\/\//,
404
+ // YAML-specific dangerous features
405
+ /&[a-zA-Z0-9_]+\s*!!/, // Anchor with tag combination
406
+ /\*[a-zA-Z0-9_]+\s*!!/, // Alias with tag combination
407
+ /!!merge/,
408
+ /!!binary/,
409
+ /!!timestamp/,
410
+ // Unicode/encoding bypass attempts - prevent visual spoofing attacks
411
+ /\\[uU]0*(?:22|27|60|3[cC])/, // Unicode escapes for quotes (") and brackets (<>)
412
+ /[\u202A-\u202E\u2066-\u2069]/, // Direction override chars (RLO, LRO, isolates)
413
+ /[\u200B-\u200F\u2028-\u202F]/, // Zero-width spaces, line/paragraph separators
414
+ /[\uFEFF\uFFFE\uFFFF]/, // BOM, non-characters for payload hiding
415
+ ];
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ /**
3
+ * Security-related error classes
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TimeoutError = exports.ValidationError = exports.SecurityError = void 0;
7
+ class SecurityError extends Error {
8
+ constructor(message, code) {
9
+ super(message);
10
+ this.name = 'SecurityError';
11
+ this.code = code;
12
+ // Maintains proper prototype chain for instanceof checks
13
+ Object.setPrototypeOf(this, SecurityError.prototype);
14
+ }
15
+ }
16
+ exports.SecurityError = SecurityError;
17
+ class ValidationError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'ValidationError';
21
+ Object.setPrototypeOf(this, ValidationError.prototype);
22
+ }
23
+ }
24
+ exports.ValidationError = ValidationError;
25
+ class TimeoutError extends SecurityError {
26
+ constructor(message = 'Operation timed out') {
27
+ super(message);
28
+ this.name = 'TimeoutError';
29
+ Object.setPrototypeOf(this, TimeoutError.prototype);
30
+ }
31
+ }
32
+ exports.TimeoutError = TimeoutError;
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ /**
3
+ * RegexValidator - Provides protection against ReDoS attacks
4
+ *
5
+ * This module implements safe regex execution by:
6
+ * 1. Pre-validating content length based on pattern complexity
7
+ * 2. Analyzing patterns for known ReDoS vulnerabilities
8
+ * 3. Limiting execution based on calculated risk
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.RegexValidator = void 0;
12
+ const errors_js_1 = require("./errors.js");
13
+ const securityMonitor_js_1 = require("./securityMonitor.js");
14
+ class RegexValidator {
15
+ /**
16
+ * Validates content against a pattern with ReDoS protection
17
+ *
18
+ * Protection strategy:
19
+ * 1. Analyze pattern complexity
20
+ * 2. Enforce content length limits based on complexity
21
+ * 3. Reject known dangerous patterns
22
+ * 4. Execute regex only if safe
23
+ */
24
+ static validate(content, pattern, options = {}) {
25
+ const { maxLength, rejectDangerousPatterns = true, logEvents = true } = options;
26
+ // Analyze pattern for ReDoS risks
27
+ const analysis = this.analyzePattern(pattern);
28
+ // Reject dangerous patterns if configured
29
+ if (rejectDangerousPatterns && !analysis.safe) {
30
+ if (logEvents) {
31
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
32
+ type: 'UPDATE_SECURITY_VIOLATION',
33
+ severity: 'HIGH',
34
+ source: 'RegexValidator',
35
+ details: 'Dangerous regex pattern rejected',
36
+ additionalData: {
37
+ pattern: pattern.source,
38
+ risks: analysis.risks
39
+ }
40
+ });
41
+ }
42
+ throw new errors_js_1.SecurityError(`Pattern rejected due to ReDoS risk: ${analysis.risks.join(', ')}`);
43
+ }
44
+ // Determine effective max length
45
+ const effectiveMaxLength = maxLength ?? analysis.maxSafeLength;
46
+ // Check content length
47
+ if (content.length > effectiveMaxLength) {
48
+ throw new errors_js_1.SecurityError(`Content too large for validation: ${content.length} bytes (max: ${effectiveMaxLength} for ${analysis.complexity} complexity pattern)`);
49
+ }
50
+ // Create a copy of the regex to avoid modifying the original
51
+ const safeCopy = new RegExp(pattern.source, pattern.flags);
52
+ try {
53
+ // Track execution time for monitoring
54
+ const startTime = performance.now();
55
+ const result = safeCopy.test(content);
56
+ const elapsed = performance.now() - startTime;
57
+ // Log slow patterns
58
+ if (elapsed > 50 && logEvents) {
59
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
60
+ type: 'RATE_LIMIT_WARNING',
61
+ severity: 'MEDIUM',
62
+ source: 'RegexValidator',
63
+ details: `Slow regex execution: ${elapsed.toFixed(2)}ms`,
64
+ additionalData: {
65
+ pattern: pattern.source,
66
+ contentLength: content.length,
67
+ elapsed
68
+ }
69
+ });
70
+ }
71
+ return result;
72
+ }
73
+ catch (error) {
74
+ // Handle any regex errors
75
+ if (logEvents) {
76
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
77
+ type: 'UPDATE_SECURITY_VIOLATION',
78
+ severity: 'HIGH',
79
+ source: 'RegexValidator',
80
+ details: 'Regex execution error',
81
+ additionalData: {
82
+ error: error instanceof Error ? error.message : 'Unknown error'
83
+ }
84
+ });
85
+ }
86
+ return false;
87
+ }
88
+ }
89
+ /**
90
+ * Validates multiple patterns with shared risk assessment
91
+ */
92
+ static validateAny(content, patterns, options = {}) {
93
+ for (const pattern of patterns) {
94
+ if (this.validate(content, pattern, options)) {
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ /**
101
+ * Validates all patterns must match
102
+ */
103
+ static validateAll(content, patterns, options = {}) {
104
+ for (const pattern of patterns) {
105
+ if (!this.validate(content, pattern, options)) {
106
+ return false;
107
+ }
108
+ }
109
+ return true;
110
+ }
111
+ /**
112
+ * Analyzes a regex pattern for potential ReDoS vulnerabilities
113
+ *
114
+ * Detects patterns known to cause exponential backtracking:
115
+ * - Nested quantifiers: (a+)+, (a*)*
116
+ * - Alternation with overlap: (a|a)*
117
+ * - Quantified groups with alternation: (a|b)+
118
+ * - Catastrophic patterns: (.+)+$
119
+ */
120
+ static analyzePattern(pattern) {
121
+ const source = pattern.source;
122
+ const risks = [];
123
+ // Nested quantifiers - extremely dangerous
124
+ if (/\([^)]+[+*]\)[+*]/.test(source) ||
125
+ /\([^)]+\{[^}]+\}\)[+*]/.test(source) ||
126
+ /\(\w+[+*]\)[+*]/.test(source)) {
127
+ risks.push('Nested quantifiers detected');
128
+ }
129
+ // Alternation with repetition
130
+ if (/\([^)]*\|[^)]*\)[+*]/.test(source)) {
131
+ risks.push('Quantified alternation detected');
132
+ }
133
+ // Alternation with overlap (e.g., (a|a)*)
134
+ const alternationMatch = source.match(/\(([^|)]+)\|([^)]+)\)/g);
135
+ if (alternationMatch) {
136
+ for (const match of alternationMatch) {
137
+ const parts = match.slice(1, -1).split('|');
138
+ if (parts.some((part, i) => parts.slice(i + 1).includes(part))) {
139
+ risks.push('Overlapping alternation detected');
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ // Catastrophic backtracking patterns
145
+ // Check for patterns like (.+)+, (.*)+, etc. that can cause exponential backtracking
146
+ if (/\([^)]*\.\+[^)]*\)\+/.test(source) || /\([^)]*\.\*[^)]*\)\+/.test(source) || /\([^)]*\\w\+[^)]*\)\+/.test(source)) {
147
+ risks.push('Potential catastrophic backtracking');
148
+ }
149
+ // Unbounded lookahead/lookbehind with quantifiers
150
+ if (/\(\?[=!<].*[+*]/.test(source)) {
151
+ risks.push('Unbounded lookahead/lookbehind');
152
+ }
153
+ // Polynomial patterns (multiple quantifiers in sequence)
154
+ const quantifierCount = (source.match(/[+*?]|\{\d*,?\d*\}/g) || []).length;
155
+ if (quantifierCount > 3) {
156
+ risks.push('Multiple quantifiers detected');
157
+ }
158
+ // Determine complexity and safe content length
159
+ let complexity;
160
+ let maxSafeLength;
161
+ if (risks.length === 0) {
162
+ if (quantifierCount === 0) {
163
+ complexity = 'low';
164
+ maxSafeLength = this.COMPLEXITY_LIMITS.low;
165
+ }
166
+ else if (quantifierCount <= 3) {
167
+ complexity = 'medium';
168
+ maxSafeLength = this.COMPLEXITY_LIMITS.medium;
169
+ }
170
+ else {
171
+ complexity = 'high';
172
+ maxSafeLength = this.COMPLEXITY_LIMITS.high;
173
+ }
174
+ }
175
+ else if (risks.length === 1) {
176
+ complexity = 'high';
177
+ maxSafeLength = this.COMPLEXITY_LIMITS.high;
178
+ }
179
+ else {
180
+ complexity = 'high';
181
+ maxSafeLength = this.COMPLEXITY_LIMITS.high;
182
+ }
183
+ return {
184
+ safe: risks.length === 0,
185
+ risks,
186
+ complexity,
187
+ maxSafeLength
188
+ };
189
+ }
190
+ /**
191
+ * Creates a regex pattern with safety analysis
192
+ */
193
+ static createSafePattern(pattern, flags) {
194
+ const regex = new RegExp(pattern, flags);
195
+ const analysis = this.analyzePattern(regex);
196
+ if (!analysis.safe) {
197
+ securityMonitor_js_1.SecurityMonitor.logSecurityEvent({
198
+ type: 'UPDATE_SECURITY_VIOLATION',
199
+ severity: 'MEDIUM',
200
+ source: 'RegexValidator',
201
+ details: 'Potentially dangerous regex pattern created',
202
+ additionalData: {
203
+ pattern,
204
+ risks: analysis.risks
205
+ }
206
+ });
207
+ }
208
+ return regex;
209
+ }
210
+ }
211
+ exports.RegexValidator = RegexValidator;
212
+ // Default limits based on pattern complexity
213
+ RegexValidator.COMPLEXITY_LIMITS = {
214
+ low: 100000, // 100KB for simple patterns
215
+ medium: 10000, // 10KB for moderate patterns
216
+ high: 1000 // 1KB for complex patterns
217
+ };