CSP Origin Restrictions Between Live Reload and Production Bundles
0 reputation · 18 Aug 2022, 22:51 UTC
0 reputation · 18 Aug 2022, 22:51 UTC
Capacitor applications often utilize a development configuration with a local IP address to enable live reload. In this state, the web view operates under a network-based origin, which typically allows for flexible network request patterns during the development cycle.
When transitioning to a production build, the web assets are bundled locally. This changes the application origin to capacitor://localhost on iOS and http://localhost on Android. This shift in origin can trigger Content Security Policy (CSP) violations that were not present during local testing.
The challenge lies in defining a CSP that maintains strict security while permitting necessary native plugin communication and external API calls across both development and production schemes.
33775 reputation · 19 Aug 2022, 08:50 UTC
Use 'self' in default-src and connect-src so the policy follows the document origin in both modes. During live reload, 'self' resolves to the dev server IP and port; in production it resolves to capacitor://localhost on iOS and http(s)://localhost on Android. Add only the dev origin and its WebSocket scheme to the development CSP, and exclude them from the production bundle. Generate the correct <meta http-equiv="Content-Security-Policy"> at build time through your bundler's HTML transform; do not try to rewrite a meta CSP after the page loads.
The violation appears because the document origin changes between schemes, so an allow-list that worked under http://192.168.x.x:PORT no longer matches capacitor://localhost. Native Capacitor plugin calls cross the native bridge and are not governed by connect-src; only HTTP/fetch targets and WebSocket connections need explicit origins.
'self' adapts to the WebView document origin in both development and production.frame-ancestors, sandbox, or report-only directives.androidScheme defaults vary by Capacitor major version; pin it explicitly in capacitor.config.ts.androidScheme (and iosScheme if needed) in Capacitor config so the production origin is stable across updates.'self' plus explicit external API hosts in connect-src and CDN hosts in script-src/style-src. Use relative URLs for app resources so one 'self' entry covers both schemes.ws:// or wss:// for HMR. Create a production variant without those entries.index.html at build time: Vite transformIndexHtml, Webpack HtmlWebpackPlugin template, or an Angular index-file transform, keyed off MODE or NODE_ENV.ws: entry.dist/index.html: grep -E "ws:|192\.168|localhost:PORT" dist/index.html should return nothing.chrome://inspect for Android). Confirm location.origin matches the pinned scheme and that no CSP errors appear.Assumptions and uncertainty: This assumes a standard Capacitor WebView and a bundler that transforms index.html. Exact placeholder syntax and config keys vary by Capacitor and bundler version; verify against the versions installed in the project. Treat the androidScheme default as version-dependent until confirmed.
Missing diagnostic detail: Which bundler or framework generates your index.html (Vite, Webpack, Angular, or another)? The injection hook changes with that choice.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 18 Aug 2022, 23:10 UTC
A critical detail when structuring the CSP for this transition is how the 'self' keyword resolves. In a live-reload environment, 'self' refers to the development server's IP or domain. Once bundled, it resolves to the platform-specific scheme: capacitor://localhost on iOS or http://localhost on Android.
To maintain a single index.html without dynamic injection, you can define a policy that explicitly permits both environments in the connect-src and script-src directives:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self' http://192.168.x.x https://api.production.com; script-src 'self' http://192.168.x.x;">
Verification should be performed by checking the Safari Web Inspector (iOS) or Chrome DevTools (Android) specifically after the production build is deployed, as 'self' violations are common when native plugins attempt to communicate with the local web view via these custom schemes.