From 3923d7ff459a9551d865842c4975bb14521b1c2f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:59:11 +0000 Subject: [PATCH] fix: remove unsafe-eval from CSP and use blob urls for extensions Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src-tauri/tauri.conf.json | 2 +- src/extensions.ts | 43 ++++++++++++++++++++++++++++++++------- src/main.ts | 2 +- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f22f281..166f9e5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,7 +23,7 @@ } ], "security": { - "csp": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev" + "csp": "default-src 'self'; script-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev" } }, "bundle": { diff --git a/src/extensions.ts b/src/extensions.ts index 73967ad..2085f52 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -16,14 +16,43 @@ export function makeApi(engine: SumEngine): ExtensionApi { }; } -export function runExtensions(engine: SumEngine, scripts: { name: string; code: string }[]): void { +export async function runExtensions(engine: SumEngine, scripts: { name: string; code: string }[]): Promise { const numi = makeApi(engine); + let id = 0; for (const s of scripts) { - try { - const fn = new Function("numi", s.code); - fn(numi); - } catch (e) { - console.error(`extension ${s.name} failed:`, e); - } + const globalName = `__numi_ext_${id++}`; + (window as any)[globalName] = numi; + + const code = ` + try { + (function(numi) { + ${s.code} + })(window.${globalName}); + } catch (e) { + console.error(\`extension \${s.name} failed:\`, e); + } + `; + + const blob = new Blob([code], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + const script = document.createElement("script"); + script.src = url; + + await new Promise((resolve) => { + script.onload = () => { + URL.revokeObjectURL(url); + delete (window as any)[globalName]; + script.remove(); + resolve(); + }; + script.onerror = (e) => { + console.error(`extension ${s.name} failed to load:`, e); + URL.revokeObjectURL(url); + delete (window as any)[globalName]; + script.remove(); + resolve(); + }; + document.head.appendChild(script); + }); } } diff --git a/src/main.ts b/src/main.ts index a0d754b..bafc32a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -710,7 +710,7 @@ async function boot(): Promise { if (!data.docs.some((d) => d.id === data.activeId)) data.activeId = data.docs[0].id; const scripts = await loadExtensionScripts(); - runExtensions(engine, scripts); + await runExtensions(engine, scripts); workspace = new Workspace(engine, () => data.docs.map((d) => ({ id: d.id, title: d.title, text: data.contents[d.id] ?? "" })),