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('; ');