// WebWorker for safe shader compilation and execution interface WorkerMessage { id: string; type: 'compile' | 'render'; code?: string; width?: number; height?: number; time?: number; } interface WorkerResponse { id: string; type: 'compiled' | 'rendered' | 'error'; success: boolean; imageData?: ImageData; error?: string; } class ShaderWorker { private compiledFunction: Function | null = null; private lastCode: string = ''; constructor() { self.onmessage = (e: MessageEvent) => { this.handleMessage(e.data); }; } private handleMessage(message: WorkerMessage): void { try { switch (message.type) { case 'compile': this.compileShader(message.id, message.code!); break; case 'render': this.renderShader(message.id, message.width!, message.height!, message.time!); break; } } catch (error) { this.postError(message.id, error instanceof Error ? error.message : 'Unknown error'); } } private compileShader(id: string, code: string): void { if (code === this.lastCode && this.compiledFunction) { this.postMessage({ id, type: 'compiled', success: true }); return; } try { const safeCode = this.sanitizeCode(code); this.compiledFunction = new Function('x', 'y', 't', 'i', ` // Timeout protection const startTime = performance.now(); let iterations = 0; function checkTimeout() { iterations++; if (iterations % 1000 === 0 && performance.now() - startTime > 5) { throw new Error('Shader timeout'); } } return (function() { checkTimeout(); return ${safeCode}; })(); `); this.lastCode = code; this.postMessage({ id, type: 'compiled', success: true }); } catch (error) { this.compiledFunction = null; this.postError(id, error instanceof Error ? error.message : 'Compilation failed'); } } private renderShader(id: string, width: number, height: number, time: number): void { if (!this.compiledFunction) { this.postError(id, 'No compiled shader'); return; } const imageData = new ImageData(width, height); const data = imageData.data; const startTime = performance.now(); const maxRenderTime = 50; // 50ms max render time try { for (let y = 0; y < height; y++) { // Check timeout every row if (performance.now() - startTime > maxRenderTime) { // Fill remaining pixels with black and break for (let remainingY = y; remainingY < height; remainingY++) { for (let remainingX = 0; remainingX < width; remainingX++) { const i = (remainingY * width + remainingX) * 4; data[i] = 0; // R data[i + 1] = 0; // G data[i + 2] = 0; // B data[i + 3] = 255; // A } } break; } for (let x = 0; x < width; x++) { const i = (y * width + x) * 4; const pixelIndex = y * width + x; try { const value = this.compiledFunction(x, y, time, pixelIndex); const safeValue = isFinite(value) ? value : 0; const color = Math.abs(safeValue) % 256; data[i] = color; // R data[i + 1] = (color * 2) % 256; // G data[i + 2] = (color * 3) % 256; // B data[i + 3] = 255; // A } catch (error) { data[i] = 0; // R data[i + 1] = 0; // G data[i + 2] = 0; // B data[i + 3] = 255; // A } } } this.postMessage({ id, type: 'rendered', success: true, imageData }); } catch (error) { this.postError(id, error instanceof Error ? error.message : 'Render failed'); } } private sanitizeCode(code: string): string { // Strict whitelist approach const allowedPattern = /^[0-9a-zA-Z\s\+\-\*\/\%\^\&\|\(\)\<\>\~\?:,\.xyti]+$/; if (!allowedPattern.test(code)) { throw new Error('Invalid characters in shader code'); } // Check for dangerous keywords const dangerousKeywords = [ 'eval', 'Function', 'constructor', 'prototype', '__proto__', 'window', 'document', 'global', 'process', 'require', 'import', 'export', 'class', 'function', 'var', 'let', 'const', 'while', 'for', 'do', 'if', 'else', 'switch', 'case', 'break', 'continue', 'return', 'throw', 'try', 'catch', 'finally' ]; const codeWords = code.toLowerCase().split(/[^a-z]/); for (const keyword of dangerousKeywords) { if (codeWords.includes(keyword)) { throw new Error(`Forbidden keyword: ${keyword}`); } } // Limit expression complexity const complexity = (code.match(/[\(\)]/g) || []).length; if (complexity > 20) { throw new Error('Expression too complex'); } // Limit code length if (code.length > 200) { throw new Error('Code too long'); } return code; } private postMessage(response: WorkerResponse): void { self.postMessage(response); } private postError(id: string, error: string): void { this.postMessage({ id, type: 'error', success: false, error }); } } // Initialize worker new ShaderWorker();