Der bekannteste Trick der CDP-Erkennung, ein Getter auf Error.stack, der feuert, sobald ein Automatisierungs-Client Runtime.enable aufgerufen hat, funktioniert nicht mehr, seit zwei V8-Inspector-Commits vom Mai 2025 mit Chrome 138 ausgeliefert wurden. Unsichtbar sind per CDP gesteuerte Browser dadurch nicht geworden. Forscher haben seitdem andere Wege gefunden, eine aktivierte Runtime-Domain zu beobachten, und die meisten kommerziellen Erkennungslösungen stützen sich ohnehin auf Framework-Artefakte, Headless- und Umgebungsmerkmale, Eigenschaften der Eingabe sowie Verhaltens- und Netzwerksignale, die nie von diesem einen Check abhingen.
Was CDP-Erkennung ist und warum jedes große Framework betroffen ist
CDP-Erkennung bedeutet festzustellen, dass ein Browser über das Chrome DevTools Protocol gesteuert wird und nicht von einem Menschen. Das gelingt, weil das Protokoll fürs Debugging gebaut wurde und manche Debugger-Funktionen Spuren hinterlassen, die JavaScript auf der Seite beobachten kann.
Das Chrome DevTools Protocol ist die WebSocket-API, die auch das DevTools-Fenster selbst nutzt: Domains wie Runtime, Page, Network und Input, jeweils mit Befehlen und Events. Der Großteil der Chromium-Automatisierung läuft darüber:
- Puppeteer steuert Chrome über CDP. Für Firefox verwendet es inzwischen WebDriver BiDi.
- Playwright nutzt CDP für Chromium. Für Firefox verwendet es Juggler, ein eigenes Protokoll innerhalb von Firefox.
- Selenium 4 spricht WebDriver mit ChromeDriver, der Chrome intern über CDP steuert; zusätzlich stellt Selenium CDP-Befehle direkt bereit, während es auf BiDi umsteigt.
Firefox hat CDP inzwischen hinter sich gelassen. Mozilla hat seine CDP-Implementierung in Firefox 129 als veraltet markiert und in Firefox 141 vollständig entfernt. „CDP-Erkennung“ heißt 2026 also: automatisierte Browser der Chromium-Familie erkennen.
Warum diese Signalklasse für Verteidiger so attraktiv war, hat DataDome in seiner Forschung vom Juni 2024 (übernommen aus dem eigenen Threat-Research-Blog) formuliert: „Being able to detect that a browser is instrumented with CDP is key to detect most modern bot frameworks.“ Seit das neue Headless-Chrome dieselbe Codebasis wie das reguläre Chrome nutzt, sind die meisten Fingerprint-Inkonsistenzen verschwunden. Ein Nebeneffekt auf Protokollebene würde dagegen Puppeteer, Playwright, Selenium und jedes unbekannte Framework auf derselben Grundlage erwischen.
Wie der klassische Runtime.enable-Check funktionierte
Der Check nutzte aus, dass V8s Inspector bei aktivierter Runtime-Domain jedes Console-Argument serialisiert, um dem Client eine Vorschau zu schicken. Beim Serialisieren eines Objekts werden seine Eigenschaften gelesen, und das Lesen einer Eigenschaft mit Getter führt Seitencode aus.
Die von Castle dokumentierte Variante sah so aus:
let wasAccessed = false;
const e = new window.Error();
Object.defineProperty(e, 'stack', {
configurable: false,
enumerable: false,
get: function () {
wasAccessed = true;
return '';
}
});
console.debug(e);
// wasAccessed === true => etwas lauscht auf der Runtime-Domain
Schritt für Schritt:
- Puppeteer, Playwright und ChromeDriver rufen
Runtime.enableauf, weil sieRuntime.executionContextCreated-Events brauchen, um zu wissen, wo sie Skripte ausführen können. Die Protokolldokumentation beschreibt den Befehl als Aktivierung des „reporting of execution contexts creation“. - Sobald die Domain aktiv ist, erzeugt jeder
console.*-Aufruf einRuntime.consoleAPICalled-Event, und V8 baut dafür eine Vorschau jedes Arguments. - Bei einem
Errorbedeutete das Erstellen der Vorschau,.stackzu lesen (zusammen mit.nameund.message). Die Seite hatte.stackdurch einen Getter ersetzt, also lief der Getter und setzte das Flag. - In einem gewöhnlichen Browser ohne angehängten Client wird keine Vorschau gebaut, und der Getter läuft nie.
Ein paar Zeilen JavaScript, praktisch kostenlos, und jedes Framework, das Runtime.enable aufrief, ging ins Netz – egal, was es an navigator.webdriver oder am User-Agent veränderte. Stealth-Plugins, die JavaScript-Eigenschaften patchen, konnten daran nichts ändern, weil das Leck im Protokoll lag, nicht in der Seite. Die Gegenmaßnahme entstand auf Treiberebene: rebrowser-patches und Patchright vermeiden den Aufruf von Runtime.enable, und DataDome stellte 2024 fest, dass nodriver und Selenium Driverless „low-level CDP commands that do not leverage Runtime.enable“ verwenden.
Was V8 2025 geändert hat
V8 hat den Nebeneffekt im Mai 2025 mit zwei Commits beseitigt: Der Inspector führt beim Erstellen von Vorschauen für Objekte und Errors keine benutzerdefinierten Getter mehr aus. Der Fix kam mit V8 13.8, das in Chrome 138 ausgeliefert wurde (Stable seit 24. Juni 2025).
61a90754(7. Mai 2025), „[inspector] Prevent side effects during object preview“, der vermeidet, „running user code when enabling runtime domain“.e08e9734(9. Mai 2025), „[inspector] Prevent side effects during error preview“, der die Erzeugung von Fehlerbeschreibungen ändert, „to avoid running getters with side effects“.
Beide Commits verweisen auf Chromium-Bug 415094795. Den ersten schrieb Pavel Feldman, Playwright-Maintainer bei Microsoft – die Änderung kam also aus dem Lager der Automatisierungswerkzeuge, nicht von einem Stealth-Projekt. Antoine Vastel von Castle beschrieb das am 28. August 2025 unter dem Titel „Why a classic CDP bot detection signal suddenly stopped working (and nobody noticed)“ und verwies auf einen neuen Helfer getErrorProperty(), der Benutzer-Getter während der Vorschau überspringt. Ab Chrome 138 liefert das obige Snippet selbst unter unverändertem Puppeteer false.
Ist Runtime.enable noch erkennbar?
Wahrscheinlich ja, aber weniger zuverlässig als früher. Im März 2026 veröffentlichte der unabhängige Forscher Sveba eine Analyse des Vorschaupfads in V8, die zwei verbleibende Wege zeigt. Der eine ist ein Codepfad, den die Absicherung vom Mai 2025 übersieht, wenn GetOwnPropertyDescriptor fehlschlägt; der Autor schreibt, er habe „verified … is exploitable in post-patch Chrome“. Der andere nutzt einen Proxy in der Prototypkette eines Objekts:
let detected = false;
const trap = new Proxy({}, {
ownKeys() { detected = true; return []; }
});
const obj = Object.create(trap);
console.groupEnd(obj); // jede Console-Methode funktioniert
// detected === true => der Inspector erstellt Vorschauen
Laut der Analyse prüft der Inspector nur, ob der Wert selbst ein Proxy ist, nicht seine Prototypen, und sammelt alle Schlüssel vorab ein. Das Durchlaufen der Kette löst deshalb die ownKeys-Trap aus. Der Autor hat das auf einem content_shell-Build vom März 2026 getestet und es damals als ungepatcht gemeldet. Wir haben das nicht gegen jedes stabile Chrome-Release geprüft; betrachten Sie es also als berichtet, nicht als allgemeingültig.
Zwei Einschränkungen begrenzen, wie sehr sich Verteidiger heute auf ein Runtime.enable-Signal stützen können. Erstens durchläuft ein echter Nutzer mit geöffneten DevTools denselben Codepfad – das Signal sagt also „ein Inspector ist angehängt“, nicht „das ist ein Bot“. Zweitens vermeiden die Frameworks, die man am ehesten erwischen möchte, Runtime.enable bereits, sodass es vor allem unverändertes Puppeteer, Playwright und Selenium markiert. Die Branche ist dazu übergegangen, viele schwächere Signale zu kombinieren, wie auch Castles Beitrag festhält.
Signale, die 2026 noch funktionieren
Die Signale, die heute tragen, stammen aus dem, was das Framework in der Seite tut, wie der Browser gebaut und gestartet wurde, wie Eingaben ankommen und wie sich die Sitzung im Netzwerk verhält. Wer einen einzelnen CDP-Nebeneffekt versteckt, beseitigt keines davon.
Framework-Artefakte in der Main World
Unveränderte Frameworks hinterlassen eigene Fingerabdrücke. Der rebrowser bot detector listet mehrere auf:
__pwInitScripts: Playwright fügt es standardmäßig dem globalen Scope jeder Seite hinzu.sourceURL-Lecks: Puppeteer versieht ausgewertete Skripte mit//# sourceURL=pptr:…, was in einem Error-Stack auftauchen kann, der innerhalb einer gehookten Funktion erfasst wird.- Ausführung in der Main World: Ruft Ihr Skript
document.querySelectorin der eigenen World der Seite auf, sieht eine Seite, die diese Funktion umhüllt hat, den Aufruf. exposeFunction/ Bindings:page.exposeFunction()und Playwrightswindow.__playwright__binding__installieren Globals, die eine Seite aufzählen kann. Intern nutzen sieRuntime.addBinding, das eine Funktion „on the global objects of all inspected contexts“ hinzufügt.- Namen von Utility Worlds: Frameworks erzeugen Isolated Worlds mit wiedererkennbaren Namen, die sich mit rebrowser-patches umbenennen lassen.
Artefakte von Init-Skripten
Page.addScriptToEvaluateOnNewDocument führt ein Skript „in every frame upon creation (before loading frame's scripts)“ aus. So patchen Stealth-Plugins navigator, bevor der Code der Website läuft. Das Protokoll selbst leckt hier nicht, die Patches aber schon. Wie es in der Camoufox-README heißt, lässt sich injiziertes JavaScript über Object.getOwnPropertyDescriptor erkennen, daran, dass toString() einer Funktion nicht mehr [native code] liefert, oder an Werten, die sich zwischen Window und Worker unterscheiden.
Worker-Kontexte
In Workern scheitert das Patchen am häufigsten. Init-Skripte und Property-Overrides für die Seite erreichen Dedicated, Shared oder Service Worker nicht automatisch. Ein Worker, der den echten navigator.userAgent, hardwareConcurrency oder WebGL-Renderer meldet, während das Window gefälschte Werte liefert, ist ein starkes Indiz. CreepJS prüft genau deshalb gezielt den Worker-Scope.
Headless- und Build-Merkmale
- Headless Shell vs. neuer Headless-Modus. Chrome führte
--headless=newin Chrome 112 ein, und ab Chrome 132 existiert der alte Modus nur noch im separaten Binarychrome-headless-shell. Playwright verwendet im Headless-Betrieb standardmäßig die Chromium Headless Shell, und die verhält sich anders als echtes Chrome. - Build-Identität. Ein User-Agent „Chrome for Testing“, Standard-Viewports (800×600 bei Puppeteer, 1280×720 bei Playwright) und ein aktiviertes
navigator.webdriversind billige Checks, die der rebrowser-Detektor auflistet. - Codecs. Open-Source-Builds von Chromium enthalten laut dem Chromium-Projekt nicht die proprietären Codecs, die Google Chrome mitliefert, etwa H.264 und AAC. Ein Browser, der sich als Chrome ausgibt, bei
canPlayTypefür H.264 aber scheitert, ist in Wahrheit Chromium. - Berechtigungen und Mediengeräte. In Headless- und Container-Umgebungen passen Benachrichtigungsberechtigungen, leere Mediengerätelisten und fehlende Sprachausgabestimmen oft nicht zu dem, was der User-Agent behauptet. CreepJS und BrowserScan prüfen das.
Synthetische Eingaben
Mit Input.dispatchMouseEvent gesendete Eingaben erzeugen vertrauenswürdige Events (isTrusted ist true), hatten aber Eigenschaften, die echte Eingaben nicht haben. Am bekanntesten war, dass screenX/screenY außerhalb des Vollbildmodus gleich pageX/pageY war. Chromium hat das mit Change 6917162, „Fix screen coordinates to avoid automation detection“, behoben, gemergt am 15. September 2025 vor dem Branch-Point von Chrome 142. Das Projekt CDP-Patches, das genau dafür einen Workaround bot, gibt inzwischen an, dass CDP-Eingaben auch Coalesced Events erzeugen, und hat sich selbst archiviert. In aktuellem Chrome sind diese beiden konkreten Checks weitgehend hinfällig, während Analysen von Bewegungsbahn, Timing und Klickposition weiterhin greifen.
Verhalten und Netzwerk
Keines der folgenden Signale hat mit CDP zu tun, und sie machen den Großteil dessen aus, was Anbieter bewerten. Castle nennt clientseitige Verhaltensanalyse, Umgebungs-„Red Pills“, Proxy- und Netzwerkerkennung sowie Zeitreihenanalyse koordinierter Aktivität. Netzwerkseitig gehören dazu TLS-Fingerprints und HTTP/2-Fingerprints, die zum angegebenen Browser passen müssen, sowie die IP-Reputation. Fingerprinting auf GPU-Ebene behandelt unsere Tiefenanalyse zu Canvas und WebGL.
Wie Patchright, nodriver und Camoufox mit CDP-Lecks umgehen
Alle drei vermeiden Lecks nach Art von Runtime.enable, jeweils auf andere Weise, und keines löst das Problem von Verhalten oder Netzwerkreputation.
Patchright ist ein gepatchter Playwright-Treiber, nur für Chromium. Laut seiner README vermeidet der Hauptpatch Runtime.enable, „by executing Javascript in (isolated) ExecutionContexts“. Außerdem deaktiviert er die Console-Domain vollständig (Console-Logging funktioniert also nicht), lässt --enable-automation weg, ergänzt --disable-blink-features=AutomationControlled und empfiehlt, echtes Chrome (channel="chrome") ohne Fingerprint-Injection zu verwenden. Es folgt den Playwright-Releases eng (der Treiber v1.63.0 erschien im September 2026). Die Einrichtung beschreibt unser Patchright-Deep-Dive.
nodriver, der Nachfolger von undetected-chromedriver, verzichtet auf WebDriver und spricht CDP direkt mit Ihrem installierten Chrome. Ein Blick in den aktuellen core/-Quellcode zeigt, dass es mit Runtime.evaluate und Runtime.callFunctionOn auswertet und nie Runtime.enable aufruft, was zu DataDomes Beschreibung von 2024 passt. Der Preis: Die Auswertung findet in der Main World statt, wo eine Seite, die DOM-Funktionen umhüllt, sie sehen kann, und es gibt kein Fingerprint-Management. Das letzte PyPI-Release ist 0.50.3 vom Mai 2026. Siehe unseren nodriver-Leitfaden.
Camoufox umgeht CDP vollständig. Es ist ein Firefox-Fork, und Playwright steuert Firefox über Juggler, das Camoufox so patcht, dass Playwright auf einer isolierten Kopie der Seite arbeitet: Seitenskripte sehen weder injizierte Helfer, noch können sie Getter-Zugriffe beobachten. Eingaben laufen über die normalen Input-Handler von Firefox, und Fingerprint-Werte werden in C++ statt per Init-Skript gefälscht, was die Klasse der toString- und Worker-Abweichungen beseitigt. Der Preis ist, dass es eben Firefox ist. Laut README prüfen manche WAFs das Verhalten der SpiderMonkey-Engine, als Chrome kann es also nicht durchgehen.
Übersicht der Signale
| Signal | Funktioniert 2026 noch? | Was es aushebelt | Kosten des Aushebelns |
|---|---|---|---|
Error.stack-Getter über die Console (klassisch) | Nein ab Chrome 138 (V8 13.8) | Aktuelles Chrome | Keine |
| Andere Nebeneffekte der Inspector-Vorschau (z. B. Proxy in der Prototypkette) | Laut Berichten Stand März 2026 funktionsfähig; feuert auch bei echten Nutzern mit geöffneten DevTools | Kein Aufruf von Runtime.enable (Patchright, nodriver, rebrowser-patches) oder kein CDP (Camoufox) | Gering bis mittel: Treiberpatch, Console-API entfällt |
__pwInitScripts, Bindings, pptr:-sourceURL | Ja, gegen unveränderte Frameworks | Gepatchte Treiber; exposeFunction vermeiden | Gering |
| Hooks auf Ausführung in der Main World | Ja | Auswertung in Isolated Worlds | Mittel: kein direkter Zugriff auf Globals der Seite |
Per JS gepatchte Eigenschaften (toString, Deskriptoren) | Ja | Nicht patchen; echtes Chrome oder Spoofing auf C++-Ebene | Mittel bis hoch |
| Abweichung zwischen Window und Worker | Ja | Wie oben; Patches müssen Worker abdecken | Hoch, wenn Sie in JS patchen |
| Headless Shell / Chrome for Testing / Codecs | Ja | Markenversion von Chrome, Headful oder neuer Headless-Modus, virtuelles Display | Mittel: mehr RAM, Xvfb auf Servern |
| CDP-Eingaben: screenX == pageX, keine Coalesced Events | In aktuellem Chrome weitgehend behoben | Aktuelles Chrome | Keine |
| Verhalten und Timing | Ja, mit wachsendem Anteil an der Bewertung | Realistisches Tempo, weniger Anfragen pro Identität | Hoch: Durchsatz |
| TLS / HTTP/2 / IP-Reputation | Ja | Echter Browser-Netzwerkstack, saubere Residential- oder ISP-IPs | Laufende Proxy-Kosten |
So testen Sie Ihr eigenes Setup
Testen Sie zuerst gegen eine eigene Probe-Seite, dann gegen öffentliche Erkennungsseiten, und ändern Sie jeweils nur eine Variable. Öffentliche Seiten zeigen, was sie erkennen. Ihre eigene Seite zeigt, welche Kombination aus Chrome-Build und Treiber welchen Check noch auslöst.
1. Eine lokale Probe-Seite
Speichern Sie das als probe.html. Die Seite führt den klassischen Check, den Prototypketten-Check und einige billige Artefakt-Checks aus und schreibt die Ergebnisse ins DOM, damit jeder Treiber sie lesen kann – unabhängig davon, in welcher World seine Skripte laufen.
<!doctype html>
<pre id="out">running</pre>
<script>
function classic() {
let hit = false;
const e = new Error();
Object.defineProperty(e, 'stack', { configurable: false, enumerable: false,
get() { hit = true; return ''; } });
console.debug(e);
return hit;
}
function protoProxy() {
let hit = false;
const trap = new Proxy({}, { ownKeys() { hit = true; return []; } });
console.groupEnd(Object.create(trap));
return hit;
}
const r = {
classicStackGetter: classic(),
protoProxyPreview: protoProxy(),
webdriver: navigator.webdriver,
pwInitScripts: '__pwInitScripts' in window,
h264: document.createElement('video').canPlayType('video/mp4; codecs="avc1.42E01E"'),
ua: navigator.userAgent,
};
setTimeout(() => { document.getElementById('out').textContent = JSON.stringify(r, null, 2); }, 300);
</script>
Führen Sie sie dann unter jedem relevanten Setup aus:
import pathlib
from playwright.sync_api import sync_playwright # alternativ: from patchright.sync_api import sync_playwright
url = pathlib.Path("probe.html").resolve().as_uri()
with sync_playwright() as p:
browser = p.chromium.launch(channel="chrome", headless=False)
page = browser.new_page()
page.goto(url)
page.wait_for_function("document.getElementById('out').textContent !== 'running'")
print(page.text_content("#out"))
browser.close()
Testen Sie unverändertes Playwright, dann Patchright, dann dieselbe Seite in einem normalen Chrome-Fenster mit und ohne geöffnete DevTools. Der DevTools-Lauf ist Ihre Kontrollgruppe: Alles, was dort feuert, feuert auch bei echten Nutzern und ist für sich genommen ein schwaches Signal.
2. Öffentliche Erkennungsseiten
- rebrowser bot detector: auf Chromium ausgerichtet, deckt Runtime.enable, sourceURL, Ausführung in der Main World,
exposeFunction,__pwInitScripts, Viewport und User-Agent ab. Manche Tests feuern nur, wenn Ihr Skript diedummyFnder Seite aufruft oder eine Funktion bereitstellt, wie die Seite erklärt. - BrowserScan Bot Detection: meldet Checks zu WebDriver, User-Agent, CDP und Navigator-Konsistenz.
- CreepJS: Open-Source-Fingerprint-Analyse, die „Lies“ (gepatchte Eigenschaften), Headless-Merkmale und Abweichungen im Worker-Scope markiert. Am besten geeignet, um Spoofing auf JavaScript-Ebene aufzudecken.
- Device and Browser Info „Are you a bot?“: Fingerprint- und Verhaltenstests, darunter Checks auf CDP, WebDriver, Playwright und Headless-Chrome.
Führen Sie diese Tests über denselben Proxy aus, den Sie in der Produktion nutzen. Mehrere dieser Seiten zeigen IP und Zeitzone neben den Browsersignalen an, und ein perfekter Browser-Score auf einer Datacenter-IP sagt wenig über echten Traffic aus. Eine Sticky Session (USERNAME-sid-abc123-ttl-10m am Residential-Gateway) hält die IP konstant, während Sie Läufe vergleichen.
Denken Sie daran: Eine Testseite zu bestehen ist nicht dasselbe, wie eine Website zu bestehen. Kommerzielle Anbieter nutzen serverseitige Modelle, sitzungsübergreifende Historie und Signale, die diese Seiten nicht sehen.
Was das nicht löst
Wer CDP-Lecks schließt, beseitigt eine Kategorie von Indizien. Automatisierung wirkt dadurch nicht menschlich, und Datenerhebung wird dadurch nicht erlaubt. Tempo, Abfolge und Volumen pro Identität verraten Automatisierung weiterhin, und IP-Reputation und TLS müssen nach wie vor zum angegebenen Browser passen. Zugriffsregeln gelten weiterhin: robots.txt, Nutzungsbedingungen und Datenschutzrecht für alles Personenbezogene. Wer Erkennung betreibt, sollte aus 2025 vor allem mitnehmen, sich nicht auf einen einzelnen Nebeneffekt zu verlassen, den ein Browserhersteller mit einem Routine-Commit entfernen kann. Wer Automatisierung betreibt, sollte seine Werkzeuge aktuell halten, Treiber bevorzugen, die kein JavaScript patchen, und davon ausgehen, dass die verbleibenden Signale im Verhalten liegen.






