@dynamicu/chromedebug-mcp 2.2.0

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 (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Log Transformation Utility
3
+ * Transforms Chrome extension logs to match associateLogsSchema compliance
4
+ *
5
+ * CONTRACT A: associateLogsSchema requires only {level, message, timestamp, args}
6
+ * CONTRACT B: Remove non-schema fields while preserving all valid data
7
+ */
8
+
9
+ /**
10
+ * Transform logs to match associateLogsSchema format
11
+ * Filters out non-schema fields like 'source', 'type' while preserving valid fields
12
+ *
13
+ * @param {Array} logs - Array of log objects from Chrome extension
14
+ * @returns {Array} - Transformed logs matching associateLogsSchema
15
+ */
16
+ export function transformLogsForSchema(logs) {
17
+ if (!Array.isArray(logs)) {
18
+ return logs;
19
+ }
20
+
21
+ return logs.map(log => {
22
+ // Handle null/undefined/non-object logs gracefully
23
+ if (!log || typeof log !== 'object') {
24
+ return log; // Return as-is, let schema validation handle it
25
+ }
26
+
27
+ // Only include fields that are allowed by associateLogsSchema
28
+ const transformedLog = {};
29
+
30
+ // Required fields
31
+ if (log.level !== undefined) {
32
+ transformedLog.level = log.level;
33
+ }
34
+ if (log.message !== undefined) {
35
+ transformedLog.message = log.message;
36
+ }
37
+ if (log.timestamp !== undefined) {
38
+ transformedLog.timestamp = log.timestamp;
39
+ }
40
+
41
+ // Optional fields
42
+ if (log.args !== undefined) {
43
+ transformedLog.args = log.args;
44
+ }
45
+
46
+ // Explicitly exclude non-schema fields:
47
+ // - source (from Chrome extension test harness)
48
+ // - type (from Chrome extension)
49
+ // - any other fields not in schema
50
+
51
+ return transformedLog;
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Transform full associate-logs request payload
57
+ * Preserves sessionId and transforms logs array
58
+ *
59
+ * @param {Object} requestPayload - Request with sessionId and logs
60
+ * @returns {Object} - Transformed payload ready for schema validation
61
+ */
62
+ export function transformAssociateLogsRequest(requestPayload) {
63
+ if (!requestPayload || typeof requestPayload !== 'object') {
64
+ return requestPayload;
65
+ }
66
+
67
+ const transformed = { ...requestPayload };
68
+
69
+ if (transformed.logs) {
70
+ transformed.logs = transformLogsForSchema(transformed.logs);
71
+ }
72
+
73
+ return transformed;
74
+ }
75
+
76
+ /**
77
+ * Validation helper - checks if logs contain non-schema fields
78
+ * Used for logging/diagnostics
79
+ *
80
+ * @param {Array} logs - Array of log objects
81
+ * @returns {Object} - Analysis of field compliance
82
+ */
83
+ export function analyzeLogFieldCompliance(logs) {
84
+ if (!Array.isArray(logs)) {
85
+ return { isCompliant: false, reason: 'logs is not an array' };
86
+ }
87
+
88
+ const allowedFields = new Set(['level', 'message', 'timestamp', 'args']);
89
+ const nonSchemaFields = new Set();
90
+ const missingRequiredFields = new Set();
91
+
92
+ for (const log of logs) {
93
+ if (!log || typeof log !== 'object') {
94
+ continue;
95
+ }
96
+
97
+ // Check for non-schema fields
98
+ for (const field of Object.keys(log)) {
99
+ if (!allowedFields.has(field)) {
100
+ nonSchemaFields.add(field);
101
+ }
102
+ }
103
+
104
+ // Check for missing required fields
105
+ const requiredFields = ['level', 'message', 'timestamp'];
106
+ for (const required of requiredFields) {
107
+ if (!(required in log)) {
108
+ missingRequiredFields.add(required);
109
+ }
110
+ }
111
+ }
112
+
113
+ return {
114
+ isCompliant: nonSchemaFields.size === 0 && missingRequiredFields.size === 0,
115
+ nonSchemaFields: Array.from(nonSchemaFields),
116
+ missingRequiredFields: Array.from(missingRequiredFields),
117
+ totalLogs: logs.length
118
+ };
119
+ }
120
+
121
+ export default {
122
+ transformLogsForSchema,
123
+ transformAssociateLogsRequest,
124
+ analyzeLogFieldCompliance
125
+ };
@@ -0,0 +1,391 @@
1
+ import Joi from 'joi';
2
+
3
+ // Common validation patterns
4
+ const patterns = {
5
+ sessionId: Joi.string().pattern(/^[a-zA-Z0-9_-]+$/).min(1).max(100),
6
+ url: Joi.string().uri({ scheme: ['http', 'https'] }).max(2048),
7
+ selector: Joi.string().min(1).max(1000),
8
+ expression: Joi.string().min(1).max(10000),
9
+ recordingId: Joi.string().pattern(/^[a-zA-Z0-9_-]+$/).min(1).max(100),
10
+ frameIndex: Joi.number().integer().min(0).max(999999)
11
+ };
12
+
13
+ // Workflow recording schemas
14
+ export const workflowRecordingSchema = Joi.object({
15
+ sessionId: patterns.sessionId.required(),
16
+ url: patterns.url.optional(),
17
+ title: Joi.string().max(500).optional(),
18
+ includeLogs: Joi.boolean().default(false),
19
+ actions: Joi.array().items(
20
+ Joi.object({
21
+ type: Joi.string().required(),
22
+ timestamp: Joi.number().required(),
23
+ data: Joi.object().optional()
24
+ })
25
+ ).required(),
26
+ logs: Joi.array().items(
27
+ Joi.object({
28
+ level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
29
+ message: Joi.string().required(),
30
+ timestamp: Joi.number().required(),
31
+ args: Joi.array().optional()
32
+ })
33
+ ).optional(),
34
+ functionTraces: Joi.array().items(
35
+ Joi.object({
36
+ type: Joi.string().optional(),
37
+ component: Joi.string().optional(),
38
+ args: Joi.array().optional(),
39
+ timestamp: Joi.number().optional(),
40
+ stack: Joi.string().optional()
41
+ })
42
+ ).optional(),
43
+ name: Joi.any().optional(),
44
+ screenshotSettings: Joi.object({
45
+ width: Joi.number().integer().min(100).max(4000).optional(),
46
+ height: Joi.number().integer().min(100).max(4000).optional(),
47
+ quality: Joi.number().min(10).max(100).optional()
48
+ }).optional()
49
+ });
50
+
51
+ // Frame batch schema
52
+ export const frameBatchSchema = Joi.object({
53
+ sessionId: patterns.sessionId.required(),
54
+ sessionName: Joi.string().max(200).optional().allow(null), // Optional session name for recordings
55
+ frames: Joi.array().items(
56
+ Joi.object({
57
+ timestamp: Joi.number().required(),
58
+ absoluteTimestamp: Joi.number().optional(), // Chrome extension sends this
59
+ imageData: Joi.string().required(), // Base64 encoded image
60
+ consoleLog: Joi.string().optional(),
61
+ frameIndex: Joi.number().integer().min(0).optional(),
62
+ index: Joi.number().integer().min(0).optional(), // Chrome extension sends this
63
+ logs: Joi.array().items(
64
+ Joi.object({
65
+ level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
66
+ message: Joi.string().required(),
67
+ timestamp: Joi.number().required(),
68
+ args: Joi.array().optional()
69
+ })
70
+ ).optional(), // Chrome extension sends this
71
+ interactions: Joi.array().items(
72
+ Joi.object({
73
+ type: Joi.string().valid('click', 'scroll', 'keydown', 'drag').required(),
74
+ timestamp: Joi.number().required(),
75
+ x: Joi.number().when('type', { is: 'click', then: Joi.required(), otherwise: Joi.optional() }),
76
+ y: Joi.number().when('type', { is: 'click', then: Joi.required(), otherwise: Joi.optional() }),
77
+ target: Joi.string().optional(),
78
+ targetId: Joi.string().optional(),
79
+ scrollX: Joi.number().when('type', { is: 'scroll', then: Joi.required(), otherwise: Joi.optional() }),
80
+ scrollY: Joi.number().when('type', { is: 'scroll', then: Joi.required(), otherwise: Joi.optional() }),
81
+ key: Joi.string().when('type', { is: 'keydown', then: Joi.required(), otherwise: Joi.optional() })
82
+ })
83
+ ).optional().default([]) // Interaction tracking data
84
+ })
85
+ ).min(1).max(1000).required()
86
+ });
87
+
88
+ // Associate logs schema
89
+ export const associateLogsSchema = Joi.object({
90
+ sessionId: patterns.sessionId.required(),
91
+ logs: Joi.array().items(
92
+ Joi.object({
93
+ level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
94
+ message: Joi.string().required(),
95
+ timestamp: Joi.number().required(),
96
+ args: Joi.array().optional()
97
+ })
98
+ ).required()
99
+ });
100
+
101
+ // Stream logs schema for batched real-time log streaming
102
+ export const streamLogsSchema = Joi.object({
103
+ sessionId: patterns.sessionId.required(),
104
+ logs: Joi.array().items(
105
+ Joi.object({
106
+ level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count').required(),
107
+ message: Joi.string().required(),
108
+ timestamp: Joi.number().required(),
109
+ sequence: Joi.number().integer().min(0).required(),
110
+ args: Joi.array().optional()
111
+ })
112
+ ).min(1).max(100).required() // Limit batch size
113
+ });
114
+
115
+ // DOM intent schema
116
+ export const domIntentSchema = Joi.object({
117
+ selector: patterns.selector.required(),
118
+ instruction: Joi.string().max(1000).optional().allow(''),
119
+ elementInfo: Joi.object({
120
+ tagName: Joi.string().optional(),
121
+ className: Joi.string().optional(),
122
+ id: Joi.string().optional(),
123
+ textContent: Joi.string().max(1000).optional(),
124
+ attributes: Joi.object().optional()
125
+ }).optional()
126
+ });
127
+
128
+ // Chrome control schemas
129
+ export const navigateSchema = Joi.object({
130
+ url: patterns.url.required()
131
+ });
132
+
133
+ export const evaluateSchema = Joi.object({
134
+ expression: patterns.expression.required()
135
+ });
136
+
137
+ // Screen interactions schema
138
+ export const screenInteractionsSchema = Joi.object({
139
+ interactions: Joi.array().items(
140
+ Joi.object({
141
+ type: Joi.string().valid('click', 'scroll', 'keypress', 'mousemove', 'input', 'change', 'drag').required(),
142
+ timestamp: Joi.number().required(),
143
+ x: Joi.number().optional(),
144
+ y: Joi.number().optional(),
145
+ key: Joi.string().optional(),
146
+ target: Joi.string().optional(),
147
+ // Basic element fields that were previously blocked
148
+ selector: Joi.string().optional(),
149
+ xpath: Joi.string().optional(),
150
+ text: Joi.string().allow('').optional(),
151
+ value: Joi.string().allow('').optional(),
152
+ // Enhanced capture fields
153
+ element_html: Joi.string().optional(),
154
+ component_data: Joi.alternatives().try(Joi.string(), Joi.object()).optional(),
155
+ event_handlers: Joi.alternatives().try(Joi.string(), Joi.object()).optional(),
156
+ element_state: Joi.alternatives().try(Joi.string(), Joi.object()).optional(),
157
+ performance_metrics: Joi.alternatives().try(Joi.string(), Joi.object()).optional(),
158
+ frameIndex: Joi.number().integer().min(0).optional()
159
+ })
160
+ ).optional().default([]) // Allow empty arrays - Chrome extension sends empty arrays when no interactions occur during frame capture
161
+ });
162
+
163
+ // API key management schemas
164
+ export const createApiKeySchema = Joi.object({
165
+ name: Joi.string().min(1).max(100).required(),
166
+ role: Joi.string().valid('admin', 'user', 'readonly').default('user')
167
+ });
168
+
169
+ export const updateApiKeySchema = Joi.object({
170
+ name: Joi.string().min(1).max(100).optional(),
171
+ active: Joi.boolean().optional()
172
+ });
173
+
174
+ // Parameter validation schemas
175
+ export const sessionIdParam = Joi.object({
176
+ sessionId: patterns.sessionId.required()
177
+ });
178
+
179
+ export const recordingIdParam = Joi.object({
180
+ recordingId: patterns.recordingId.required()
181
+ });
182
+
183
+ export const frameParams = Joi.object({
184
+ sessionId: patterns.sessionId.required(),
185
+ frameIndex: patterns.frameIndex.required()
186
+ });
187
+
188
+ // Query parameter schemas
189
+ export const pageContentQuery = Joi.object({
190
+ includeText: Joi.boolean().default(true),
191
+ includeHtml: Joi.boolean().default(false),
192
+ includeStructure: Joi.boolean().default(true)
193
+ });
194
+
195
+ export const paginationQuery = Joi.object({
196
+ page: Joi.number().integer().min(1).default(1),
197
+ limit: Joi.number().integer().min(1).max(1000).default(50),
198
+ search: Joi.string().max(500).optional()
199
+ });
200
+
201
+ // WebSocket message schemas
202
+ export const wsMessageSchema = Joi.object({
203
+ type: Joi.string().valid('element_selected', 'get_status').required(),
204
+ data: Joi.object().optional()
205
+ });
206
+
207
+ // File upload validation
208
+ export const validateFileUpload = (file, allowedTypes = ['image/jpeg', 'image/png', 'image/webp']) => {
209
+ const errors = [];
210
+
211
+ if (!file) {
212
+ errors.push('File is required');
213
+ return errors;
214
+ }
215
+
216
+ // Check file size (50MB limit)
217
+ const maxSize = 50 * 1024 * 1024;
218
+ if (file.size > maxSize) {
219
+ errors.push(`File size exceeds ${maxSize / (1024 * 1024)}MB limit`);
220
+ }
221
+
222
+ // Check file type
223
+ if (!allowedTypes.includes(file.mimetype)) {
224
+ errors.push(`File type ${file.mimetype} not allowed. Allowed types: ${allowedTypes.join(', ')}`);
225
+ }
226
+
227
+ return errors;
228
+ };
229
+
230
+ // Sanitization helpers
231
+ export const sanitizers = {
232
+ // Remove potentially dangerous HTML/JS
233
+ sanitizeHtml: (str) => {
234
+ if (typeof str !== 'string') return str;
235
+ return str
236
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
237
+ .replace(/javascript:/gi, '')
238
+ .replace(/on\w+\s*=/gi, '');
239
+ },
240
+
241
+ // Sanitize SQL-like patterns
242
+ sanitizeSql: (str) => {
243
+ if (typeof str !== 'string') return str;
244
+ return str.replace(/['"`;\\]/g, '');
245
+ },
246
+
247
+ // Sanitize file paths
248
+ sanitizePath: (str) => {
249
+ if (typeof str !== 'string') return str;
250
+ return str
251
+ .replace(/[\.]{2,}/g, '') // Remove directory traversal
252
+ .replace(/\/+/g, '/') // Normalize multiple slashes
253
+ .replace(/^\/+/, '') // Remove leading slashes
254
+ .replace(/[^a-zA-Z0-9_\-\.\/]/g, ''); // Remove dangerous characters
255
+ }
256
+ };
257
+
258
+ // Validation middleware factory
259
+ export function createValidator(schema, target = 'body') {
260
+ return (req, res, next) => {
261
+ const data = target === 'params' ? req.params :
262
+ target === 'query' ? req.query : req.body;
263
+
264
+ const { error, value } = schema.validate(data, {
265
+ abortEarly: false,
266
+ stripUnknown: true,
267
+ allowUnknown: false
268
+ });
269
+
270
+ if (error) {
271
+ // Diagnostic logging for validation failures
272
+ console.log(`[Validation] Failed for ${target}:`, JSON.stringify(data, null, 2));
273
+ console.log(`[Validation] Errors:`, error.details.map(d => ({ field: d.path.join('.'), message: d.message, value: d.context?.value })));
274
+
275
+ return res.status(400).json({
276
+ error: 'Validation failed',
277
+ details: error.details.map(detail => ({
278
+ field: detail.path.join('.'),
279
+ message: detail.message,
280
+ value: detail.context?.value
281
+ }))
282
+ });
283
+ }
284
+
285
+ // Replace original data with validated/sanitized data
286
+ if (target === 'params') {
287
+ req.params = value;
288
+ } else if (target === 'query') {
289
+ req.query = value;
290
+ } else {
291
+ req.body = value;
292
+ }
293
+
294
+ next();
295
+ };
296
+ }
297
+
298
+ // Instrumentation validation schemas
299
+ export const instrumentationProjectSchema = Joi.object({
300
+ projectPath: Joi.string().min(1).max(1000).required()
301
+ .custom((value, helpers) => {
302
+ // Prevent directory traversal attacks
303
+ if (value.includes('..') || value.includes('~')) {
304
+ return helpers.error('path.unsafe');
305
+ }
306
+ // Must be absolute path
307
+ if (!value.startsWith('/')) {
308
+ return helpers.error('path.relative');
309
+ }
310
+ return value;
311
+ }),
312
+ patterns: Joi.array().items(Joi.string().min(1).max(200)).default([]),
313
+ exclusions: Joi.array().items(Joi.string().min(1).max(200)).default([])
314
+ });
315
+
316
+ export const instrumentationOptionsSchema = Joi.object({
317
+ preserveComments: Joi.boolean().default(true),
318
+ createBackup: Joi.boolean().default(true),
319
+ dryRun: Joi.boolean().default(false),
320
+ useWorkerThreads: Joi.boolean().default(false),
321
+ maxPreviewFiles: Joi.number().integer().min(1).max(100).default(10),
322
+ instrumentationType: Joi.string().valid('auto-detect', 'react', 'vue', 'angular', 'vanilla-js').default('auto-detect'),
323
+ excludeTests: Joi.boolean().default(true),
324
+ backupStrategy: Joi.string().valid('git-stash', 'file-copy', 'none').default('git-stash')
325
+ });
326
+
327
+ // Git repository validation
328
+ export const gitRepositorySchema = Joi.object({
329
+ projectPath: Joi.string().required(),
330
+ allowUncommittedChanges: Joi.boolean().default(false),
331
+ requireGitRepo: Joi.boolean().default(false),
332
+ protectedBranches: Joi.array().items(Joi.string()).default(['main', 'master', 'production'])
333
+ });
334
+
335
+ // File pattern validation with security constraints
336
+ export const filePatternSchema = Joi.object({
337
+ patterns: Joi.array().items(
338
+ Joi.string().pattern(/^[a-zA-Z0-9_\-\.\*\/]+$/).max(200)
339
+ .custom((value, helpers) => {
340
+ // Prevent malicious glob patterns
341
+ if (value.includes('/../') || value.startsWith('../')) {
342
+ return helpers.error('pattern.traversal');
343
+ }
344
+ // Prevent excessive wildcards that could cause DoS
345
+ const wildcardCount = (value.match(/\*/g) || []).length;
346
+ if (wildcardCount > 3) {
347
+ return helpers.error('pattern.excessive');
348
+ }
349
+ return value;
350
+ })
351
+ ).required(),
352
+ exclusions: Joi.array().items(
353
+ Joi.string().pattern(/^[a-zA-Z0-9_\-\.\*\/]+$/).max(200)
354
+ ).default([])
355
+ });
356
+
357
+ // Project boundary validation
358
+ export const projectBoundarySchema = Joi.object({
359
+ projectRoot: Joi.string().required(),
360
+ allowedPaths: Joi.array().items(Joi.string()).optional(),
361
+ restrictedPaths: Joi.array().items(Joi.string()).default([
362
+ '/etc', '/usr', '/var', '/home', '/root', '/System', '/Library'
363
+ ])
364
+ });
365
+
366
+ export default {
367
+ workflowRecordingSchema,
368
+ frameBatchSchema,
369
+ associateLogsSchema,
370
+ streamLogsSchema,
371
+ domIntentSchema,
372
+ navigateSchema,
373
+ evaluateSchema,
374
+ screenInteractionsSchema,
375
+ createApiKeySchema,
376
+ updateApiKeySchema,
377
+ sessionIdParam,
378
+ recordingIdParam,
379
+ frameParams,
380
+ pageContentQuery,
381
+ paginationQuery,
382
+ wsMessageSchema,
383
+ instrumentationProjectSchema,
384
+ instrumentationOptionsSchema,
385
+ gitRepositorySchema,
386
+ filePatternSchema,
387
+ projectBoundarySchema,
388
+ validateFileUpload,
389
+ sanitizers,
390
+ createValidator
391
+ };