Pydoll drives Chrome without a WebDriver. It is one of the few browser libraries that lets you keep the login inside the proxy address. Most tools tell you not to do that. We read release 2.27.0 on 20 September 2026, to see how it works and where it stops working.
The login never reaches the command line
Chrome does not accept a login inside --proxy-server. Pydoll accepts one anyway:
options.add_argument('--proxy-server=http://user:pass@proxy.example.com:8080')
It gets away with that by never passing your password to Chrome. Before the browser starts, the library scans the launch arguments. It cuts the login out of the address and rewrites the argument without it. The login stays in memory. When the proxy answers with a 407 challenge, the library supplies it over the debugging protocol.
| Step | What happens |
|---|---|
| You pass the address with the login | The library looks for a --proxy-server argument |
| Before Chrome starts | The login is cut out and the argument is rewritten |
| The proxy asks for a login | The library answers over the debugging protocol |
| After the first answer | Request interception is switched off again |
That order matters, because a command line is not a private place. Other programs on the machine can read it. Their own test pins the point. It asserts that the text user:pass does not appear in the joined arguments.
The last row is a real trap. Once the first challenge is answered, the library disables Chrome's Fetch domain. That is the same switch your own request interception uses. If you intercept requests yourself, turn yours back on after the first navigation.
Three addresses that break it
The parser is small. It cuts the address at the first at sign, then splits the login at the first colon. A colon inside the password is safe, and their tests cover it. Other shapes are not covered.
We re-implemented the published function and ran nine addresses through it.
An at sign in the password. The address http://user:pa@ss@host:8080 is cut at the first at sign. The password becomes pa and Chrome is handed the host ss@host:8080, which is not a host. What you see is a connection error, so the password looks fine and the network looks broken.
An email address as the user name. Here the login part has no colon of its own, so the parser gives up and changes nothing. The whole address stays in the launch argument, password and all. Chrome ignores the login, as Chrome always does. The proxy then refuses every request.
One token with no colon. Same path, same result. Some networks hand out a single key rather than a pair, and this is where that shape lands.
Writing the at sign as %40 does not rescue it. Neither parser decodes percent escapes, so the proxy is sent the characters pa%40ss instead of your password.
There is a second parser in the same release, used when the address is given to a browser context rather than to the launch arguments. It reads the same text differently. Where the launch parser gives up, this one accepts the login part as a user name with an empty password. In our run, two of nine addresses came out differently depending on which door they went through.
The fix for all of this is short. Use a password without an at sign, or use an allowed address so there is no login to parse at all.
One browser, several lines
A browser context can carry its own proxy, so one browser run can send different tabs through different addresses:
us = await browser.create_browser_context(proxy_server='http://user:pass@us.example.com:8080')
de = await browser.create_browser_context(proxy_server='http://user:pass@de.example.com:8080')
The login is stored against the context, and the handlers are wired on each new tab of that context. This is the cheapest rotation there is, because nothing restarts. The same call takes a bypass list, which keeps local development servers off the paid line.
A SOCKS5 line with a login
Chrome cannot log in to a SOCKS5 proxy. The project says so plainly and cites the upstream bug. It also ships the answer in the same package. A small forwarder listens on your own machine and does the SOCKS5 login upstream. The browser connects to it with no login at all.
| What | Does it pass through the forwarder |
|---|---|
| A connection to a web server | yes |
| A host name, looked up at the proxy | yes |
| Anything over UDP | no |
| A login on the local port | no, it accepts any local program |
Two things follow. While the forwarder runs, anything on that machine can use your paid line through it. So do not bind it to a public address. It warns you if you try. And only connections pass, so nothing leaves over UDP. Their own docs suggest adding --disable-quic for that reason. The browser then does not quietly try a protocol the proxy cannot carry.
| Your line | What to set |
|---|---|
| HTTP with a login | the address with the login inside it |
| HTTP by allowed address | the plain address |
| SOCKS5 without a login | the plain address |
| SOCKS5 with a login | the forwarder, then point at 127.0.0.1 |
Which proxy type fits it?
Residential, for the sites that answer a hosting address differently. Our own test is the size of that effect: 4 of 13 answers changed between a server address and a residential one.
Two notes for this library. An allowed address removes every problem on this page, because there is no login to parse. On a Residential Premium plan your machine's address can be allowed, up to 150 per plan. This also drives a real browser, so a run pulls images, fonts and scripts as well as data. On a per gigabyte line that is where the bill comes from. Size the plan for pages rather than for records.
A sticky port suits a run that works through one site. Write a retry either way, because a sticky address can change early when its device leaves the network. The residential proxies page lists the plans, and the plan API manages allowed addresses from code.
What breaks
- A connection error you cannot explain. Check your password for an at sign before you check anything else.
- The proxy refuses every request although the password is right. Your user name probably has no colon after it, so nothing was sent.
- Your own request interception stops working. The proxy handler switches the Fetch domain off after the first challenge.
- A SOCKS5 login does nothing. Chrome ignores it. Use the forwarder that ships with the library.
- The traffic bill is larger than the page count suggests. A real browser loads everything on the page.
What this page does not cover
We read the release as text and did not install or run the library. So we did not watch a challenge being answered, and did not start the forwarder. Our parser results come from our own copy of the published functions, not from the library itself. We did not establish what Chrome does once the Fetch domain is switched off. We only know that the switch covers the whole browser. This project ships about one release a month, and the one we read was four days old, so check your version. We will read it again by 20 October 2026.
Where to go from here
Proxies for nodriver covers the closest relative, which solves the same login problem with a forwarder of its own. Proxies for SeleniumBase covers a third answer, a generated browser extension. Proxies for Steel Browser covers a browser service that counts the bytes your line carried.
Sources
- The parser that cuts the login out of the launch argument and rewrites it (pydoll/browser/managers/proxy_manager.py), the second parser used by browser contexts and the handlers that answer the challenge (pydoll/browser/chromium/base.py), and the test file that pins the password out of the arguments (tests/unit/test_proxy_manager.py). autoscrape-labs/pydoll, release 2.27.0 of 16 September 2026, read 20 September 2026.
- The forwarder, its local binding, its warning and its connection only rule (pydoll/utils/socks5_proxy_forwarder.py), with the two documentation pages that carry the project's own advice (guides/proxies.md and the network page http-proxies.md). Same release.
- Our run of both published parsers over nine addresses, 20 September 2026, kept in the research folder of this page.
- Our paired address test of 19 September 2026: 16 URLs, plain requests, two runs from our server and two through a residential line of our house plan. Raw output is kept in the research folder of our OpenClaw page.
- Plans, allowed addresses and sticky sessions. HProxy documentation, hproxy.com/docs, 20 September 2026.


