From 75ec7f6006fea6b224300a03c81cbd5025d3f853 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Sun, 19 Jul 2026 22:27:17 +0200 Subject: [PATCH] CSP: data:-Fonts erlauben (eingebettete Fonts in Excalidraw-SVGs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Excalidraw bettet beim Speichern die verwendeten Schrift-Subsets als data:-URIs ins Snapshot-SVG ein. Die Seiten-CSP (nginx) und die Plugin-Frame-CSP erlaubten aber nur `font-src 'self'` bzw. den Asset-Pfad — die Handschrift fiel in der öffentlichen Ansicht und im Snapshot-Render auf Serifen zurück (auf Prod an der ersten Demo-Skizze sichtbar). Fix: `data:` in beiden font-src-Direktiven. data:-Fonts lösen keinerlei Netzwerk-Request aus — die Zero-Third-Party-Garantie (security.md) bleibt unberührt; fonts.ts-Kommentar entsprechend präzisiert. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC --- apps/api/src/plugins/plugin-frame.ts | 5 ++++- apps/web/nginx.conf | 6 +++++- packages/shared/src/fonts.ts | 7 ++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/api/src/plugins/plugin-frame.ts b/apps/api/src/plugins/plugin-frame.ts index 5242001..9e8e6b9 100644 --- a/apps/api/src/plugins/plugin-frame.ts +++ b/apps/api/src/plugins/plugin-frame.ts @@ -29,7 +29,10 @@ export function buildPluginFrameCsp(assetBase: string): string { `script-src ${assetBase}`, `style-src ${assetBase} 'unsafe-inline'`, `img-src ${assetBase} data: blob:`, - `font-src ${assetBase}`, + // `data:` so a saved sketch SVG with embedded (subsetted) fonts — the + // Excalidraw plugin stores those — renders its handwriting look in the + // snapshot view too. data: fonts make no network request. + `font-src ${assetBase} data:`, // A plugin may talk to its OWN version-pinned assets (bundled apps like // drawio lazy-load stencils/resources via XHR) — and to nothing else: // no api, no external hosts. The zero-external-network guarantee holds. diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf index 0ea8184..c9c8b95 100644 --- a/apps/web/nginx.conf +++ b/apps/web/nginx.conf @@ -28,7 +28,11 @@ server { # from this origin only (the GDPR "zero external requests" posture). # `style-src 'unsafe-inline'` covers the app's inline style attributes # (CSS custom properties, layout); scripts are all external files. - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: blob:; connect-src 'self'; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always; + # font-src includes `data:` for the subsetted fonts Excalidraw embeds + # into saved sketch SVGs (inlined by the plugin fallback renderer on + # public pages). data: fonts trigger no network request, so the + # zero-third-party-request guarantee (security.md) is unaffected. + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob:; connect-src 'self'; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always; add_header X-Content-Type-Options "nosniff" always; try_files $uri /index.html; } diff --git a/packages/shared/src/fonts.ts b/packages/shared/src/fonts.ts index e36e654..9cbf82e 100644 --- a/packages/shared/src/fonts.ts +++ b/packages/shared/src/fonts.ts @@ -5,9 +5,10 @@ * page lists their licenses. Adding a font is a change here + an image rebuild * — there is no runtime font management (deliberately small surface). * - * Fonts are served only from the instance itself (`font-src 'self'`); a - * visitor's browser makes zero third-party requests (the GDPR guarantee, - * security.md). + * Fonts are served only from the instance itself (`font-src 'self' data:` — + * the `data:` part covers fonts embedded inline in saved plugin SVGs, e.g. + * Excalidraw sketches); a visitor's browser makes zero third-party requests + * (the GDPR guarantee, security.md). */ export type FontCategory = 'sans-serif' | 'serif' | 'monospace'; export type FontLicense = 'OFL-1.1' | 'Apache-2.0';