diff --git a/packages/cli/src/services/web-ui.ts b/packages/cli/src/services/web-ui.ts index 732e505f0b1..4d2c872f9e9 100644 --- a/packages/cli/src/services/web-ui.ts +++ b/packages/cli/src/services/web-ui.ts @@ -58,8 +58,10 @@ function isRouteNotFound(error: unknown) { return error instanceof HttpServerError.HttpServerError && error.reason._tag === "RouteNotFound" } +// qr-scanner decodes in a worker it creates from a blob: URL whenever the browser has no BarcodeDetector (Safari, desktop +// Chrome on Windows and Linux). Without worker-src that worker falls under script-src 'self' and never starts. function csp(hash = "") { - return `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; media-src 'self' data:; connect-src * data: blob:` + return `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; media-src 'self' data:; connect-src * data: blob:; worker-src 'self' blob:` } function cspForHtml(body: string) { diff --git a/packages/cli/test/web-ui.test.ts b/packages/cli/test/web-ui.test.ts index 4bc4b59b5ed..5db5ca3fdb9 100644 --- a/packages/cli/test/web-ui.test.ts +++ b/packages/cli/test/web-ui.test.ts @@ -171,6 +171,7 @@ describe("web UI", () => { expect(yield* Effect.promise(() => fallback.text())).toContain("embedded") expect(fallback.headers.get("content-security-policy")).toContain("default-src 'self'") expect(fallback.headers.get("content-security-policy")).toContain("connect-src * data: blob:") + expect(fallback.headers.get("content-security-policy")).toContain("worker-src 'self' blob:") const dotted = yield* Effect.promise(() => fetch(`${origin}/workspace/example.js`)) expect(dotted.status).toBe(200)