웹 자동화 세계의 오랜 골칫거리: "내가 봇이 아니라고 증명해야 한다". CloakBrowser는 Chromium 소스 코드를 C++ 레벨에서 직접 패치해 컴파일한 스텔스 브라우저로, 스톡 Playwright에서 import 한 줄만 바꾸면 reCAPTCHA v3에서 0.9점(사람 수준)을 받고 Cloudflare Turnstile을 통과한다. JS 인젝션이나 플래그 트릭에 의존하는 기존 스텔스 라이브러리와 근본적으로 다른 접근.
무엇을 하는 프로젝트인가.
기존 스텔스 도구들은 자바스크립트를 주입하거나 시작 플래그를 바꿔서 봇처럼 보이는 흔적을 가린다. CloakBrowser는 그게 아니라, Chromium의 C++ 코드를 58곳 고쳐서 빌드한 나만의 Chromium 바이너리를 다운로드해 사용한다. 탐지 사이트가 "당신은 봇인가요?"라고 물어볼 때, JS로 거짓말을 하는 게 아니라 처음부터 사람 브라우저가 되어 있다는 게 핵심이다.
그러면서도 사용자 입장에서는 from playwright.sync_api import sync_playwright 한 줄을 from cloakbrowser import launch로 바꾸기만 하면 끝. 나머지 코드는 그대로 동작한다.
navigator.webdriver = true, 헤드리스 모드 UA, 빈 플러그인 목록 등)을 남긴다. 이 흔적을 지워서 "사람이 직접 띄운 Chrome"처럼 보이게 만든 브라우저가 스텔스 브라우저. 군용 스텔스기처럼 레이더에 잘 안 잡힌다는 비유.트렌딩 이유, 경쟁 제품 대비 장점.
2026년 5월 셋째 주 TrendShift 주간 23위. 스타 수는 32개로 절대값은 작지만, 경쟁 도구들이 차례로 죽거나 무력화된 빈 자리에 들어왔다는 게 핵심이다. 5년 가까이 사실상 표준이었던 도구들의 종말을 정리해보면:
playwright-stealth, undetected-chromedriver, puppeteer-extra는 모두 자바스크립트를 주입하거나 시작 플래그를 조정해서 흔적을 가린다. Chrome이 업데이트될 때마다 패치 위치가 바뀌어 안 먹히고, 안티봇 회사들은 "패치가 적용된 흔적 자체"를 탐지한다. 또 가장 유망했던 Camoufox는 Firefox를 패치한 프로젝트였는데 2025년 초 잠시 중단됐다가 2026년 초 불안정 베타로 복귀했다.
지문(fingerprint)을 만들어내는 코드 자체를 58곳 고쳐서 빌드했다. 탐지 사이트가 검사할 때 보이는 값은 처음부터 진짜 Chrome과 동일하다. JS 주입이 아니라 "처음부터 다르게 만든 Chrome"이라 패치 흔적을 검출할 수 없다. TLS 핸드셰이크의 ja3n/ja4 지문도 진짜 Chrome과 일치한다.
| 도구 | reCAPTCHA v3 | 패치 레벨 | 유지보수 | 엔진 |
|---|---|---|---|---|
| Stock Playwright | 0.1 (봇) | 없음 | 활발 | Chromium |
| playwright-stealth | 0.3~0.5 | JS 주입 | 정체 | Chromium |
| undetected-chromedriver | 0.3~0.7 | 플래그 | 정체 | Chrome |
| Camoufox | 0.7~0.9 | C++ (Firefox) | Unstable beta (2026년 복귀) | Firefox |
| CloakBrowser | 0.9 | C++ (Chromium) | 활발 | Chromium |
주목할 점은 Camoufox의 "Firefox 자리"가 비었다는 것. Firefox는 Playwright 지원도 제한적이고 시장 점유율도 낮아 TLS 지문 자체가 봇 의심을 산다. Chromium은 그 자체로 시장 점유율 65%를 넘는 일반 브라우저라, TLS·HTTP/2·QUIC 핸드셰이크가 정상 트래픽의 바닷속에 묻힌다.
백엔드 / 프론트엔드 / 인프라 각각.
이 프로젝트의 "진짜 자산"은 GitHub 저장소의 Python 코드가 아니라 GitHub Releases에 호스팅된 약 200MB짜리 패치된 Chromium 바이너리다. Chromium은 C++ 약 3,500만 줄짜리 거대한 코드베이스인데, 그중 58곳을 수정하고 Linux x64용으로 다시 빌드했다.
저장소는 Python(~53%)과 TypeScript(~43%)의 혼합 구성이다. Python 래퍼(pip install cloakbrowser)와 JS/Node.js 래퍼(npm install cloakbrowser)를 모두 정식 지원한다. Playwright뿐 아니라 Puppeteer 등 Node.js 환경에서도 동일하게 사용할 수 있다. 역할은 ① 패치된 Chromium 바이너리를 GitHub Releases에서 자동 다운로드·캐시, ② Playwright/Puppeteer를 그 바이너리로 실행시키는 얇은 wrapper, ③ HumanConfig로 마우스·키보드 동작에 사람 같은 미세한 흔들림 추가(옵션).
| 분류 | 패키지 | 역할 |
|---|---|---|
| 핵심 | playwright>=1.40 | 브라우저 자동화 API |
| 핵심 | httpx>=0.24 | 바이너리 다운로드용 HTTP 클라이언트 |
| 옵션 (geoip) | geoip2, socksio | 프록시 IP의 시간대·언어 자동 매칭, SOCKS5 지원 |
| 옵션 (patchright) | patchright>=1.40 | 패치된 Playwright 변종 (추가 우회용) |
| 옵션 (serve) | aiohttp, websockets | 원격 브라우저 서버 모드 (Docker 컨테이너에 띄워두기) |
setup.py로 빌드했는데, 최근에는 PEP 517 표준을 따르는 빌드 백엔드를 쓴다. hatchling은 그 표준 백엔드 중 하나. pyproject.toml에 메타데이터만 적으면 알아서 빌드/배포 가능. CloakBrowser는 [build-system]에서 hatchling을 명시.Dockerfile이 흥미롭다. Chromium을 띄우려면 libnss3, libgbm1, libatk1.0-0 등 그래픽·폰트·오디오 관련 시스템 라이브러리가 30개 이상 필요하다. 거기에 xvfb(가상 디스플레이)와 xdotool(헤드드 모드 자동화), Node.js 20(JS 래퍼용)까지 깐다.
핵심 최적화는 빌드 타임에 ensure_binary()를 호출해 200MB Chromium을 미리 다운로드한다는 점. 런타임에 다운받으면 컨테이너 첫 실행이 1~2분 멈춰버리는데, 빌드에 넣으면 이미지가 무거워지는 대신 시작이 즉시 된다.
Python 패키지는 pip install cloakbrowser로 PyPI에서 가져오고, 무거운 Chromium 바이너리는 cloakbrowser 패키지가 처음 실행될 때 GitHub Releases에서 다운로드해 ~/.cloakbrowser에 캐시한다. 환경변수로 다운로드 URL이나 바이너리 경로를 오버라이드할 수 있어 사내 미러를 두기도 쉽다.
시스템 구조도 + 핵심 설계 패턴.
봇을 숨기는 코드는 어디에 있을 수 있을까. CloakBrowser가 다른 도구와 갈라지는 결정적 차이가 이 위치다:
JS 인젝션은 navigator.webdriver 같은 속성을 Object.defineProperty로 덮어쓰는 식인데, 탐지 사이트는 "속성 정의가 native 코드인지, JS로 정의된 건지"를 검사해서 패치 흔적을 찾아낸다. 반면 C++ 소스에서 패치하면 정의 자체가 native라서 구분이 불가능하다.
저장소 코드는 가볍게(~20KB Python), 진짜 자산은 무겁게(200MB 바이너리). 이 분리 덕분에:
전구를 교체할 때 백열등을 LED로 바꿔도 소켓이 같으니 그대로 끼울 수 있는 것처럼, CloakBrowser는 Playwright와 똑같은 함수 시그니처(launch(), new_page(), goto() ...)를 제공한다. 기존 코드 한 줄(import)만 바꾸면 된다.
이건 단순한 편의성이 아니라 채택 마찰을 0에 가깝게 낮추는 전략이다. 회사가 이미 만들어둔 수백 줄짜리 Playwright 스크립트를 통째로 재작성할 필요가 없다.
주요 폴더/파일의 역할.
__getattr__를 통한 지연 임포트cloakbrowser/__init__.py를 보면 HumanConfig는 모듈 최상단에서 import하지 않고 __getattr__ 훅 안에서 import한다. Python 3.7+의 모듈 레벨 __getattr__ 기능. 이러면 HumanConfig가 필요 없는 사용자는 그 코드를 메모리에 안 올린다.
pip install cloakbrowser[geoip]처럼 대괄호로 추가 기능을 선택 설치. geoip(IP 기반 시간대 매칭), patchright(추가 우회), serve(원격 브라우저 서버), dev(테스트). 사용자는 자기가 필요한 만큼만 무겁게 만든다.
_version.py)pyproject.toml의 [tool.hatch.version] path = "cloakbrowser/_version.py"가 핵심. 릴리스 자동화 스크립트가 _version.py 한 줄만 고치면 패키지 버전이 바뀐다. 메인 코드는 건드리지 않는다.
각 기술에서 배울 것 + 실습 아이디어.
CloakBrowser는 Playwright의 얇은 wrapper다. 그러니 Playwright를 모르면 이 도구를 써도 의미가 없다. Sync API와 Async API 두 가지가 있다. 동시에 여러 페이지를 띄울 거면 async, 한 페이지 한 페이지 순차 처리면 sync로 충분.
navigator.webdriver = true가 켜지는 것도 CDP 연결 때문. CloakBrowser는 이 신호 자체를 C++에서 끈다.실습 아이디어: 본인 블로그를 Playwright로 열고 첫 화면 캡처 → 같은 코드를 CloakBrowser로 바꿔서 다시 실행. 두 스크린샷을 비교해 본인이 만든 페이지가 두 환경에서 똑같이 보이는지 확인.
탐지 사이트가 어떤 신호를 보는지 알면 회피하려는 시도도 이해가 된다. 주요 신호 7가지:
| 신호 | 스톡 Playwright | CloakBrowser |
|---|---|---|
navigator.webdriver | true | false |
navigator.plugins.length | 0 | 5 |
window.chrome | undefined | 실제 객체 |
| User-Agent | HeadlessChrome | Chrome/142.0.0.0 |
| Canvas 지문 | 유니크 | 일반 Chrome과 같음 |
| TLS ja3n 지문 | node.js 클라이언트 | 실제 Chrome |
| WebGL 렌더러 | SwiftShader | 일반 GPU 문자열 |
실습 아이디어: https://bot.sannysoft.com이나 https://browserscan.net을 Playwright로 방문해 결과를 캡처 → CloakBrowser로 다시 방문해 비교. "탐지 사이트가 어떤 항목을 어떻게 검사하는가"가 표 한 장에 정리된다.
CloakBrowser의 진짜 마법은 Chromium 소스를 빌드하는 데 있다. Chromium 코드 받기, GN 빌드 설정, 패치 적용, ninja 컴파일은 큰 학습 곡선이지만 한 번 익히면 평생 자산이다.
Chromium 풀빌드는 16GB RAM, 100GB+ 디스크, 멀티코어 CPU로도 1~3시간 걸린다. 패치된 빌드만 매번 만들 일이 아니라면 GitHub Actions 같은 CI에서 한 번씩 돌리는 게 현실적.
실습 아이디어: 작은 패치 하나만 해보기. 예: navigator.webdriver를 항상 false로 만드는 한 줄 C++ 변경 → 빌드 → 같은 탐지 페이지에서 결과 확인. 빌드만 성공해도 큰 성취다.
CloakBrowser의 pyproject.toml은 현대적 Python 패키지의 좋은 본보기다:
cloakbrowser CLI 명령 자동 등록Chromium은 30개 이상의 시스템 라이브러리를 요구한다. CloakBrowser의 Dockerfile은 이것들을 한 번에 정리한 좋은 참고 자료다. 특히 "빌드 타임에 무거운 다운로드 미리 끝내기" 패턴이 인상적.
실제로 돌리려면 무엇이 필요한가.
| 항목 | 최소 | 권장 |
|---|---|---|
| Python | 3.9 | 3.12+ |
| OS | Linux x86_64 | Linux x86_64 (macOS arm64 베타, Windows 예정) |
| RAM | 2GB | 4GB+ (페이지를 여러 개 띄울 거면) |
| 디스크 | 500MB | ~1GB (200MB 바이너리 + 캐시) |
| 네트워크 | 최초 200MB 다운로드 가능 | 안정적 회선 + (선택) 주거용 프록시 |
현재 시점에서 큰 제약은 플랫폼이 Linux x86_64만 정식 지원이라는 것. macOS arm64(M1/M2/M3)와 Windows는 로드맵에 있다. Apple Silicon 사용자는 Docker로 Linux 컨테이너 띄워서 쓰는 게 임시 해법.
Chromium 풀빌드는 사실상 워크스테이션이 필요하다. 16코어 CPU + 32GB RAM + SSD 100GB 비어 있어도 첫 빌드는 1~2시간. M1 Pro MacBook으로도 비슷한 시간. 빌드 캐시(ccache, goma)를 잘 쓰면 두 번째부터는 30분 이내.
| 항목 | 요구사항 |
|---|---|
| CPU | 8코어+ (16코어 권장) |
| RAM | 16GB+ (링커가 OOM 잘 일으킴, 32GB 권장) |
| 디스크 | 100GB+ 여유 (Chromium 소스 + 빌드 산출물) |
| 네트워크 | 최초 fetch에 ~30GB 다운로드 |
| OS | Linux 권장 (depot_tools가 가장 안정적) |
| 도구 | depot_tools, GN, ninja, Python 3, git |
난이도별 5단계.
먼저 pip install playwright 후 playwright install chromium으로 표준 환경 준비. browserscan.net을 띄우고 결과 캡처. 그 다음 pip install cloakbrowser로 CloakBrowser 설치, 같은 페이지를 띄워 캡처. 두 결과를 좌우로 놓고 어떤 항목이 어떻게 바뀌었는지 표로 정리.
examples/recaptcha_score.py를 베이스로, 다양한 사이트(Google 자체 데모, 본인이 운영하는 사이트 등)에서 점수를 측정하는 스크립트를 작성. 결과를 CSV로 저장하고, 시간대별·페이지별로 점수가 어떻게 변하는지 1주일간 모니터링.
기본 CloakBrowser는 지문은 가리지만 "1초에 정확히 다섯 번 클릭" 같은 비인간적 동작 패턴은 안 막는다. HumanConfig를 적용해 마우스 이동을 곡선으로, 클릭 사이 간격을 가우시안 분포로, 타이핑 속도를 단어별로 다르게 만들기. FingerprintJS의 행동 분석 페이지에서 점수 변화 측정.
[serve] 옵션 설치 후 cloakserve 명령으로 원격 CDP 서버를 띄운다. 별도 머신에서 Playwright 클라이언트가 connect_over_cdp로 연결해서 사용하는 구조. 여러 봇 작업자가 한 브라우저 풀을 공유할 수 있다. 라즈베리파이 4 같은 작은 서버에서 컨트롤러를, 강력한 클라우드 인스턴스에서 브라우저를 굴리는 분리 구조 실험.
Google의 "Get the Code" 가이드를 따라 Chromium 소스 받기 → third_party/blink/renderer/core/frame/navigator.cc의 webdriver() 함수를 return false로 한 줄 수정 → 빌드 → 패치된 바이너리를 CLOAKBROWSER_BINARY_PATH로 지정해 CloakBrowser 래퍼와 함께 사용. 이게 되면 CloakBrowser의 모든 마법이 어떻게 작동하는지 완전히 이해한 것.
주차별 학습 계획.
Playwright 공식 튜토리얼 끝까지 따라하기. page.locator(), page.fill(), page.click(), page.wait_for_selector() 핵심 5개 메소드를 손에 익히기. 본인이 자주 가는 사이트 5곳을 자동으로 여는 스크립트 작성.
FingerprintJS의 오픈소스 코어를 읽어보면 "어떤 JS API를 호출해서 어떤 값을 모아 해시를 만드는가"가 보인다. CreepJS도 비슷한 컨셉. 이걸 알면 어떤 신호를 가려야 하는지 직관이 생긴다.
탐지는 JS만으로 하는 게 아니다. TLS 핸드셰이크의 cipher suite 순서, HTTP/2 SETTINGS 프레임 순서, ALPN 순서 등이 클라이언트별로 다르다. ja3, ja3n, ja4가 이걸 해시화한 표준. Wireshark로 직접 캡처해 보기.
Chromium을 --remote-debugging-port=9222로 띄우고 직접 WebSocket으로 CDP 메시지를 주고받기. Page.navigate, Runtime.evaluate 같은 메소드를 손으로 호출해보면 Playwright가 내부에서 뭘 하는지 보인다.
Google이 만든 빌드 도구 체인. fetch chromium, gclient sync, gn gen out/Default, autoninja -C out/Default chrome. 첫 빌드 성공이 목표. 한 번만 성공하면 그다음은 익숙해진다.
navigator.webdriver는 navigator.idl에 선언되고 navigator.cc에 구현된다. Blink가 IDL을 어떻게 C++로 바인딩하는지 알면 어떤 JS API든 C++ 레벨에서 수정할 수 있다. 이게 CloakBrowser의 58개 패치가 작동하는 원리.
여기까지 오면 본인이 직접 작은 스텔스 도구를 만들 수 있다. 1개 패치 + 1개 Python 래퍼 + Docker. CloakBrowser와의 차이는 58개 패치 vs 1개 패치의 정도 차이일 뿐, 본질은 같다.
프로젝트에 등장하는 주요 기술 용어.
HeadlessChrome이라고 표시해 한눈에 봇임이 들통났다. 새 헤드리스(--headless=new)는 거의 일반 모드와 같지만, 일부 미세한 차이가 남는다.fetch, gclient, gn, git-cl 등 Chromium 소스를 받고 빌드하고 리뷰하는 데 필요한 모든 명령을 묶은 Python 도구 모음. Chromium 소스 안에 포함되지 않는 외부 도구. 패치된 Chromium 빌드의 첫 단추.xvfb를 까는 이유.[geoip] 옵션 의존성에 socksio가 들어가는 이유.더 깊이 파고들 자료들.
pip install 출발점pip install cloakbrowser로 설치해보기. Linux 환경(또는 Docker)이 있으면 첫 다운로드 후 examples/basic.py를 그대로 실행. ~/.cloakbrowser/ 안에 무엇이 생기는지 확인.fetch chromium으로 30GB 받고 src/third_party/blink/renderer/core/frame/navigator.cc 열어서 webdriver() 함수 한 번 들여다보기. CloakBrowser가 정확히 무엇을 고치는지 한눈에 보인다.