// ========================================== // JavaScript Sandbox — Browser Extension // ========================================== // Executes JavaScript in a sandboxed iframe. // Registers the js_eval tool so the LLM can run code, // verify calculations, and transform data. // // Security: The iframe has sandbox="allow-scripts" only. // No allow-same-origin, no network, no DOM access. // ========================================== Extensions.register({ id: 'js-sandbox', _iframe: null, _pendingCallbacks: new Map(), // messageId → { resolve, reject, timer } _messageCounter: 0, async init(ctx) { const self = this; // Create the sandboxed iframe on first use (lazy) ctx.tools.handle('js_eval', async (args) => { return self._execute(args.code || ''); }); }, async _execute(code) { if (!code.trim()) { return { error: 'No code provided' }; } // Ensure iframe is ready await this._ensureIframe(); const messageId = 'eval-' + (++this._messageCounter); const TIMEOUT_MS = 10000; return new Promise((resolve) => { // Set up timeout const timer = setTimeout(() => { this._pendingCallbacks.delete(messageId); resolve({ success: false, error: 'Execution timed out (10s)', output: [], result: null, }); }, TIMEOUT_MS); this._pendingCallbacks.set(messageId, { resolve, timer }); // Send code to sandbox this._iframe.contentWindow.postMessage({ type: 'eval', id: messageId, code: code, }, '*'); }); }, _ensureIframe() { if (this._iframe) return Promise.resolve(); return new Promise((resolve) => { // Build sandbox HTML as a blob URL const sandboxHTML = this._buildSandboxHTML(); const blob = new Blob([sandboxHTML], { type: 'text/html' }); const blobURL = URL.createObjectURL(blob); const iframe = document.createElement('iframe'); iframe.sandbox = 'allow-scripts'; iframe.style.cssText = 'display:none;width:0;height:0;border:none;position:absolute;'; iframe.src = blobURL; iframe.onload = () => { this._iframe = iframe; URL.revokeObjectURL(blobURL); resolve(); }; // Listen for messages from sandbox window.addEventListener('message', (event) => { // Only accept messages from our sandbox iframe if (!this._iframe || event.source !== this._iframe.contentWindow) return; const data = event.data; if (data?.type !== 'eval-result') return; const pending = this._pendingCallbacks.get(data.id); if (!pending) return; clearTimeout(pending.timer); this._pendingCallbacks.delete(data.id); pending.resolve({ success: !data.error, result: data.result !== undefined ? data.result : null, output: data.output || [], error: data.error || null, }); }); document.body.appendChild(iframe); }); }, _buildSandboxHTML() { // This HTML runs INSIDE the sandboxed iframe. // It has no access to the parent page, no network, no cookies. return ` Sandbox