Astro 6 Architektur: Islands, Server Islands & Content Layer API im Detail
Technischer Deep-Dive in Astro 6: Islands Architecture, Server Islands für dynamische Inhalte, die neue Content Layer API und View Transitions mit ClientRouter.
Rechtliches & Info
CSP richtig einsetzen: Hash-basierte vs. nonce-basierte Direktiven, strict-dynamic für Third-Party-Scripts, report-only Modus und der CSP-Workflow in Astro-Projekten.
Content Security Policy (CSP) ist eine der effektivsten Verteidigungslinien gegen Cross-Site-Scripting (XSS). Aber eine falsch konfigurierte CSP ist entweder wirkungslos oder bricht die eigene Anwendung. Dieser Artikel erklärt die Konzepte — Hashes, Nonces, strict-dynamic — und zeigt einen produktionstauglichen Workflow für Astro-Projekte.
'unsafe-inline' keine Option istEine CSP mit script-src 'unsafe-inline' ist de facto keine CSP. Sie erlaubt die Ausführung beliebiger Inline-Scripts — exakt das, was ein XSS-Angriff ausnutzen will.
# Wirkungslos — erlaubt allen Inline-Script-Ausführung:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
# Sinnvoll — nur explizit erlaubte Scripts:
Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abc123'
Das Problem: viele Frameworks, Analytics-Tools und CMS injizieren Inline-Scripts. Die Lösung sind Hashes oder Nonces.
Ein Hash erlaubt genau das Script, dessen Inhalt dem Hash entspricht. Ändert sich das Script, ist der Hash ungültig — der Browser blockiert die Ausführung.
# SHA-256 Hash des genauen Script-Inhalts:
Content-Security-Policy: script-src 'sha256-base64encodedHash='
In Astro können Hashes automatisch generiert werden. Ein Workflow mit einem Build-Script:
// scripts/generate-csp-hashes.mjs
import { createHash } from 'crypto';
import { readFileSync, writeFileSync } from 'fs';
import { glob } from 'glob';
import { parse } from 'node-html-parser';
const distFiles = await glob('dist/**/*.html');
const hashes = new Set();
for (const file of distFiles) {
const html = readFileSync(file, 'utf-8');
const root = parse(html);
root.querySelectorAll('script:not([src])').forEach((script) => {
const content = script.text.trim();
if (!content) return;
const hash = createHash('sha256').update(content).digest('base64');
hashes.add(`'sha256-${hash}'`);
});
}
const cspValue = `script-src 'self' ${[...hashes].join(' ')};`;
console.log(cspValue);
// Output in _headers schreiben oder in Netlify-Config eintragen
Der Hash-Ansatz ist besonders geeignet für statisch generierte Sites (SSG), bei denen sich der Script-Inhalt zwischen Deploys nicht ändert.
Ein Nonce (Number used Once) ist ein kryptografisch zufälliger Wert, der pro Request generiert wird. Nur Scripts mit dem gleichen Nonce-Wert dürfen ausgeführt werden.
# Server setzt Header mit zufälligem Nonce:
Content-Security-Policy: script-src 'nonce-r4nd0m8yt3s'
<!-- Script muss denselben Nonce haben -->
<script nonce="r4nd0m8yt3s">
// Erlaubt
</script>
<script>
// Blockiert — kein Nonce
</script>
Nonces erfordern Server-Side-Rendering — bei statischen Sites ist Hash der richtige Ansatz.
In Astro mit SSR-Adapter (Netlify):
---
// layouts/Layout.astro
const nonce = crypto.randomUUID().replace(/-/g, '');
const csp = `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';`;
---
<html>
<head>
<!-- CSP als meta-Tag oder besser als HTTP-Header -->
<meta http-equiv="Content-Security-Policy" content={csp} />
</head>
<body>
<script nonce={nonce}>
// Erlaubt, weil Nonce übereinstimmt
window.__INIT__ = true;
</script>
<slot />
</body>
</html>
Wichtig: CSP als HTTP-Header ist stärker als als <meta>-Tag — HTTP-Header werden vor dem HTML-Parsing verarbeitet. Für Netlify-Projekte gehört die CSP in public/_headers.
'strict-dynamic' löst das größte praktische Problem mit CSP: Third-Party-Scripts, die selbst weitere Scripts laden.
Ohne strict-dynamic muss jede Third-Party-Domain explizit in der CSP stehen — und jedes dynamisch geladene Script ebenfalls. Das ist nicht praktikabel und wird oft zur Whitelist-Sammlung, die jeden XSS-Angriff erlaubt, der über eine gewhitelistete Domain kommt.
Mit 'strict-dynamic' gilt: wenn ein Script mit gültigem Nonce oder Hash weitere Scripts lädt, vertraut der Browser auch diesen geladenen Scripts — aber nicht anderen Inline-Scripts.
Content-Security-Policy:
script-src 'nonce-abc' 'strict-dynamic';
<!-- Basis-Script mit Nonce -->
<script nonce="abc" src="https://analytics.example.com/loader.js"></script>
<!-- loader.js lädt dynamisch weitere Scripts -->
<script>
const s = document.createElement('script');
s.src = 'https://analytics.example.com/tracker.js';
document.head.appendChild(s);
// Erlaubt mit strict-dynamic — kein explizites Allow nötig
</script>
Bevor man eine strikte CSP produktiv schaltet, empfiehlt sich der Content-Security-Policy-Report-Only-Header:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-abc'; report-uri /csp-violations
Der Browser führt alle Requests normal durch, meldet aber Verstöße gegen die Policy an den report-uri-Endpunkt. So kann man sehen, was blockiert würde, ohne die Seite zu brechen.
Für Astro/Netlify-Projekte ohne eigenen Server kann man CSP-Violations an einen externen Service wie report-uri.com senden.
# Nach dem Build:
npm run build
# Hashes aus dist/ extrahieren:
node scripts/generate-csp-hashes.mjs >> csp-hashes.txt
# public/_headers
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-HASH1=' 'sha256-HASH2='; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.brevo.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Häufige Quellen von CSP-Verstößen in Astro-Projekten:
onclick="...") — durch JavaScript-Events ersetzenstyle-Attribute — durch Tailwind-Klassen ersetzen (Pflicht für 'unsafe-inline'-Freiheit)font-src fonts.gstatic.com whitelistAnti-Feature: X-XSS-Protection-Header setzen
# FALSCH — dieser Header ist veraltet und riskant:
X-XSS-Protection: 1; mode=block
# Er ist in modernen Browsern nicht mehr vorhanden.
# In alten Edge/IE-Versionen kann er mXSS-Angriffe ermöglichen.
# CSP ersetzt ihn vollständig. Einfach weglassen.
Anti-Feature: Wildcard in script-src
# FALSCH:
script-src 'self' *.googleapis.com *.gstatic.com
# Diese Whitelist erlaubt beliebige Scripts von Google-Domains —
# ein Angreifer, der eine beliebige Google-Subdomain kompromittiert,
# kann XSS einschleusen.
Security-Audit für Ihre Web-App? Wender Media prüft CSP-Konfigurationen und HTTP-Sicherheitsheader — systematisch und ohne Ausfallrisiko. Anfrage an info@wendermedia.info.
Wender Media unterstützt Sie bei der praktischen Umsetzung — von der technischen Konzeption bis zum Launch. Schreiben Sie uns, wir antworten innerhalb von 24 Stunden.
Jetzt Beratung anfragenKostenlos & unverbindlich — info@wendermedia.info
Technischer Deep-Dive in Astro 6: Islands Architecture, Server Islands für dynamische Inhalte, die neue Content Layer API und View Transitions mit ClientRouter.
Was sich mit Tailwind CSS 4 wirklich ändert: CSS-first Konfiguration mit @theme, der Oxide Compiler, Container Queries, color-mix() und die Migration von v3.
TypeScript strict vollständig in Astro-Projekten nutzen: das strict-Flag, der satisfies-Operator, typsichere Props mit Generics und das Typsystem der Content Collections.