Leaflet + Stadia Maps hinter strikter CSP: zwei Stolpersteine
Eine Foto-Weltkarte mit Leaflet und Stadia-Maps-Tiles auf einer Astro-Seite hinter Caddy einbauen — und warum es trotz korrekter CSP erstmal nicht ging.
Kategorie: Technik
Für meine Foto-Sektion wollte ich eine Weltkarte haben, auf der alle Aufnahmeorte aus den EXIF-Koordinaten der Fotos eingezeichnet sind. Technisch unspektakulär: Leaflet plus leaflet.markercluster, Tiles von Stadia Maps, Marker aus den vorhandenen Sidecar-JSONs der Fotos. Astro rendert die Seite statisch, das Client-Script baut die Karte beim Load auf.
Lokal lief das auf Anhieb. In Produktion kam nichts — nur eine graue Fläche mit den Markern. Zwei unabhängige Probleme, beide mit den Security-Headern aus meiner Caddy-Config zu tun.
Problem 1: img-src 'self' blockt die Tiles
Die CSP in der Caddy-Config war bewusst restriktiv:
Content-Security-Policy "default-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; font-src 'self'; connect-src 'self'; ..."
Leaflet lädt Tiles als ganz normale <img>-Elemente — also schlägt die Direktive img-src 'self' data: unbarmherzig zu, sobald der Host nicht die eigene Domain ist.
Fix: Den Stadia-Maps-Host explizit in die img-src-Direktive aufnehmen. Bei mir liegt die Site-Config unter /etc/caddy/sites/adrian-altner.de.caddy:
- img-src 'self' data:;
+ img-src 'self' data: https://tiles-eu.stadiamaps.com;
connect-src muss nicht angefasst werden — Leaflet benutzt keine fetch- oder XHR-Requests für die Tiles, nur <img>-Tags.
Ablauf zum Übernehmen (Backup → Validate → Reload):
sudo cp /etc/caddy/sites/adrian-altner.de.caddy \
/etc/caddy/sites/adrian-altner.de.caddy.pre-csp-tiles
sudo vim /etc/caddy/sites/adrian-altner.de.caddy # img-src-Zeile patchen
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
caddy validate parst die Config komplett durch, ohne den laufenden Dienst anzufassen — Tippfehler fallen hier auf, nicht erst beim Reload.
Problem 2: Referrer-Policy "no-referrer" produziert 401
Nach dem Caddy-Reload lud die Karte — aber die Tiles zeigten nur noch QR-Codes und “401 Error · Invalid Authentication · Learn more at docs.stadiamaps.com/authentication”. Stadia Maps nutzt für die Domain-basierte Authentifizierung den Referer-Header, um zu prüfen, von welchem Host der Request kommt. Genau diesen Header hatte ich aber mit Referrer-Policy "no-referrer" global abgeschaltet.
Die globale Policy aus DSGVO-Gründen aufzuweichen wollte ich nicht. Also brauchte es eine Ausnahme nur für die Tile-Requests. Der Weg darüber: das referrerpolicy-Attribut direkt am jeweiligen <img>-Element — das überschreibt die Dokumenten-Policy.
Leaflets TileLayer unterstützt genau das seit 1.9:
L.tileLayer('https://tiles-eu.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png', {
attribution: '…',
maxZoom: 20,
referrerPolicy: 'origin',
}).addTo(map);
'origin' sendet nur den Origin (also https://adrian-altner.de), keine Pfade, keine Query-Parameter — genug für Stadia Maps, um die Domain zu verifizieren, aber minimal im Sinne der Referrer-Policy.
Was ich mir mitnehme
- CSP-Ausnahmen für Third-Party-Maps sind unvermeidbar. Sobald Kartendienste Tiles oder Glyphs ausliefern, musst du deren Host in
img-src(und bei WebGL-basierten Libs wie MapLibre auch inworker-srcundconnect-src) freigeben. - Referrer-Policy und Referer-basierte Auth beißen sich. Services wie Stadia Maps, Mapbox oder Google Maps nutzen den Referer als Domain-Nachweis. Eine globale
no-referrer-Policy zerstört das stillschweigend. Per-Element-Overrides sind der saubere Mittelweg. - Lokale Entwicklung ist keine Referenz. Stadia Maps (und viele andere) erlauben
localhostund127.0.0.1ohne Authentifizierung — der Bug taucht erst in Produktion auf. Teste CSP-relevante Dinge nicht nur im Dev-Server.