# Authenticated transport: driving your own Chrome Some marketplaces refuse datacenter traffic outright. Ozon answers `composer-api.bx` with an endless 307 redirect loop; no amount of TLS impersonation clears it. The reliable answer is not a better fingerprint — it is to run the fetch **inside a browser you already trust**, over the Chrome DevTools Protocol. Nine sources use this: Ozon, Avito, Taobao, Megamarket, Lamoda, DNS, Citilink, AliExpress and Cian. The split matters. Taobao, Megamarket, DNS, Citilink, AliExpress and Cian are **CDP-only** — no anonymous tier exists, so nothing reads from them without a logged-in Chrome. Ozon and Avito use CDP as a **tier-2 fallback**: tier 1 (TLS impersonation) usually works from a Russian residential IP, and CDP only kicks in when the anonymous tier is challenged. Lamoda is split — its card path is anonymous GraphQL, but search needs CDP. Wildberries, Yandex Market and Detsky Mir never touch it. ## Read this before you enable it CDP hands **any local process full control of the Chrome profile it is attached to**, including every session logged into that profile. If you attach it to your daily browser, a bug or a hostile local process inherits your banking, email and work sessions. So the setup below uses a **dedicated scraping profile**. That is not a nicety; it is the entire mitigation. The other three layers: | Mitigation | What it prevents | |---|---| | Separate `--user-data-dir` | Blast radius beyond the marketplaces you log into | | `--remote-debugging-address=127.0.0.1` | Any LAN access to the debugging port | | Scheme guard in `open_page` | An authenticated browser being aimed at `file:///` | | Per-connector host allowlists | A crafted SKU becoming a request for `/api/personal/orders` | **No credentials are ever stored, read or transmitted by this project.** You log in by hand, in a browser you control. There is no credential store to leak. (The optional MPStats connector is the one exception — you supply `MPSTATS_MP_AUTH` yourself via env; it is never stored by the project either.) ## Setup ### Optional challenge handoff Set `CHROME_CHALLENGE_HANDOFF_S=120` in the MCP server's environment to retain DOM-detected Lamoda search and Taobao search/card challenge tabs for up to two minutes. Default `0` disables retention; the hard maximum is 300 seconds, including initial browser attachment/navigation. A headed Chrome and an active MCP session are required. This mode does not solve CAPTCHA or log into accounts. An error with `handoff_expires_at` confirms a retained tab. Complete the interaction in that tab, then repeat the same operation and arguments in the same MCP session. The connector reads that exact page without navigating again; a successful read closes it. For comparison, repeat the original query with only the affected source. Pending handoffs take precedence over successful-result caches, so another session's cached response cannot bypass the retained page. The original expiry never extends on retries. Different sessions and operations receive independent tabs; at most four handoffs are active per process. Concurrent reads of one lease or a full registry report a busy transport error. The runtime attempts to reveal the owned browser window and prevents unrelated connector calls from hiding it while the handoff is active. Foreground activation is best-effort, especially for remote browsers. Expiry, errors, cancellation and graceful shutdown close owned tabs; a hard process kill cannot guarantee expiry in an external Chrome. Without `handoff_expires_at`, use the ordinary retry flow: HTTP errors rejected before DOM extraction, headless runs and calls without an MCP session do not retain a tab. No cookies, screenshots or browser profile copies are written by the handoff runtime. A retained error also carries an opaque `handoff_id`; a vision-capable MCP client may pass it to `compare_browser_snapshot(handoff_id)` to receive the current viewport as an image plus metadata. The snapshot does not solve or interpret the challenge. ### 1. Start Chrome with remote debugging **Windows (PowerShell):** ```powershell .\scripts\start_chrome_cdp.ps1 ``` **Linux / macOS:** ```bash ./scripts/start_chrome_cdp.sh ``` Both create a scraping-only profile and bind the debugging port to localhost: | Platform | Default profile location | |---|---| | Windows | `%LOCALAPPDATA%\Chrome-Scraping` | | macOS | `~/Library/Application Support/Chrome-Scraping` | | Linux | `${XDG_DATA_HOME:-~/.local/share}/chrome-scraping` | Useful flags: `--port 9333`, `--profile /path/to/dir`, `--headless`. Headless is off by default and should stay off: anti-bot systems detect headless Chrome readily, which defeats the purpose of using a real browser. ### 2. Log into the marketplaces you need — and nothing else In the window that opens, sign into whichever CDP sources you use — `ozon.ru`, `avito.ru`, `taobao.com`, `megamarket.ru`, `lamoda.ru`, `dns-shop.ru`, `citilink.ru`, `aliexpress.ru`. One profile serves them all. **Do not** sign into banking, email, or work accounts here. Keeping it single-purpose is what bounds the risk. ### 3. Verify ```bash curl -s http://127.0.0.1:9222/json/version ``` You should see Chrome's version JSON. On Windows you can also use `Test-NetConnection 127.0.0.1 -Port 9222`. ### 4. Confirm the connector sees it ```bash uv run python -c " import asyncio from ozon_connector.server import ozon_selfcheck print(asyncio.run(ozon_selfcheck()).status) " ``` `success` means both tiers work. `inconclusive` with a transport message means Chrome is not reachable — recheck the port. `drift_detected` means Ozon changed its payload shape and the connector needs updating. ## Configuration | Variable | Default | Purpose | |---|---|---| | `CHROME_CDP_PORT` | `9222` | Debugging port | | `CHROME_SCRAPING_PROFILE` | platform default above | Profile directory | | `CHROME_BINARY` | auto-detected | Explicit path to Chrome/Chromium/Edge | | `CHROME_STEALTH` | `1` | Windows: park the window off-screen; macOS: open tabs in the background and keep the app hidden (⌘H) so it never steals focus or switches your Space | | `CHROME_HEADLESS` | `0` | Headless mode — detectable, use only on a display-less host | Auto-detection covers Chrome, Chromium and Edge in the standard locations for all three platforms. On Linux it also consults `PATH`. ## How it behaves at runtime The connector starts Chrome automatically on first use if the port is not already listening, so the launcher scripts are optional convenience. It waits up to 12 seconds for the port to bind. Chrome is **detached, never closed** by the connector — it is your browser. Each fetch opens a tab, navigates, reads, and closes that tab. Every operation is individually bounded (tab creation, navigation, teardown), so a wedged CDP session cannot hang a tool call indefinitely. A block, auth wall or 5xx main document raises `NavBlocked` rather than handing a login page to a parser as though it were data — Playwright resolves `goto()` for those statuses, so this check is what stands between you and silently parsing a captcha page. ## Troubleshooting **"Chrome/Chromium not found"** — set `CHROME_BINARY` to the full executable path. **"CDP did not bind within 12s"** — another Chrome instance is likely holding the same profile directory. Close it, or use a different `--profile`. **Port already in use** — something else has 9222. Use `--port 9333` and set `CHROME_CDP_PORT` to match. **Ozon still blocked with CDP running** — confirm you are logged into ozon.ru *in the scraping profile* (not your main browser), and that the session has not expired. Open the page manually in that window to check. **Running in a container as root** — Chrome's sandbox refuses to run as root; the launcher adds `--no-sandbox` automatically when it detects that case. ## Should you use this at all? If you only need Wildberries, Yandex Market and Detsky Mir: **no**. All three work over plain anonymous HTTP, and `compare_prices` will report every CDP source it can't reach as blocked and rank the rest. You need it the moment Taobao, Megamarket, DNS, Citilink or AliExpress matter — those six have no anonymous tier, so without a logged-in Chrome they return nothing at all. For Ozon and Avito it is optional insurance: tier 1 usually answers, and CDP only earns its keep when the anonymous tier is challenged. For Lamoda, card lookups work without it but search does not. A residential IP is the alternative for the fallback sources — from a Russian residential address Ozon's and Avito's tier 1 often work without any browser. It does nothing for the CDP-only six, whose block is at the API, not the IP. Set `OZON_PROXY`/`AVITO_PROXY` or the standard `HTTPS_PROXY` to route through one.