Serve the interface font from the installation, not from a font CDN

app.blade.php is the root template for all three interfaces, and it opened
with two lines pointing at a third party:

    <link rel="preconnect" href="https://fonts.bunny.net">
    <link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />

Every visitor to /login, /register, /forgot-password, /s/{token} and every
public listing page therefore made a request to a host the operator did
not choose and could not switch off, before they had done anything at all
-- handing it their IP address, their user agent, and through Origin the
hostname of the installation they were visiting. On the signed-out pages
that is a visitor who has agreed to nothing, and an operator who often has
told their own users that this server is where their files live.

There was no self-hosted copy in the repository, no setting, no mention in
INSTALL.md, DOCKER.md or SECURITY.md, and no SRI on the tag.

The font now ships with the application, through @fontsource/instrument-sans
-- the same font, the same three weights the URL asked for, from a
versioned dependency rather than binaries pasted into the repository.
Vite fingerprints and emits them like any other asset.

Cost, measured on this build: twelve files, 192 KB on disk. A browser
fetches only woff2 and only the subsets it needs, which is 73 KB for all
six woff2 files together and typically 41 KB (latin, three weights) for a
page in English. Against that, every page load loses a DNS lookup, a TLS
handshake and a round trip to another origin, so signed-out pages get
faster rather than slower.

This is a privacy change rather than a vulnerability fix, and worth saying
plainly: the share token does not leak this way. Referrer-Policy:
strict-origin-when-cross-origin is set in both nginx configs and in the
INSTALL.md snippet, so the path never travelled in the Referer. What
travelled was the visit itself.

Not changed: public/.htaccess still sets no security headers at all, so an
Apache installation has no Referrer-Policy. That is a real gap and a
separate change.

Verified: `npm run build` succeeds and emits the faces; no reference to
the CDN survives anywhere in public/build; `tsc --noEmit` and prettier are
clean. No test asserts on the font, before or after.
This commit is contained in:
denkfabrik-li
2026-08-29 00:14:23 +02:00
parent 81bb136e9e
commit 7264c44fd7
4 changed files with 27 additions and 3 deletions
+10
View File
@@ -11,6 +11,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/instrument-sans": "^5.3.0",
"@headlessui/react": "^2.2.0",
"@inertiajs/react": "^2.0.0",
"@radix-ui/react-avatar": "^1.1.3",
@@ -1160,6 +1161,15 @@
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@fontsource/instrument-sans": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource/instrument-sans/-/instrument-sans-5.3.0.tgz",
"integrity": "sha512-QwXc4hb/px3XvSPS2CPAOgey8nyrQFxgxrmXQQ+pN+P51hKdAcchxpg8rSbjANFfPu6VKXhDqkVDucXMZ9CM5g==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@headlessui/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz",
+1
View File
@@ -30,6 +30,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/instrument-sans": "^5.3.0",
"@headlessui/react": "^2.2.0",
"@inertiajs/react": "^2.0.0",
"@radix-ui/react-avatar": "^1.1.3",
+16
View File
@@ -1,5 +1,21 @@
@import 'tailwindcss';
/*
* Instrument Sans, served by this installation rather than by a font CDN.
*
* The three weights the design uses, which is what the stylesheet this
* replaces asked for. Every visitor to /login, /register, /forgot-password
* and every public share link used to make a request to a third party
* before they had done anything, handing it their IP, their user agent and
* the hostname of the installation they were visiting on the signed-out
* pages, where the visitor has not agreed to anything and the operator
* never chose the recipient. Bundling it makes those pages talk only to
* the server they were asked for.
*/
@import '@fontsource/instrument-sans/400.css';
@import '@fontsource/instrument-sans/500.css';
@import '@fontsource/instrument-sans/600.css';
@plugin 'tailwindcss-animate';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
-3
View File
@@ -33,9 +33,6 @@
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
@routes
@viteReactRefresh
@php