En bilinen CDP tespit yöntemi, yani bir otomasyon istemcisi Runtime.enable çağırdığında tetiklenen Error.stack getter'ı, Mayıs 2025 tarihli iki V8 inspector commit'i Chrome 138 ile yayına girince çalışmaz oldu. Bu, CDP ile yönetilen tarayıcıları görünmez yapmadı. Araştırmacılar o zamandan beri etkin bir Runtime domain'ini gözlemlemenin başka yollarını buldu; ticari tespit sistemlerinin çoğu ise zaten bu tek kontrole hiç bağlı olmamış sinyallere dayanıyor: framework izleri, headless ve ortam ipuçları, girdi özellikleri, davranış ve ağ sinyalleri.
CDP tespiti nedir ve neden tüm büyük framework'ler açık veriyor
CDP tespiti, bir tarayıcının bir insan tarafından değil Chrome DevTools Protocol üzerinden yönetildiğini fark etmek demektir. İşe yaramasının nedeni, protokolün hata ayıklama için tasarlanmış olması ve bazı debugger özelliklerinin sayfa JavaScript'inin görebileceği izler bırakmasıdır.
Chrome DevTools Protocol, DevTools penceresinin kendisinin kullandığı WebSocket API'sidir: Runtime, Page, Network ve Input gibi domain'lerden oluşur, her birinin komutları ve olayları vardır. Chromium otomasyonunun büyük kısmı bu protokolden geçer:
- Puppeteer, Chrome'u CDP üzerinden kontrol eder. Firefox için artık WebDriver BiDi kullanıyor.
- Playwright, Chromium için CDP kullanır. Firefox için ise Firefox'un içinde ayrı bir protokol olan Juggler'ı kullanır.
- Selenium 4, ChromeDriver ile WebDriver üzerinden konuşur; ChromeDriver da Chrome'u içeride CDP ile yönetir. Selenium, BiDi'ye geçiş sürecinde CDP komutlarını doğrudan da sunar.
Firefox ise CDP'yi geride bıraktı. Mozilla, CDP uygulamasını Firefox 129'da kullanımdan kaldırdı ve Firefox 141'de tamamen sildi. Dolayısıyla 2026'da "CDP tespiti", otomasyonla yönetilen Chromium ailesi tarayıcıları tespit etmek anlamına geliyor.
Bu sinyal sınıfının savunma tarafına cazip gelmesinin nedenini DataDome, Haziran 2024 tarihli araştırmasında (tehdit araştırması blogundan yeniden yayımlanmış) şöyle açıklamıştı: "Bir tarayıcının CDP ile enstrümante edildiğini tespit edebilmek, modern bot framework'lerinin çoğunu yakalamanın anahtarıdır." Yeni headless Chrome normal Chrome ile aynı kod tabanını paylaşmaya başlayınca fingerprint tutarsızlıklarının çoğu ortadan kalktı. Protokol düzeyindeki bir yan etki ise Puppeteer'ı, Playwright'ı, Selenium'u ve aynı altyapı üzerine kurulmuş bilinmeyen her framework'ü yakalayabilirdi.
Klasik Runtime.enable kontrolü nasıl çalışıyordu
Kontrol şu durumdan faydalanıyordu: Runtime domain'i etkinken V8'in inspector'ı istemciye önizleme gönderebilmek için her console argümanını serileştirir. Bir nesneyi serileştirmek özelliklerini okumak demektir; getter'ı olan bir özelliği okumak da sayfa kodunu çalıştırır.
Castle'ın belgelediği sürüm şöyleydi:
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 => Runtime domain'ini dinleyen bir şey var
Adım adım:
- Puppeteer, Playwright ve ChromeDriver
Runtime.enableçağırır, çünkü script'leri nerede çalıştıracaklarını bilmek içinRuntime.executionContextCreatedolaylarına ihtiyaç duyarlar. Protokol belgeleri bunu "execution context oluşturulmasının raporlanmasını" etkinleştirmek olarak tanımlar. - Domain etkinleştirildikten sonra her
console.*çağrısı birRuntime.consoleAPICalledolayı üretir ve V8 bu olay için her argümanın önizlemesini oluşturur. - Bir
Erroriçin önizleme oluşturmak.stacközelliğini (.nameve.messageile birlikte) okumak demekti. Sayfa.stackyerine bir getter koyduğu için getter çalışıyor ve bayrağı değiştiriyordu. - Hiçbir şeyin bağlı olmadığı sıradan bir tarayıcıda önizleme oluşturulmaz ve getter hiç çalışmaz.
Birkaç satır JavaScript'ten ibaretti, neredeyse hiç maliyeti yoktu ve navigator.webdriver ya da user agent ile ne yaparsa yapsın Runtime.enable çağıran her framework'ü yakalıyordu. JavaScript özelliklerini yamalayan stealth eklentileri bunu düzeltemiyordu, çünkü sızıntı sayfada değil protokoldeydi. Ortaya çıkan karşı önlem sürücü düzeyindeydi: rebrowser-patches ve Patchright Runtime.enable çağırmaktan kaçınır; DataDome da 2024'te nodriver ile Selenium Driverless'ın "Runtime.enable kullanmayan düşük seviyeli CDP komutları" kullandığını not etmişti.
V8 2025'te neyi değiştirdi
V8 bu yan etkiyi Mayıs 2025'te iki commit ile kapattı: inspector, nesnelerin ve hataların önizlemesini oluştururken kullanıcı tanımlı getter'ları çalıştırmayı bıraktı. Düzeltme V8 13.8'e girdi; bu sürüm de Chrome 138 ile yayımlandı (24 Haziran 2025'te stable).
61a90754(7 Mayıs 2025), "[inspector] Prevent side effects during object preview": "runtime domain'i etkinleştirilirken kullanıcı kodunun çalıştırılmasını" önler.e08e9734(9 Mayıs 2025), "[inspector] Prevent side effects during error preview": hata açıklamasının üretilme biçimini "yan etkisi olan getter'ları çalıştırmayacak şekilde" değiştirir.
İki commit de Chromium hata kaydı 415094795'e atıf yapıyor. İlkini Microsoft'ta Playwright geliştiricisi olan Pavel Feldman yazmış; yani değişiklik bir stealth projesinden değil, otomasyon araçları tarafından geldi. Castle'dan Antoine Vastel bunu 28 Ağustos 2025'te "Why a classic CDP bot detection signal suddenly stopped working (and nobody noticed)" başlığıyla yazdı ve önizleme sırasında kullanıcı getter'larını atlayan yeni getErrorProperty() yardımcı fonksiyonuna dikkat çekti. Chrome 138 ve sonrasında yukarıdaki kod parçası, hiç değiştirilmemiş Puppeteer altında bile false döndürüyor.
Runtime.enable hâlâ tespit edilebilir mi?
Muhtemelen evet, ama eskisi kadar güvenilir değil. Mart 2026'da bağımsız bir araştırmacı olan Sveba, V8'in önizleme yolunu inceleyen bir analiz yayımladı ve geriye kalan iki yol gösterdi. Birincisi, GetOwnPropertyDescriptor başarısız olduğunda Mayıs 2025 korumasının kaçırdığı bir kod yolu; yazar bunun "yama sonrası Chrome'da istismar edilebilir olduğunu doğruladığını" söylüyor. İkincisi, bir nesnenin prototip zincirine yerleştirilen bir Proxy kullanıyor:
let detected = false;
const trap = new Proxy({}, {
ownKeys() { detected = true; return []; }
});
const obj = Object.create(trap);
console.groupEnd(obj); // herhangi bir console metodu iş görür
// detected === true => inspector önizleme oluşturuyor
Yazıya göre inspector yalnızca değerin kendisinin Proxy olup olmadığına bakıyor, prototiplerine bakmıyor ve tüm anahtarları hemen topluyor. Zinciri dolaşmak da bu yüzden ownKeys tuzağını tetikliyor. Yazar bunu Mart 2026 tarihli bir content_shell derlemesinde test etmiş ve o tarihte yamalanmamış olduğunu bildirmiş. Biz bunu her stable Chrome sürümünde doğrulamadık; bu yüzden evrensel bir gerçek olarak değil, raporlanmış bir bulgu olarak değerlendirin.
Savunma tarafının artık herhangi bir Runtime.enable sinyaline ne kadar yaslanabileceğini iki çekince sınırlıyor. Birincisi, DevTools'u açık olan gerçek bir kullanıcı da aynı kod yolundan geçer; yani sinyal "bu bir bot" değil, "bir inspector bağlı" der. İkincisi, yakalanmaya en çok değen framework'ler zaten Runtime.enable kullanmıyor; dolayısıyla sinyal çoğunlukla değiştirilmemiş Puppeteer, Playwright ve Selenium'u işaretliyor. Sektör, daha zayıf pek çok sinyali üst üste koymaya yöneldi; Castle'ın yazısı da bunu söylüyor.
2026'da hâlâ işe yarayan sinyaller
Bugün ayakta kalan sinyaller; framework'ün sayfada ne yaptığından, tarayıcının nasıl derlenip başlatıldığından, girdinin nasıl geldiğinden ve oturumun ağda nasıl davrandığından geliyor. Tek bir CDP yan etkisini gizlemek bunların hiçbirini ortadan kaldırmıyor.
Main world'deki framework izleri
Değiştirilmemiş framework'ler kendilerine özgü izler bırakır. rebrowser bot detector bunlardan birkaçını listeliyor:
__pwInitScripts: Playwright bunu varsayılan olarak her sayfanın global kapsamına ekler.sourceURLsızıntıları: Puppeteer, değerlendirilen script'leri//# sourceURL=pptr:…ile etiketler; bu etiket, hook'lanmış bir fonksiyonun içinde yakalanan bir error stack'inde görünebilir.- Main world'de çalıştırma: script'iniz sayfanın kendi world'ünde
document.querySelectorçağırırsa, bu fonksiyonu sarmalamış bir sayfa çağrıyı görür. exposeFunction/ binding'ler:page.exposeFunction()ve Playwright'ınwindow.__playwright__binding__nesnesi, sayfanın listeleyebileceği global'ler ekler. Arka planda, "incelenen tüm context'lerin global nesnelerine" fonksiyon ekleyenRuntime.addBindingkullanılır.- Utility world adları: framework'ler tanınabilir adlara sahip izole world'ler oluşturur; rebrowser-patches bunları yeniden adlandırmanıza izin verir.
Init script izleri
Page.addScriptToEvaluateOnNewDocument, bir script'i "her frame oluşturulduğunda (frame'in script'leri yüklenmeden önce)" çalıştırır. Stealth eklentileri navigator nesnesini sitenin kodu çalışmadan önce bu yolla yamalar. Protokol sızdırmaz, ama yamalar sızdırır. Camoufox README'sinin ifadesiyle, enjekte edilen JavaScript Object.getOwnPropertyDescriptor ile, bir fonksiyonun toString() çıktısının artık [native code] döndürmemesiyle ya da window ile worker arasında farklılaşan değerlerle yakalanabilir.
Worker context'leri
Yamalamanın en sık başarısız olduğu yer worker'lardır. Sayfaya uygulanan init script'ler ve özellik değişiklikleri dedicated, shared veya service worker'lara kendiliğinden ulaşmaz. Window sahte değerler bildirirken gerçek navigator.userAgent, hardwareConcurrency veya WebGL renderer değerini bildiren bir worker güçlü bir ipucudur. CreepJS worker kapsamını tam da bu nedenle ayrıca kontrol eder.
Headless ve derleme ipuçları
- Headless shell ve yeni headless. Chrome,
--headless=newmodunu Chrome 112'de sundu ve Chrome 132'den itibaren eski mod yalnızca ayrıchrome-headless-shellbinary'sinde yaşıyor. Playwright headless çalışırken varsayılan olarak chromium headless shell kullanıyor ve bu, gerçek Chrome'dan farklı davranıyor. - Derleme kimliği. "Chrome for Testing" user agent'ı, varsayılan viewport'lar (Puppeteer'da 800×600, Playwright'ta 1280×720) ve açık bırakılmış
navigator.webdriver, rebrowser detector'ın listelediği ucuz kontrollerdir. - Codec'ler. Chromium projesine göre açık kaynaklı Chromium derlemeleri, Google Chrome'un içerdiği H.264 ve AAC gibi tescilli codec'leri içermez. Chrome olduğunu iddia edip H.264 için
canPlayTypekontrolünü geçemeyen bir tarayıcı aslında Chromium'dur. - İzinler ve medya cihazları. Headless ve container ortamlarında bildirim izni durumları, boş medya cihazı listeleri ve eksik konuşma sesleri çoğu zaman user agent'ın iddia ettiğiyle uyuşmaz. CreepJS ve BrowserScan bunları kontrol eder.
Sentetik girdi
Input.dispatchMouseEvent ile gönderilen girdi güvenilir olaylar üretir (isTrusted true olur), ama gerçek girdide olmayan özellikleri olmuştur. En bilineni, tam ekran dışında screenX/screenY değerlerinin pageX/pageY ile eşit olmasıydı. Chromium bunu 15 Eylül 2025'te, Chrome 142 dal noktasından önce birleştirilen 6917162 numaralı değişiklikle ("Fix screen coordinates to avoid automation detection") düzeltti. Tam da bu sorunu aşmak için var olan CDP-Patches projesi, artık CDP girdisinin coalesced olaylar da ürettiğini belirtiyor ve kendini arşivledi. Güncel Chrome'da bu iki özel kontrol büyük ölçüde ortadan kalktı; hareket yörüngesi, zamanlama ve tıklama konumu analizi ise hâlâ geçerli.
Davranış ve ağ
Aşağıdaki sinyallerin hiçbiri CDP ile ilgili değil ve sağlayıcıların puanladığı şeylerin çoğunu bunlar oluşturuyor. Castle; istemci tarafı davranış analizini, ortam "red pill"lerini, proxy ve ağ tespitini ve koordineli etkinliğin zaman serisi analizini sayıyor. Ağ tarafında buna, iddia edilen tarayıcıyla uyuşması gereken TLS fingerprint'leri ve HTTP/2 fingerprint'leri ile IP itibarı da dahil. GPU düzeyinde fingerprinting'i canvas ve WebGL incelememizde ele aldık.
Patchright, nodriver ve Camoufox CDP sızıntılarını nasıl ele alıyor
Üçü de Runtime.enable türü sızıntılardan farklı yollarla kaçınıyor ve hiçbiri davranış ya da ağ itibarı sorununu çözmüyor.
Patchright, yalnızca Chromium için yamalanmış bir Playwright sürücüsüdür. README'sine göre ana yaması, Runtime.enable çağrısından "JavaScript'i (izole) ExecutionContext'lerde çalıştırarak" kaçınıyor. Ayrıca Console domain'ini tamamen devre dışı bırakıyor (yani console loglama çalışmıyor), --enable-automation bayrağını kaldırıyor, --disable-blink-features=AutomationControlled ekliyor ve fingerprint enjeksiyonu olmadan gerçek Chrome (channel="chrome") kullanılmasını öneriyor. Playwright sürümlerini yakından takip ediyor (v1.63.0 sürücüsü Eylül 2026'da çıktı). Kurulumu Patchright incelememizde anlattık.
nodriver, undetected-chromedriver'ın halefidir; WebDriver'ı tamamen çıkarır ve yüklü Chrome'unuzla doğrudan CDP üzerinden konuşur. Güncel core/ kaynak koduna bakıldığında, değerlendirmeyi Runtime.evaluate ve Runtime.callFunctionOn ile yaptığı ve hiçbir zaman Runtime.enable çağırmadığı görülüyor; bu da DataDome'un 2024'teki tanımıyla örtüşüyor. Bedeli şu: değerlendirme main world'de gerçekleşiyor, DOM fonksiyonlarını sarmalayan bir sayfa bunu görebiliyor ve fingerprint yönetimi yok. PyPI'daki son sürüm Mayıs 2026 tarihli 0.50.3. nodriver rehberimize göz atın.
Camoufox CDP'yi tamamen devre dışı bırakır. Bir Firefox fork'udur; Playwright, Firefox'u Juggler üzerinden yönetir ve Camoufox, Juggler'ı Playwright sayfanın izole bir kopyası üzerinde çalışacak şekilde yamalar: sayfa script'leri enjekte edilen yardımcıları göremez ve getter okumalarını izleyemez. Girdi Firefox'un normal girdi işleyicilerinden geçer, fingerprint değerleri de init script'lerle değil C++ düzeyinde taklit edilir; bu da toString ve worker uyuşmazlığı sınıfını ortadan kaldırır. Bedeli, Firefox olmasıdır. README'ye göre bazı WAF'lar SpiderMonkey motorunun davranışını test ediyor, dolayısıyla Chrome gibi görünemez.
Sinyal başvuru tablosu
| Sinyal | 2026'da hâlâ işe yarıyor mu? | Neyle aşılır | Aşmanın maliyeti |
|---|---|---|---|
Console'da Error.stack getter'ı (klasik) | Chrome 138+ sürümlerinde hayır (V8 13.8) | Güncel Chrome | Yok |
| Diğer inspector önizleme yan etkileri (ör. prototip zincirinde Proxy) | Mart 2026 itibarıyla çalıştığı bildirildi; DevTools'u açık gerçek kullanıcılarda da tetiklenir | Runtime.enable çağırmamak (Patchright, nodriver, rebrowser-patches) ya da CDP kullanmamak (Camoufox) | Düşük-orta: sürücü yaması, console API'nin kaybı |
__pwInitScripts, binding'ler, pptr: sourceURL | Evet, değiştirilmemiş framework'lere karşı | Yamalı sürücüler; exposeFunction kullanmamak | Düşük |
| Main world'de çalıştırma hook'ları | Evet | İzole world'de değerlendirme | Orta: sayfa global'lerine doğrudan erişim yok |
JS ile yamalanmış özellikler (toString, descriptor'lar) | Evet | Yamalamamak; gerçek Chrome ya da C++ düzeyinde taklit | Orta-yüksek |
| Window ile worker uyuşmazlığı | Evet | Yukarıdakiyle aynı; yamalar worker'ları da kapsamalı | JS ile yamalıyorsanız yüksek |
| Headless shell / Chrome for Testing / codec'ler | Evet | Markalı Chrome, headful veya yeni headless, sanal ekran | Orta: daha fazla RAM, sunucularda Xvfb |
| CDP girdisi: screenX == pageX, coalesced olay yok | Güncel Chrome'da büyük ölçüde düzeltildi | Güncel Chrome | Yok |
| Davranış ve zamanlama | Evet, puanlamadaki payı da artıyor | Gerçekçi tempo, kimlik başına daha az istek | Yüksek: throughput |
| TLS / HTTP/2 / IP itibarı | Evet | Gerçek tarayıcı ağ yığını, temiz residential veya ISP IP'leri | Sürekli proxy harcaması |
Kendi kurulumunuzu nasıl test edersiniz
Önce kendi test sayfanızda, ardından herkese açık tespit sayfalarında deneyin ve her seferinde tek bir değişkeni değiştirin. Herkese açık sayfalar size neyi tespit ettiklerini söyler. Kendi sayfanız ise hangi Chrome derlemesi ile sürücü kombinasyonunun hâlâ hangi kontrolü tetiklediğini gösterir.
1. Yerel test sayfası
Bunu probe.html olarak kaydedin. Klasik kontrolü, prototip zinciri kontrolünü ve birkaç ucuz iz kontrolünü çalıştırır, ardından sonuçları DOM'a yazar; böylece script'leri hangi world'de çalışırsa çalışsın her sürücü sonuçları okuyabilir.
<!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>
Ardından ilgilendiğiniz her kurulumda çalıştırın:
import pathlib
from playwright.sync_api import sync_playwright # alternatif: 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()
Önce değiştirilmemiş Playwright'ı, sonra Patchright'ı, ardından aynı sayfayı normal bir Chrome penceresinde DevTools açıkken ve kapalıyken çalıştırın. DevTools'lu çalıştırma kontrol grubunuzdur: orada tetiklenen her şey gerçek kullanıcılarda da tetiklenir, yani tek başına zayıf bir sinyaldir.
2. Herkese açık tespit sayfaları
- rebrowser bot detector: Chromium odaklıdır; Runtime.enable, sourceURL, main world'de çalıştırma,
exposeFunction,__pwInitScripts, viewport ve user agent kontrollerini kapsar. Sayfada açıklandığı gibi bazı testler yalnızca script'iniz sayfanındummyFnfonksiyonunu çağırırsa ya da bir fonksiyonu expose ederse tetiklenir. - BrowserScan bot tespiti: WebDriver, user agent, CDP ve navigator tutarlılığı kontrollerini raporlar.
- CreepJS: "yalanları" (yamalanmış özellikleri), headless özelliklerini ve worker kapsamı uyuşmazlıklarını işaretleyen açık kaynaklı fingerprint analizi. JavaScript düzeyindeki taklidi yakalamada en iyisi budur.
- Device and Browser Info "Are you a bot?": CDP, WebDriver, Playwright ve headless Chrome kontrolleri dahil fingerprint ve davranış testleri.
Bunları production'da kullanacağınız proxy üzerinden çalıştırın. Birçoğu tarayıcı sinyallerinin yanında IP ve saat dilimini de gösteriyor; datacenter IP'si üzerinde alınmış kusursuz bir tarayıcı puanı gerçek trafik hakkında pek bir şey söylemez. Sticky oturum (residential gateway üzerinde USERNAME-sid-abc123-ttl-10m), çalıştırmaları karşılaştırırken IP'yi sabit tutar.
Bir test sayfasını geçmenin bir siteyi geçmekle aynı şey olmadığını unutmayın. Ticari sağlayıcılar sunucu tarafı modeller, oturumlar arası geçmiş ve bu sayfaların göremediği sinyaller kullanır.
Bunun çözmediği şeyler
CDP sızıntılarını kapatmak yalnızca bir kanıt kategorisini ortadan kaldırır. Otomasyonu insan gibi göstermez, veri toplamayı da izinli hale getirmez. Kimlik başına hız, sıralama ve hacim otomasyonu yine ele verir; IP itibarı ve TLS de iddia ettiğiniz tarayıcıyla uyuşmak zorundadır. Erişim kuralları da geçerliliğini korur: robots.txt, kullanım koşulları ve kişisel veri içeren her şey için veri koruma mevzuatı. Tespit tarafındaysanız 2025'ten çıkan pratik ders, bir tarayıcı üreticisinin sıradan bir commit ile kaldırabileceği tek bir yan etkiye bel bağlamamaktır. Otomasyon tarafındaysanız araçlarınızı güncel tutun, JavaScript yamalamayan sürücüleri tercih edin ve geriye kalan sinyallerin davranışsal olduğunu varsayın.






