/** * Worker Template for Serverless Functions * * This code runs inside a Web Worker environment created by Deno. * Each worker is created fresh for a single request, executes once, and terminates. */ /* eslint-env worker */ /* global self, Request, Deno */ // --- SECURITY BLACKOUT (Top-level) --- // We polyfill Deno.env and process.env BEFORE any imports using Top-Level Await. // This prevents libraries (like 'debug') from triggering 'NotCapable' errors // when using a strict native whitelist (env: false). const sterileEnv = { NODE_ENV: 'production', }; try { // Shadow Deno.env with a pure JS implementation const mockDenoEnv = { get: (key) => sterileEnv[key] ?? undefined, set: () => { throw new Error('Deno.env.set is disabled'); }, delete: () => { throw new Error('Deno.env.delete is disabled'); }, toObject: () => ({ ...sterileEnv }), has: (key) => key in sterileEnv, }; // Replace global Deno.env Object.defineProperty(globalThis, 'Deno', { value: Object.freeze({ env: mockDenoEnv }), configurable: false, // Lock down permanently (Audit Finding) writable: false, }); // Shadow process.env (Node compatibility) if (!globalThis.process) globalThis.process = {}; globalThis.process.env = { ...sterileEnv }; } catch (e) { // FATAL: Security setup failed. Terminate immediately to prevent leakage. console.error('Security shadow application failed:', e); self.postMessage({ success: false, error: 'Security Initialization Error', status: 500, }); self.close(); throw new Error('Security initialization failed - halting worker'); } // ---------------------------- // ---------------------------- // EARLY MESSAGE BUFFERING (Race Fix) // ---------------------------- // The runtime (server.ts -> executeInWorker) calls worker.postMessage() // immediately after `new Worker()`. Web Workers do NOT queue messages that // arrive before an `onmessage` handler is registered, so if we only attached // the handler AFTER the top-level `await import(...)` below (cold import can // take >200ms), the request message was silently dropped -> 504 timeout. // // Fix: register a synchronous handler BEFORE any top-level await. It buffers // any early message(s). Once imports finish, we swap `self.onmessage` to the // real handler and drain the buffer through it. Single-threaded execution // guarantees no message can slip between the swap and the drain. const __earlyMessages = []; self.onmessage = (e) => { __earlyMessages.push(e); }; // ---------------------------- // LATE IMPORTS (Pre-emptive Mocking) // ---------------------------- // We use dynamic imports AFTER the environment is shadowed. // // If an import fails (e.g. npm registry unreachable under restricted egress), // the worker can never produce a real handler. Answer the request with an // explicit 500 (same {success,error,status} shape server.ts onmessage expects) // instead of hanging to the runtime's 60s timeout. // // The request message may NOT have been delivered yet when the import rejects: // literal dynamic imports are prefetched with the module graph, so a failed // import rejects before the first event-loop turn. Closing the worker at that // point would drop the undelivered request and reproduce the silent 504, so we // only close() after a message has been answered (with a deadline as backstop). let createClient, encodeBase64, decodeBase64; let __importFailed = false; try { ({ createClient } = await import('npm:@insforge/sdk')); ({ encodeBase64, decodeBase64 } = await import('https://deno.land/std@0.224.0/encoding/base64.ts')); } catch (importError) { __importFailed = true; const failMsg = 'SDK import failed: ' + (importError?.message || importError); console.error(failMsg); const answerAndClose = () => { self.postMessage({ success: false, error: failMsg, status: 500 }); self.close(); }; if (__earlyMessages.splice(0).length > 0) { answerAndClose(); } else { self.onmessage = answerAndClose; setTimeout(() => self.close(), 10000); } } // Handle the single message with code, request data, and secrets const handleMessage = async (e) => { const { code, requestData, secrets = {} } = e.data; try { /** * MOCK DENO OBJECT: * Providing safe secrets access even under strict native lock-down (env: false). * This fake 'Deno' object is injected into the user function's scope, ensuring * they only see the secrets we explicitly allow, while the native Deno runtime * remains blindfolded at the C++ layer. */ const mockDeno = { // Mock only the required Deno.env API for secret retrieval env: { get: (key) => secrets[key] ?? undefined, // (toObject removed for security to prevent secret enumeration) }, // Explicitly block all subprocess APIs as a secondary defense tier run: () => { throw new Error('Deno.run is natively disabled'); }, spawn: () => { throw new Error('Deno.spawn is natively disabled'); }, Command: function () { throw new Error('Deno.Command is natively disabled'); }, }; /** * FUNCTION WRAPPING: * Injecting mocks into the user function execution scope. * We pass mockDeno instead of the real Deno global. */ const wrapper = new Function( 'exports', 'module', 'createClient', 'Deno', 'encodeBase64', 'decodeBase64', code ); const exports = {}; const module = { exports }; // Execute the wrapper, passing mockDeno as the Deno global wrapper(exports, module, createClient, mockDeno, encodeBase64, decodeBase64); // Get the exported function const functionHandler = module.exports || exports.default || exports; if (typeof functionHandler !== 'function') { throw new Error( 'No function exported. Expected: module.exports = async function(req) { ... }' ); } // Create Request object from data const request = new Request(requestData.url, { method: requestData.method, headers: requestData.headers, body: requestData.body, }); // Execute the function const response = await functionHandler(request); // Serialize and send response let body = null; if (![204, 205, 304].includes(response.status)) { body = await response.text(); } const responseData = { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers), body: body, }; self.postMessage({ success: true, response: responseData }); } catch (error) { if (error instanceof Response) { let body = null; if (![204, 205, 304].includes(error.status)) { body = await error.text(); } const responseData = { status: error.status, statusText: error.statusText, headers: Object.fromEntries(error.headers), body: body, }; self.postMessage({ success: true, response: responseData }); } else { self.postMessage({ success: false, error: error.message || 'Unknown error', status: error.status || 500, }); } } }; // Activate the real handler and drain any messages that arrived during import. if (!__importFailed) { self.onmessage = handleMessage; for (const e of __earlyMessages.splice(0)) { await handleMessage(e); } }