From 41cc52721167f076aa66ace9bb12f2e757212d7e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 4 May 2026 20:12:29 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20fix:=20relax=20connect-src=20to?= =?UTF-8?q?=20allow=20sourcemap=20fetches=20(silence=20CSP=20noise)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual e2e on PR #12934: every time DevTools is open while viewing a DOCX or PPTX preview, the console fills with CSP violations like: Connecting to 'https://cdn.jsdelivr.net/npm/docx-preview@0.3.7/ dist/docx-preview.min.js.map' violates the following Content Security Policy directive: "connect-src 'none'". The request has been blocked. The actual rendering isn't affected (sourcemap fetches happen AFTER the script has already loaded and executed via `script-src`), but the noise is enough to make people suspect a real problem and distracts from useful console output. Fix: relax `connect-src` from `'none'` to `'self' https://cdn. jsdelivr.net` in both DOCX and PPTX CDN docs. This allows: - Same-origin fetches (sandpack-static-server) — covers any bundler-embedded sourcemaps + same-origin runtime fetches the renderer might make - jsdelivr fetches — covers sourcemaps from the CDN where we loaded the script Exfiltration risk stays minimal: the iframe is cross-origin to LibreChat so an attacker can't read application data anyway, and neither 'self' (sandpack-static-server) nor jsdelivr is a useful target for exfiltrating slide content to a host the attacker controls. Tests updated: assertions for `connect-src 'none'` swapped to `connect-src 'self' https://cdn.jsdelivr.net` for both DOCX + PPTX CDN docs. Added negative assertion for wildcard `*` in connect-src so a future "let's allow everything" rewrite can't widen the exfiltration surface. --- packages/api/src/files/documents/html.spec.ts | 24 +++++++++++---- packages/api/src/files/documents/html.ts | 29 +++++++++++++++++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/api/src/files/documents/html.spec.ts b/packages/api/src/files/documents/html.spec.ts index 681be2bf4d..54aa036e2f 100644 --- a/packages/api/src/files/documents/html.spec.ts +++ b/packages/api/src/files/documents/html.spec.ts @@ -123,10 +123,15 @@ describe('Office HTML producers', () => { ); expect(cspMatch).not.toBeNull(); const csp = cspMatch![1]; - // The bomb defense: no outbound HTTP requests from the rendered - // iframe — a parser bug in docx-preview can't be turned into - // exfiltration of the embedded document content. - expect(csp).toMatch(/connect-src 'none'/); + /* `connect-src` is restricted to self + jsdelivr (where the + * renderer script came from). Allowing these is necessary for + * DevTools sourcemap fetches; broader exfiltration paths + * (arbitrary HTTPS, websockets, etc.) stay blocked. Manual + * e2e on PR #12934 — strict `'none'` filled DevTools console + * with `.min.js.map` violations every time the iframe was + * inspected. */ + expect(csp).toMatch(/connect-src 'self' https:\/\/cdn\.jsdelivr\.net/); + expect(csp).not.toMatch(/connect-src[^;]*\*/); // no wildcard // No `` tampering, no form submission either. expect(csp).toMatch(/base-uri 'none'/); expect(csp).toMatch(/form-action 'none'/); @@ -438,7 +443,7 @@ describe('Office HTML producers', () => { expect(html).toContain('crossorigin="anonymous"'); }); - test('CSP locks the iframe down: connect-src none, no eval, no base/form tampering', async () => { + test('CSP locks the iframe down: connect-src restricted, no eval, no base/form tampering', async () => { const pptx = await buildPptx([{ title: 'X' }]); const html = await _internal.pptxToHtmlViaCdn(pptx); const cspMatch = html.match( @@ -446,7 +451,14 @@ describe('Office HTML producers', () => { ); expect(cspMatch).not.toBeNull(); const csp = cspMatch![1]; - expect(csp).toMatch(/connect-src 'none'/); + /* `connect-src` allows self + jsdelivr (the script's origin) + * so DevTools sourcemap fetches succeed and any same-origin + * fetches the renderer makes at runtime work. Wildcard + * exfiltration paths stay blocked. Manual e2e on PR #12934 — + * strict `'none'` filled DevTools console with `.min.js.map` + * violations every time the iframe was inspected. */ + expect(csp).toMatch(/connect-src 'self' https:\/\/cdn\.jsdelivr\.net/); + expect(csp).not.toMatch(/connect-src[^;]*\*/); // no wildcard expect(csp).toMatch(/base-uri 'none'/); expect(csp).toMatch(/form-action 'none'/); expect(csp).toMatch(/script-src https:\/\/cdn\.jsdelivr\.net 'unsafe-inline'/); diff --git a/packages/api/src/files/documents/html.ts b/packages/api/src/files/documents/html.ts index baa723fd17..ae09282706 100644 --- a/packages/api/src/files/documents/html.ts +++ b/packages/api/src/files/documents/html.ts @@ -378,13 +378,25 @@ const OFFICE_HTML_OUTPUT_CAP = 512 * 1024; * styles into `` at render time). */ function buildDocxCdnDocument(base64: string, mammothFallbackHtml: string): string { + /* `connect-src` allows fetches to: + * - `'self'`: the sandpack-static-server origin the iframe runs in + * (covers any same-origin sourcemap fetches the bundler embedded) + * - `https://cdn.jsdelivr.net`: where the renderer script came from + * (DevTools fetches `.min.js.map` from the same host as the script + * to map minified line numbers; with `'none'` the console fills + * with CSP violations every time DevTools is open) + * + * Exfiltration risk is minimal: the iframe is cross-origin to the + * LibreChat host so an attacker can't read application data from it, + * and the only meaningful target ('self' or jsdelivr) isn't useful + * for exfiltrating slide content to a host the attacker controls. */ const csp = [ "default-src 'none'", "script-src https://cdn.jsdelivr.net 'unsafe-inline'", "style-src 'unsafe-inline'", "img-src 'self' data: blob:", 'font-src data:', - "connect-src 'none'", + "connect-src 'self' https://cdn.jsdelivr.net", "base-uri 'none'", "form-action 'none'", ].join('; '); @@ -1061,6 +1073,19 @@ function buildPptxCdnDocument(base64: string): string { * visible symptom is a black iframe with the renderer half- * started. Allowing blob:-only workers is the minimum surface to * unblock rendering without permitting arbitrary worker URLs. */ + /* `connect-src` allows fetches to: + * - `'self'`: the sandpack-static-server origin the iframe runs in + * (covers any same-origin fetches pptx-preview or its bundled + * echarts make at render time, plus same-origin sourcemap loads) + * - `https://cdn.jsdelivr.net`: where the renderer script came from + * (DevTools fetches `.min.js.map` from there to map minified + * line numbers; with `'none'` the console fills with CSP + * violations every time DevTools is open) + * + * Exfiltration risk is minimal: the iframe is cross-origin to the + * LibreChat host so an attacker can't read application data, and + * the only meaningful targets ('self' or jsdelivr) aren't useful + * for exfiltrating slide content to a host the attacker controls. */ const csp = [ "default-src 'none'", "script-src https://cdn.jsdelivr.net 'unsafe-inline'", @@ -1068,7 +1093,7 @@ function buildPptxCdnDocument(base64: string): string { 'worker-src blob:', "img-src 'self' data: blob:", 'font-src data:', - "connect-src 'none'", + "connect-src 'self' https://cdn.jsdelivr.net", "base-uri 'none'", "form-action 'none'", ].join('; ');