Eintausend Dollar. So viel Bounty hat Google an Salvatore Gulizia gezahlt, den Forscher hinter dem Handle Serotav, für die Meldung dessen, was später zu CVE-2026-85046 wurde, ein Type-Confusion-Bug in V8, den Kriminelle bereits ausnutzten, bevor der Fix die meisten Rechner erreichte. Der gepatchte Build, Chrome 152.0.7977.82, ging am 3. September 2026 raus. CISA nahm die Lücke am Tag darauf in ihren Katalog der Known Exploited Vulnerabilities auf und setzte US-Bundesbehörden eine Frist bis zum 18. September. CVSS 8.8, der sechste Chrome-Zero-Day des Jahres mit bestätigter aktiver Ausnutzung. Du fragst dich zu Recht, warum ein PHP-Blatt seine Kolumne auf einen Browser-Bug verwendet. Weil die Chancen gut stehen, dass deine PHP-Anwendung einen Browser ausliefert und niemand in deinem Team fürs Patchen zuständig ist.

Lass mal ein schnelles mentales grep über deinen eigenen Stack laufen. spatie/browsershot, das Rechnungen und Versandetiketten über Puppeteer rendert. Laravel Dusk oder Symfony Panther, die in deiner Testsuite ein echtes Chromium steuern. Ein Playwright-Container in der CI-Pipeline. Vielleicht ein Scraper, der Lieferantenseiten abruft und sie für das Shop-Backend in strukturierte Daten verwandelt. Jedes einzelne davon zieht einen kompletten Chromium-Build mit, und dieses Binary stellt alles in deinem vendor-Verzeichnis zusammengenommen in den Schatten. Trotzdem meldet composer audit grün, Dependabot bleibt stumm, und das Image, das du im März gebaut hast, läuft immer noch mit dem Chromium, mit dem es geboren wurde. Wir haben ein Jahrzehnt damit verbracht, die PHP-Dependency-Hygiene zu professionalisieren, und die größte ausführbare Datei im Container marschiert an all dem einfach vorbei.

Das Advisory bietet einen Trost: Dieser Bug allein liefert Codeausführung im gesandboxten Renderer-Prozess, und um daraus eine vollständige Kompromittierung des Hosts zu machen, braucht es eine Kette mit einem separaten Sandbox-Escape. Fair genug für ein Desktop-Chrome. Jetzt öffne mal dein Dockerfile. Siehst du das --no-sandbox-Flag, das du gesetzt hast, weil Chromium seine Sandbox im Container ohne zusätzliche Privilegien nicht starten will und jedes Browsershot-Tutorial im Internet dir geraten hat, es hinzuzufügen? Glückwunsch, der Trost gilt für dich nicht mehr. In diesem Setup ist Codeausführung im Renderer Codeausführung in deinem Worker, mit der Umgebung deines Workers: der Datenbank-DSN, die S3-Keys, die internen API-Tokens, die in $_ENV liegen wie Obst in einer Schale.

Der stärkste Einwand verdient eine ehrliche Antwort: Mein Render-Worker surft doch nicht auf Angreifer-Websites, ich kontrolliere das HTML, das er zu sehen bekommt. Tust du das wirklich? Der Auslöser für CWE-843 ist JavaScript in einer präparierten Seite, das eine Verwechslung im Compiler ausnutzt, bei der ein Array aus normalen Objekten eine Map verpasst bekommt, die für Small Integers gedacht war, was in beliebige Lese- und Schreibzugriffe auf dem JS-Heap kaskadiert. Dein Rechnungstemplate interpoliert Kundennamen, Adressen, Freitext-Bestellnotizen. Dein Scraper holt Seiten von Dritten, deren ganzer Job es ist, außerhalb deiner Kontrolle zu liegen. Solange du JavaScript beim Rendern nicht explizit abschaltest, und das tut fast niemand, weil Print-Stylesheets und Chart-Bibliotheken es brauchen, sitzt ungeprüfte Eingabe nur einen Template-Render vom JIT in V8 entfernt.

Und das war nicht das letzte Mal. Zwei der fünf früheren ausgenutzten Chrome-Zero-Days von 2026 steckten ebenfalls in V8. Diese Engine spekuliert aggressiv über Objektformen, um JavaScript schnell zu machen, und jede Spekulation ist eine Annahme, die sich brechen lässt. Google fuzzt sie, härtet sie, zahlt Bounties dafür, und die Bugs kommen trotzdem, weil das Design auf heißen Pfaden Sicherheitsmargen gegen Geschwindigkeit tauscht. Desktop-Chrome fängt das per Auto-Update ab, meist bevor die Nutzer etwas merken. Deine gepinnte Puppeteer-Version im package.json-Geschwisterchen deiner composer-Dateien fängt gar nichts ab. Sie wartet, geduldig verwundbar, bis ein Mensch sie hochzieht, das Image neu baut und die Queue-Worker neu deployt.

Hier ist also, was ich tatsächlich ändern würde. Erstens: Befördere den Browser zu einer benannten, versionierten Produktionsabhängigkeit mit einem Verantwortlichen, so wie du die PHP-Runtime selbst behandelst. Wenn Chrome Stable sich aus einem Sicherheitsgrund bewegt, sollte ein CI-Job laut scheitern, bis dein gebündeltes Chromium sich mitbewegt. Zweitens: Steck ihn in Quarantäne. Ein Render-Schritt sollte ein langweiliger kleiner Dienst sein, der auf der einen Seite HTML annimmt und auf der anderen PDF-Bytes ausspuckt, ohne Credentials, die sich zu stehlen lohnen, und ohne Weg ins Internet, der sich missbrauchen ließe. Drittens, und das ist der unbequeme Punkt: Frag dich, ob der Browser überhaupt in die Pipeline gehört. Eine zweispaltige Rechnung mit Logo braucht keinen JIT-Compiler. dompdf oder mpdf zeichnen sie, ohne je eine Zeile JavaScript auszuführen, und ja, du wirst über deren CSS-Support fluchen, und ja, dieser Tausch kann trotzdem der richtige sein.

Genau bei diesem Tausch will ich deinen Widerspruch wirklich hören. Ich habe beides ausgeliefert: pixelgenaue, mit Chromium gerenderte Dokumente, die die Buchhaltung glücklich gemacht haben, und native PHP-PDFs, die das Security-Review kurz gemacht haben. Nachdem ich zugesehen habe, wie V8 in neun Monaten seinen sechsten ausgenutzten Zero-Day produziert hat, neige ich dazu, den Browser für die wenigen Dokumente zu reservieren, die ihn wirklich verdienen. Wo landest du? Ist originalgetreues Print-CSS es wert, eine Browser-Engine mit dieser Patch-Kadenz zu babysitten, oder bist du zu einer PHP-nativen PDF-Bibliothek zurückgekehrt und hast mit ihren Macken Frieden geschlossen? Erzähl mir, worauf deine Dokumenten-Pipeline läuft, und warum.