Repository navigation
Solve a DataDome challenge through 2Captcha on the browser's own proxy exit - #22
Merged
Merged
Conversation
…y exit When Expedia's challenge handler picks DataDome, all three engines now wait up to 15 s for the page to clear by itself or mount its slider. They then buy at most one DataDomeSliderTask per page. The task carries the slider's iframe URL, the browser's own User-Agent and the browser's current --proxy exit, sticky session login included. Nothing is bought: - without TWOCAPTCHA_KEY; - over --cdp-endpoint or without a proxy; - by Selenium with a proxy login; - for t=bv; - for a page reached by a button press; - once the page's solve allowance is spent. The returned cookie is installed with the challenge page's own cookie attributes, read back, and the page re-loaded. A handler page that comes back gets the handler's own validate call (POST /botOrNot/validate with the page's exchangeHandshake), as the site's challenge bundle makes it. A DataDome pick is now state `challenge`; an unsolved one still exits 3. The sidecar records captcha_kind, captcha_solves, captcha_solved, captcha_accepted and per-decision captcha_attempts. The attempt sidecar is written whenever a captcha was met. The canary's walk receives TWOCAPTCHA_KEY and reports the captcha fields. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CKsd7q25N19WzUZjrAdzx8
…g the cookie The first live solve came back (26.8 s), and the cookie was installed with the page's own attributes. The re-load still got the slider, and the handler's validate call answered 403. The site's tag (tags.js 5.11.1) takes a pass only as a trusted message from the slider's captcha-delivery frame. It then sets the cookie and the session it sends as x-datadome-clientid, and fires the event on which the handler page validates by itself. Playwright and Selenium now post that message from inside the iframe and give the page 20 s to leave, before falling back to cookie + re-load + validate. pyppeteer cannot reach a cross-origin iframe and goes straight to the fallback. The record gains widget_posted, after_widget and the statuses of the page's own validate calls. The opt-in real-browser group serves the slider frame over local HTTPS and checks the path in all three drivers. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CKsd7q25N19WzUZjrAdzx8
…widget Seven canary walks on 2026-10-23 through the pinned residential exit: - 6 DataDome solves were bought, and 2 were accepted (3 pages, 150 rows each). Both went through the widget step, where the page's own validate call answered 200. - The cookie-and-re-load path was accepted 0 times out of 4. - The no-solve first-page job met DataDome on all seven runs and never cleared by itself. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CKsd7q25N19WzUZjrAdzx8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every measured refusal on Vrbo is Expedia's "Bot or Not?" handler picking DataDome. Until now the repo only reported that as blocked (exit 3), and its single 2026-09-14 experiment had stopped at a bare cookie pasted behind the page.
Resulting behaviour
When the handler picks DataDome, all three engines go through these steps:
t=bv, which 2Captcha's docs say means the exit is banned;TWOCAPTCHA_KEY;--cdp-endpoint, with no proxy, or Selenium with a proxy login (Selenium strips the login, so that proxy is not its exit);DataDomeSliderTaskwith websiteURL, captchaUrl, the browser's ownnavigator.userAgentand the browser's current proxy exit (the sticky-session-login from Keep one proxy exit for the whole run on a 2Captcha gateway #20 included). The fields follow 2Captcha's current DataDome docs, checked 2026-10-23. The task is polled for at most 180 s. The allowance is claimed before createTask and never given back.postMessagefrom inside the slider's captcha-delivery iframe,{"eventType":"passed","responseType":"captcha","cookie":…}.tags.js5.11.1) then installs the cookie and itsx-datadome-clientidsession, and firesdd_response_passed.POST /botOrNot/validateand leaves for the search. The engine gives this 20 s.SecureandSameSite, reads it back, and re-loads with the same settle. If the handler page is still there, the engine makes the validate call itself (X-Exchange-Handshakeplus the page config'sdestination/exchangeHandshake).The widget message and the validate call are reproduced from the site's served tag and challenge bundle, read 2026-10-23. Neither was taken from a captured exchange.
responseType: "captcha"is an inference.6. Record. The sidecar gains these fields:
captcha_kind;captcha_solves(tasks created);captcha_solved(answers returned);captcha_accepted(answers after which the page classified as served);captcha_attempts(one record per decision, with the outcome, cookie applied/kept, re-load and validate statuses, and accepted).<out>.latest_attempt.meta.jsonis now written for any run that met a captcha.A DataDome page now classifies as
challengeinstead ofblocked. An uncleared one still exits 3, withblocked_by: datadome-challenge.The canary's
paginationjob now receivesTWOCAPTCHA_KEY, passes--solve-captcha when-blockedexplicitly, adds the captcha fields to the step summary and uploads the attempt sidecars. It warns, without failing, when the key is absent. Thefirst-pagejob gets no key and no proxy, so its records are the free no-solve control.README, TROUBLESHOOTING, CONTRIBUTING,
.env.example,--helpand the CHANGELOG under[Unreleased]now describe this path and its limits. The README keeps the 2026-09-14 control table as history.Evidence
Offline suite (
python smoke_test.py), with no request leaving 127.0.0.1 and no key sent anywhere:t=bv, no key, no proxy, no address, four attempts on one budget = 1 purchase;_solver_proxy;_fetch_one_pagethrough retries and rotation: exactly 1 purchase with Playwright and pyppeteer, 0 with Selenium plus a login proxy, 0 over--cdp-endpoint, accepted recorded only on a served re-load.DATADOME_BROWSER=1, local Chromium 154). It runs against a stub site and a stub API on 127.0.0.1, with the slider frame served over local HTTPS and a stub of the tag's trusted-message handler. It covers three cases (accepted through the widget, refused everywhere, the cookie path alone), and all checks passed for each of Playwright, pyppeteer and Selenium:widget_posted: falseand falls through);/;t=bvis bought;ci_checks.py --allpassed, including the secret scan.Live verification (paid, bounded)
These were 7 canary dispatches on this branch, 2026-10-23. Run 1 was on
dcc7eff, before the widget step existed. Runs 2-7 were on19f5631. Each walk used theVRBO_PROXYresidential exit pinned to one sticky session, headful real Chrome,--block-mediaand a 15 MB budget. At most one solve per page was bought.The totals:
No-solve control: the
first-pagejob, from the runner's own address with no key and no proxy, met DataDome on all 7 runs. It never cleared by itself in either of its two tries per run (no_key/no_widgetrecords).Conclusion: the fallback works, but not reliably.
I stopped at 7 of the 10 allowed dispatches, because the pattern was already clear: accepted only through the widget, refused at the validate step otherwise.
Not changed
scraper_api_client.py) get no DataDome path: there is no browser to carry the cookie.--helpand the README.🤖 Generated with Claude Code
https://claude.ai/code/session_01CKsd7q25N19WzUZjrAdzx8