99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

Fixing Development Server Access Issues Caused by the CSP Upgrade Insecure Requests Directive in Safari

SafariCSPVite
a year ago
0
HTML
1
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
The CSP Upgrade Insecure Requests directive

upgrade-insecure-requests is a CSP (Content-Security-Policy) directive that tells the browser to automatically upgrade every HTTP request in a web page (images, scripts, APIs, and so on) to HTTPS.
Loading HTTP resources on a web page served over HTTPS can open security holes such as XSS, MITM, and data interception, so most browsers block these resources or show a mixed content warning.
Automatically converting HTTP resource requests to HTTPS is an easy way to solve this mixed content problem.

Blocking mixed content with the block-all-mixed-content directive is deprecated, and it conflicts with the upgrade-insecure-requests directive.

In a development environment, you usually use an HTTP address such as http://localhost:5173.
With the upgrade-insecure-requests directive, even a local development server like this would have to be redirected to HTTPS, but common browsers such as Chrome and Firefox make an exception so that HTTP requests still work.
Safari, however, is a well-known browser that makes no such exception and strictly tries to redirect HTTP requests to HTTPS.
So when http://localhost is redirected to https://localhost, you can no longer reach a local development server that does not support HTTPS.

The problem in the Safari browser

A simple way to fix this is to remove the Upgrade Insecure Requests directive from your local development environment.
Apply the directive conditionally, for example with environment variables, so that it is only used in production.

Vite configuration example

Inject the CSP meta tag conditionally at build time.

BASH
1
npm i -D vite-plugin-html
/vite.config.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { defineConfig } from 'vite' import { createHtmlPlugin } from 'vite-plugin-html' const tags = [] if (process.env.NODE_ENV === 'development') { tags.push({ tag: 'meta', attrs: { httpEquiv: 'Content-Security-Policy', content: 'upgrade-insecure-requests' } }) } export default defineConfig({ plugins: [ createHtmlPlugin({ inject: { tags } }) ] })

Next.js configuration example

Add the CSP header to requests for every path.

/next.config.ts
TS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import type { NextConfig } from 'next' const nextConfig: NextConfig = { async headers() { return [ { source: '/:path*', headers: [ { key: 'Content-Security-Policy', value: process.env.NODE_ENV === 'production' ? 'upgrade-insecure-requests' : '' } ] } ] } } export default nextConfig

SvelteKit configuration example

Add the CSP meta tag conditionally in the application.

/app/routes/+layout.svelte
SVELTE
1
2
3
4
5
6
7
8
9
10
11
<script lang="ts"> import { dev } from '$app/environment' </script> <svelte:head> {#if !dev} <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> {/if} </svelte:head>

HTTPS development server example

The first time you run the local Vite development server, you may be asked to enter your password, and a local certificate is installed on your system and browsers.
After that, you can access the server at https://localhost.

BASH
1
npm i -D vite-plugin-mkcert
/vite.config.ts
TS
1
2
3
4
5
6
7
8
import { defineConfig } from 'vite' import mkcert from 'vite-plugin-mkcert' export default defineConfig({ plugins: [ mkcert() ] })