Banner-Hintergründe CSP-fest: <img> statt Inline-Variable

Beim ersten Deploy blieben die Slides des Banner-Sliders schwarz. Ursache war
kein fehlendes Bild, sondern die CSP: public/.htaccess setzt
default-src 'self' ohne style-src, der Browser verwirft damit jedes
style-Attribut — und die Komponente lieferte Bild und Akzentfarbe genau so
(style="--banner-bg: url(…); --banner-accent: #6cbbbc"). Lokal war das
unsichtbar, weil php -S keine .htaccess liest.

Der Hintergrund ist jetzt ein <img class="ad-banner__bg"> über die bestehende
img-Komponente (CSP erlaubt img-src) und bringt srcset mit: das Handy lädt die
640er statt der 1600er, die erste Slide lädt eager mit fetchpriority. Der
dunkle Leseverlauf liegt auf ::before statt im background-image.

Die Akzentfarbe kann als beliebiger Hex-Wert nicht ohne Inline-Style in die
Seite. Sie steht deshalb als Token in tokens.css (wo Farben laut CLAUDE.md
hingehören), components.css bildet den Namen darauf ab, und banners.json nennt
nur noch "accent": "breadcrumb".

bin/preflight.php bricht ab, wenn unter app/ wieder ein style-Attribut
auftaucht — der Fehler wäre sonst erneut erst in Produktion sichtbar. Beim
Bauen dieser Wache zweimal selbst hineingetappt: ein Muster mit [^>]* bricht an
jedem schließenden PHP-Tag ab, und die Zeichenfolge dieses Tags beendet den
PHP-Modus auch in einem //-Kommentar (preflight gab ab dort seinen eigenen
Quelltext aus, php -l meldete nichts).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
This commit is contained in:
2026-07-31 16:50:43 +02:00
parent e8296a465b
commit a3cfd4941d
8 changed files with 169 additions and 33 deletions

View File

@@ -296,6 +296,49 @@ if ($cronKey === '' || $cronKey === 'CHANGE_ME') {
}
}
// --- Inline-Styles (die CSP verwirft sie stillschweigend) ---
// Diese Prüfung existiert wegen eines echten Ausfalls am 31.07.2026: der
// Banner-Slider setzte sein Hintergrundbild per style="--banner-bg: url(…)". Auf
// echtem Apache greift die CSP aus public/.htaccess (default-src 'self', KEIN
// style-src), der Browser verwarf das Attribut, die Slides blieben schwarz. Lokal
// war das unsichtbar, weil `php -S` keine .htaccess liest — der Fehler konnte also
// erst in Produktion auffallen. Genau davor schützt diese Wache.
// Hinweis: per JavaScript gesetzte Styles (element.style.…) sind NICHT betroffen,
// die CSP prüft nur vom HTML-Parser gelesene style-Attribute und <style>-Blöcke.
/* Zeilenweise und ohne Tag-Kontext geprüft: ein Muster, das beim öffnenden Tag
ansetzt und mit [^>]* weitersucht, bricht sofort ab — PHP-Templates enthalten
über das schließende PHP-Tag ständig eine spitze Klammer. Ein style-Attribut in
einer PHP-Datei ist praktisch immer echtes Markup, diese Prüfung genügt also.
Dieser Kommentar ist bewusst ein Blockkommentar: die Zeichenfolge des schließenden
PHP-Tags beendet den PHP-Modus AUCH in einem //-Kommentar. Genau daran ist diese
Prüfung beim Bauen einmal gescheitert (preflight gab ab dort seinen eigenen
Quelltext aus, und php -l meldete nichts, weil das gültiges PHP mit HTML ist). */
$inlineStyles = [];
$phpDateien = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ROOT_PATH . '/app'));
foreach ($phpDateien as $datei) {
if (!$datei->isFile() || $datei->getExtension() !== 'php') {
continue;
}
foreach (file($datei->getPathname()) ?: [] as $nr => $zeile) {
$roh = ltrim($zeile);
// Kommentarzeilen überspringen — dieser Abschnitt selbst nennt style=" ja auch.
if ($roh === '' || str_starts_with($roh, '*') || str_starts_with($roh, '//') || str_starts_with($roh, '/*')) {
continue;
}
if (preg_match('/\sstyle\s*=\s*"/i', $zeile) === 1) {
$kurz = str_replace(ROOT_PATH . '/', '', $datei->getPathname());
$inlineStyles[] = $kurz . ':' . ($nr + 1);
break;
}
}
}
if ($inlineStyles !== []) {
$errors[] = 'Inline-Style im Markup — die CSP (kein style-src) verwirft ihn, die Regel greift NUR in Produktion: '
. implode(', ', $inlineStyles)
. '. Bilder als <img> ausgeben, Farben als Token in tokens.css plus Klasse in components.css.';
}
// --- Ausgabe ---
foreach ($warnings as $w) {
echo "WARNUNG {$w}\n";