fix: allow blob PDF preview frames #1840

Merged
hongming merged 1 commits from fix/pdf-preview-csp into main 2026-05-25 08:33:00 +00:00
4 changed files with 17 additions and 3 deletions
+6
View File
@@ -41,6 +41,12 @@ describe("buildCsp — production", () => {
expect(csp).toContain("object-src 'none'");
});
it("allows blob: in frame-src for authenticated PDF previews", () => {
const frameSrc = csp.match(/frame-src[^;]*/)?.[0] ?? "";
expect(frameSrc).toContain("'self'");
expect(frameSrc).toContain("blob:");
});
it("locks base-uri to 'self' (prevents base-tag injection)", () => {
expect(csp).toContain("base-uri 'self'");
});
+4 -1
View File
@@ -12,7 +12,9 @@ import type { NextRequest } from "next/server";
* • style-src retains 'unsafe-inline': React Flow positions nodes via
* element-level style="" attributes which cannot be nonce'd; CSS injection
* is significantly lower risk than script injection and is acceptable here.
* • object-src / base-uri / frame-ancestors locked to 'none'/'self'.
* • object-src locked to 'none'; frame-src allows self + blob: for
* browser-native PDF previews backed by authenticated Blob URLs.
* • base-uri / frame-ancestors locked to 'self'/'none'.
* • upgrade-insecure-requests forces HTTPS on mixed-content.
*
* Development — permissive policy:
@@ -61,6 +63,7 @@ export function buildCsp(nonce: string, isDev: boolean): string {
"img-src 'self' blob: data:",
"font-src 'self'",
"object-src 'none'",
"frame-src 'self' blob:",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
@@ -23,7 +23,7 @@ var apiPrefixes = []string{
"/settings",
"/bundles",
"/org",
"/orgs", // #610 — per-org plugin allowlist routes
"/orgs", // #610 — per-org plugin allowlist routes
"/templates",
"/plugins",
"/webhooks",
@@ -95,6 +95,7 @@ func SecurityHeaders() gin.HandlerFunc {
"script-src 'self' 'unsafe-inline'; "+
"style-src 'self' 'unsafe-inline'; "+
"img-src 'self' data: blob:; "+
"frame-src 'self' blob:; "+
"connect-src 'self' ws: wss:; "+
"font-src 'self' data:")
}
@@ -57,6 +57,7 @@ func TestSecurityHeaders(t *testing.T) {
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"frame-src 'self' blob:",
"connect-src 'self' ws: wss:",
"font-src 'self' data:",
} {
@@ -195,6 +196,9 @@ func TestCSPCanvasRoutesGetPermissivePolicy(t *testing.T) {
if strings.Contains(csp, "'unsafe-eval'") {
t.Errorf("canvas path %q: CSP must not contain 'unsafe-eval', got %q", path, csp)
}
if !strings.Contains(csp, "frame-src 'self' blob:") {
t.Errorf("canvas path %q: CSP should allow blob: frames for PDF previews, got %q", path, csp)
}
})
}
}
@@ -267,7 +271,7 @@ func TestIsAPIPath(t *testing.T) {
{"/ws", true},
{"/events", true},
{"/approvals", true},
{"/orgs", true}, // #610 allowlist routes
{"/orgs", true}, // #610 allowlist routes
{"/orgs/org-1/plugins/allowlist", true},
// Sub-paths
{"/workspaces/abc-123", true},