Description
EmDash Media Upload Issue & CSP Patch Report
The Problem
When running an instance of EmDash CMS backed by a Google Cloud Storage (GCS) or Firebase Storage bucket, uploading and displaying media assets in the admin dashboard resulted in persistent failures.
Symptoms:
- UI Error Message: "Upload failed: Failed to fetch" or silent drop upon adding files to content pages or the Media Library.
- Browser Console Error:
Connecting to 'https://storage.googleapis.com/...' violates the following Content Security Policy directive: "connect-src 'self'". The action has been blocked.
- Database records pending: Turso Database shows records of the media in a pending state.
Root Cause
The root cause was traced back to EmDash's built-in authentication middleware (node_modules/emdash/dist/astro/middleware/auth.mjs). This middleware statically generates a Content-Security-Policy (CSP) header onto the dashboard and API routes /_emdash/*.
By default, EmDash hardcodes the following directives:
"connect-src 'self'"
"img-src 'self' data: blob:"
The connect-src 'self' directive forces Google Chrome (and other browsers) to block any outgoing Ajax or fetch() connections to external domains. This inherently breaks EmDash's S3/GCS media adapter flow: when uploading a file, EmDash generates a presigned URL, and the client-side browser attempts to PUT the actual payload directly to https://storage.googleapis.com. The browser immediately terminates the upload because the origin disagrees with the CSP. For similar reasons, the img-src string prevents the CMS from displaying preview thumbnails populated directly from the bucket's URL.
The Solution
To resolve this issue, the auth.mjs middleware file requires tweaking to inject https://storage.googleapis.com into EmDash's allowlist. Since there is currently no exposed option in astro.config.mjs to append global CSP domains, we used patch-package to safely and persistently rewrite the compiled Node module.
Implementation Guide
1. Direct JS Patching:
We updated node_modules/emdash/dist/astro/middleware/auth.mjs inside the buildEmDashCsp functional scope.
Before:
"connect-src 'self'",
`img-src ${imgSources.join(" ")}`,
After:
"connect-src 'self' https://storage.googleapis.com",
`img-src ${imgSources.join(" ")} https://storage.googleapis.com`,
2. Preserving the Patch (NPM):
To preserve this change across different machines and environments, we captured the diff natively via patch-package.
npm install patch-package
npx patch-package emdash
This produced a patch blueprint stored safely into patches/emdash+0.1.0.patch.
3. Automatic Implementation (CI/CD):
We added a postinstall hook inside the project's package.json so every subsequent npm install automatically patches to EmDash gracefully:
"scripts": {
"postinstall": "patch-package"
}
4. Docker Compatibility Adjustments:
In the application's Dockerfile, we adjusted the staging sequence to insert the patches/ folder adjacent to package.json before firing npm ci. Without this minor tweak, the Docker container's npm ci attempt would fail, since the postinstall script lacks the patch references.
# Install dependencies strictly
COPY package.json package-lock.json* ./
COPY patches ./patches
RUN npm ci
# Copy remaining project files
COPY . .
With this deployment configuration in place, developers and future Google Cloud Run deploy pipelines will automatically weave the CSP exemption into EmDash, resulting in a healthy, successful connection line to the Firebase Cloud Storage bucket.
Steps to reproduce
- Set up EmDash to un on Google Cloud Run - Firebase and Google Cloud Storage for Firebase (bucket).
- Create new page or go to the Media Library.
- Try to upload an image.
- Upload dialog erros: "Upload failed: Failed to fetch".
- Upload through Media Library directly simply silently drops.
- Using Chrome's "Inspect" feature, you will see errors and warnings in the console.
Environment
- emdash 0.1.0
- Google Cloud Run
- Firebase
- Cloud Store for Firebase
- Google Chrome latest running in Linux
Logs / error output
PluginRegistry.hYnmbUrG.js:237 Connecting to 'https://storage.googleapis.com/fdp-emdash.firebasestorage.app/01KNT2F96ZHQA…cksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject' violates the following Content Security Policy directive: "connect-src 'self'". The action has been blocked.
a$e @ PluginRegistry.hYnmbUrG.js:237
ure @ PluginRegistry.hYnmbUrG.js:237
await in ure
mutationFn @ PluginRegistry.hYnmbUrG.js:326
fn @ PluginRegistry.hYnmbUrG.js:13
g @ PluginRegistry.hYnmbUrG.js:13
start @ PluginRegistry.hYnmbUrG.js:13
execute @ PluginRegistry.hYnmbUrG.js:13
await in execute
mutate @ PluginRegistry.hYnmbUrG.js:13
(anonymous) @ PluginRegistry.hYnmbUrG.js:13
onUpload @ PluginRegistry.hYnmbUrG.js:326
A @ PluginRegistry.hYnmbUrG.js:245
E1 @ client.CHlC-0c6.js:8
(anonymous) @ client.CHlC-0c6.js:8
Hi @ client.CHlC-0c6.js:8
Cc @ client.CHlC-0c6.js:8
rc @ client.CHlC-0c6.js:9
Bh @ client.CHlC-0c6.js:9
PluginRegistry.hYnmbUrG.js:237 Fetch API cannot load https://storage.googleapis.com/fdp-emdash.firebasestorage.app/01KNT2F96ZHQA…cksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject. Refused to connect because it violates the document's Content Security Policy.
a$e @ PluginRegistry.hYnmbUrG.js:237
ure @ PluginRegistry.hYnmbUrG.js:237
await in ure
mutationFn @ PluginRegistry.hYnmbUrG.js:326
fn @ PluginRegistry.hYnmbUrG.js:13
g @ PluginRegistry.hYnmbUrG.js:13
start @ PluginRegistry.hYnmbUrG.js:13
execute @ PluginRegistry.hYnmbUrG.js:13
await in execute
mutate @ PluginRegistry.hYnmbUrG.js:13
(anonymous) @ PluginRegistry.hYnmbUrG.js:13
onUpload @ PluginRegistry.hYnmbUrG.js:326
A @ PluginRegistry.hYnmbUrG.js:245
E1 @ client.CHlC-0c6.js:8
(anonymous) @ client.CHlC-0c6.js:8
Hi @ client.CHlC-0c6.js:8
Cc @ client.CHlC-0c6.js:8
rc @ client.CHlC-0c6.js:9
Bh @ client.CHlC-0c6.js:9
Also these warnings:
Some resources are blocked because their origin is not listed in your site's Content Security Policy (CSP). Your site's CSP is allowlist-based, so resources must be listed in the allowlist in order to be accessed.
A site's Content Security Policy is set either via an HTTP header (recommended), or via a meta HTML tag.
To fix this issue do one of the following:
(Recommended) If you're using an allowlist for 'script-src', consider switching from an allowlist CSP to a strict CSP, because strict CSPs are more robust against XSS. See how to set a strict CSP.
Or carefully check that all of the blocked resources are trustworthy; if they are, include their sources in the CSP of your site. ⚠️Never add a source you don't trust to your site's CSP. If you don't trust the source, consider hosting resources on your own site instead.
3 directives
Resource Status Directive Source location
https://storage.googleapis.com/fdp-emdash.firebasestorage.app/01KNT23DHVAP39BXFKX9RC4DKK.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=GOOG1EGXWWAU54G2JHQSM7LE3VLLISVTPQBAYXZLLIYJ2AJQDQWDNMGLWXEUC%2F20260409%2Fus-east1%2Fs3%2Faws4_request&X-Amz-Date=20260409T212419Z&X-Amz-Expires=3600&X-Amz-Signature=b5b2461187f1bb9f249e7def9d02a52e9a74cef9694492b080b803b620464712&X-Amz-SignedHeaders=content-length%3Bhost&x-amz-checksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject blocked connect-src PluginRegistry.hYnmbUrG.js:237
https://storage.googleapis.com/fdp-emdash.firebasestorage.app/01KNT2C7QX2KDMD9K80FCAV3D6.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=GOOG1EGXWWAU54G2JHQSM7LE3VLLISVTPQBAYXZLLIYJ2AJQDQWDNMGLWXEUC%2F20260409%2Fus-east1%2Fs3%2Faws4_request&X-Amz-Date=20260409T212908Z&X-Amz-Expires=3600&X-Amz-Signature=a97045e7f40e97a199a22e2045f131b72973e7eb7663c181eb02a6d10498a4c0&X-Amz-SignedHeaders=content-length%3Bhost&x-amz-checksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject blocked connect-src PluginRegistry.hYnmbUrG.js:237
https://storage.googleapis.com/fdp-emdash.firebasestorage.app/01KNT2F96ZHQAM3QQ253X4Y9V5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=GOOG1EGXWWAU54G2JHQSM7LE3VLLISVTPQBAYXZLLIYJ2AJQDQWDNMGLWXEUC%2F20260409%2Fus-east1%2Fs3%2Faws4_request&X-Amz-Date=20260409T213048Z&X-Amz-Expires=3600&X-Amz-Signature=8905c2c90c8b0f4107b4ef0bc8aace3042dba87bb6034a68ad4a2e3bb72880dd&X-Amz-SignedHeaders=content-length%3Bhost&x-amz-checksum-crc32=AAAAAA%3D%3D&x-amz-sdk-checksum-algorithm=CRC32&x-id=PutObject blocked connect-src PluginRegistry.hYnmbUrG.js:237
Learn more: Content Security Policy - Source Allowlists
Description
EmDash Media Upload Issue & CSP Patch Report
The Problem
When running an instance of EmDash CMS backed by a Google Cloud Storage (GCS) or Firebase Storage bucket, uploading and displaying media assets in the admin dashboard resulted in persistent failures.
Symptoms:
Connecting to 'https://storage.googleapis.com/...' violates the following Content Security Policy directive: "connect-src 'self'". The action has been blocked.Root Cause
The root cause was traced back to EmDash's built-in authentication middleware (
node_modules/emdash/dist/astro/middleware/auth.mjs). This middleware statically generates aContent-Security-Policy(CSP) header onto the dashboard and API routes/_emdash/*.By default, EmDash hardcodes the following directives:
The
connect-src 'self'directive forces Google Chrome (and other browsers) to block any outgoing Ajax orfetch()connections to external domains. This inherently breaks EmDash's S3/GCS media adapter flow: when uploading a file, EmDash generates a presigned URL, and the client-side browser attempts toPUTthe actual payload directly tohttps://storage.googleapis.com. The browser immediately terminates the upload because the origin disagrees with the CSP. For similar reasons, theimg-srcstring prevents the CMS from displaying preview thumbnails populated directly from the bucket's URL.The Solution
To resolve this issue, the
auth.mjsmiddleware file requires tweaking to injecthttps://storage.googleapis.cominto EmDash's allowlist. Since there is currently no exposed option inastro.config.mjsto append global CSP domains, we usedpatch-packageto safely and persistently rewrite the compiled Node module.Implementation Guide
1. Direct JS Patching:
We updated
node_modules/emdash/dist/astro/middleware/auth.mjsinside thebuildEmDashCspfunctional scope.Before:
After:
2. Preserving the Patch (NPM):
To preserve this change across different machines and environments, we captured the diff natively via
patch-package.This produced a patch blueprint stored safely into
patches/emdash+0.1.0.patch.3. Automatic Implementation (CI/CD):
We added a
postinstallhook inside the project'spackage.jsonso every subsequentnpm installautomatically patches to EmDash gracefully:4. Docker Compatibility Adjustments:
In the application's
Dockerfile, we adjusted the staging sequence to insert thepatches/folder adjacent topackage.jsonbefore firingnpm ci. Without this minor tweak, the Docker container'snpm ciattempt would fail, since thepostinstallscript lacks the patch references.With this deployment configuration in place, developers and future Google Cloud Run deploy pipelines will automatically weave the CSP exemption into EmDash, resulting in a healthy, successful connection line to the Firebase Cloud Storage bucket.
Steps to reproduce
Environment
Logs / error output