fix: allow blob PDF preview frames #1840
@@ -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'");
|
||||
});
|
||||
|
||||
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user