proxy_pool: refuse a bad URL instead of crashing, and never leak it - #9
Merged
Conversation
A canary run with a real proxy secret crashed and printed a live proxy login and password into its own CI log. Three defects, all here, all triggered by one wrong value. A malformed URL crashed the run instead of being refused --------------------------------------------------------- parse_proxy_line validated the scheme and the host but never the PORT — and urlparse computes a port lazily, raising ValueError only when something finally asks for one. So a bad entry passed validation and blew up several calls later inside to_playwright: exit 1, a crash, where it should have been exit 2, bad usage, with a message naming the problem. The value that caused it is the mistake a new user makes: a line from a proxy LIST FILE (scheme://host:port:login:password) pasted where a proxy URL (http://login:password@host:port) belongs. The extra colons become part of the port. The refusal now says exactly that. mask() raised on the values that most needed masking ----------------------------------------------------- It read parsed.port too. So the one function standing between a password and a log blew up on a malformed URL, and the caller printed the raw string instead. That is how the credential reached the log. A masker must never raise. It is called precisely when something is already wrong with the value. It is now total: anything it cannot take apart is redacted whole rather than echoed, and every ProxyError message reports mask(line) rather than the line. A pool of N identical entries claimed to be N exits ----------------------------------------------------- A copied proxy list is often one address repeated — the one in use here was fifty identical lines. The pool reported "exit 2/50" on every rotation while every one of them left from the same place, and the single-exit warning never fired because it counted entries rather than addresses. So a run believed it was spread over fifty exits while burning one. Duplicates are now dropped, order preserved, and the collapse is LOGGED: a pool quietly smaller than the file that produced it is the same kind of surprise in the other direction. Also removed: a test asserting that "http://host:port:login:pass" "is understood". It checked only that parse_proxy_line did not reject the line — which it did not, returning it unchanged — so it passed while the value was never usable. A test that asserts a function did not complain is not a test that its answer was right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp7ctnpMGb5gi67Dco7C5V
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.
The mediamarkt canary's first run with a real proxy secret crashed, and printed a live proxy login and password into its own CI log doing it. Three defects, all in
proxy_pool.py, all triggered by one wrong value.A malformed URL crashed the run instead of being refused
parse_proxy_linevalidated the scheme and the host but never the port — andurlparsecomputes a port lazily, raisingValueErroronly when something finally asks. So a bad entry passed validation and blew up several calls later insideto_playwright: exit 1 (crash) where it should have been exit 2 (bad usage), with a traceback and no message naming the problem.The value that caused it is the mistake a new user makes:
The extra colons become part of the port. The refusal now says exactly that.
mask()raised on the values that most needed maskingIt read
parsed.porttoo. So the one function standing between a password and a log blew up on a malformed URL, and the caller printed the raw string instead. That is how the credential reached the log.A masker must never raise — it is called precisely when something is already wrong with the value. It is now total: anything it cannot take apart is redacted whole rather than echoed, and every
ProxyErrormessage reportsmask(line)rather than the line.A pool of N identical entries claimed to be N exits
A copied proxy list is often one address repeated; the one in use here was fifty identical lines. The pool reported
exit 2/50on every rotation while every one of them left from the same place, and the single-exit warning never fired because it counted entries rather than addresses. A run believed it was spread across fifty exits while burning one.Duplicates are now dropped, order preserved, and the collapse is logged — a pool quietly smaller than the file that produced it is the same kind of surprise in the other direction.
Also removed
A test asserting that
"http://host:port:login:pass""is understood". It checked only thatparse_proxy_linedid not reject the line — which it did not, returning it unchanged — so the check passed while the value was never usable.A test that asserts a function did not complain is not a test that its answer was right. Replaced with one that pins the refusal, the message, and the absence of the credential from it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Wp7ctnpMGb5gi67Dco7C5V