feat: proxy modes and proxy authentication - #30
Merged
Merged
Conversation
The proxy could only ever be a forward proxy, which ruled out two things people actually need: standing in front of a service so a client needs no proxy settings at all, and running on a network where the machine cannot reach the internet without going through a proxy of its own. Start Mitm Proxy takes mode, one or several, and proxy_auth. Modes are checked when the keyword runs rather than left to the proxy: mitmproxy logs an unusable specification instead of raising it, so without this a typo surfaced as a startup timeout with no reason attached. The failure now carries mitmproxy's own explanation and an example of what a mode looks like. proxyauth turned out not to be a core option. It belongs to the addon of the same name, which registers it when the master loads its addons, so it cannot be passed when the options are built and is set on the master afterwards. Passing it to Options raises KeyError, which is how this was found. Reverse mode is exercised for real against a local server: a client reaches the origin with no proxy settings, rules still apply, and a blocked request is still blocked. Upstream mode is exercised by chaining two proxies, and asserts on routing rather than on inspection: reaching the origin proves nothing on its own because the origin is reachable anyway, so the decisive test points the chain at an upstream that is not there and requires it to fail. Rules on the *upstream* proxy are deliberately not asserted - with a live upstream mitmproxy the request arrives at its port but produces no HTTP flow there, so a test claiming otherwise would be claiming something untrue. That is documented on the keyword, since a suite might otherwise expect it. Transparent and socks5 are passed through and covered only at the options level, because they need the operating system or a client set up for them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Robot Framework version legs caught this: a pre-existing test failed with "multiple unraisable exception warnings", triggered by the new tests in this branch rather than by anything they assert. mitmproxy attaches a handler to the root logger that forwards every log record to its master's event loop, and never removes it. Once the proxy has stopped and its loop is closed, any later log record - from anywhere in the test run, not just from this library - raises "Event loop is closed" inside logging. Those surface as unraisable exceptions attributed to whichever test happens to be running, which is why the failure landed on an unrelated one. It is also a leak: every proxy started leaves another handler on the root logger, all of them holding a dead master. Discarding a proxy now uninstalls its handler. Two tests cover it: one that a stopped proxy leaves none behind and that logging afterwards is harmless, and one that starting three proxies does not pile them up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Robot Framework version legs caught this too: starting a proxy failed with "Task was destroyed but it is pending!" and "Addon error: Event loop is closed", neither of which has anything to do with the proxy being started. StartupErrorCollector listens on the root logger, because that is where mitmproxy reports a bind failure. It therefore also hears everything else: a proxy stopped moments ago still logs from its own teardown, asyncio complains about tasks it destroyed, and the rest of the test run logs as well. Any of those arriving during the startup window ended the wait and failed the keyword, which is the same mistake mitmproxy's own errorcheck addon makes and which this library removes that addon for. The collector now keeps only records from mitmproxy, and only a message reporting a failure to listen ends the wait. Anything else is still remembered and still reported if the startup times out for its own reasons, so nothing diagnostic is lost. Tests: one that logs exactly that noise from another thread while a proxy starts and requires the proxy to come up anyway, and one that a real bind failure is still reported, so the filter cannot swallow the failure it exists to report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MobyNL
force-pushed
the
feat/proxy-modes
branch
from
August 18, 2026 19:05
09716d6 to
45913fb
Compare
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.
PR 8 of the 1.0.0 sequence (workstream #6). Branches from
main— depends only on the proxy controller, so it does not stack on #29.Why
The proxy could only ever be a forward proxy. That ruled out two things people actually need: standing in front of a service so a client needs no proxy settings at all, and running on a network where the test machine cannot reach the internet except through a proxy of its own.
What it adds
Start Mitm Proxytakesmode— one mode or a list — andproxy_auth.Modes are validated when the keyword runs. mitmproxy logs an unusable specification rather than raising it, so without this a typo surfaced as a startup timeout with no reason attached. The failure now carries mitmproxy's own explanation plus an example of the expected shape.
One thing that had to be found by running it
proxyauthis not a core option. It belongs to the addon of the same name, which registers it when the master loads its addons — so it cannot be passed when the options are built. Passing it toOptionsraisesKeyError: 'Unknown options: proxyauth', which is how this surfaced. It is now set on the master after construction, and a test pins that.What is tested, and what deliberately is not
Reverse mode, for real against a local server: a client reaches the origin with no proxy settings, rules still apply, and a blocked request is still blocked.
Upstream mode by chaining two proxies — but asserting on routing, not inspection. Reaching the origin proves nothing on its own, because the origin is reachable either way; the decisive test points the chain at an upstream that is not there and requires a 502.
I did not assert that rules on the upstream proxy fire, because they do not: with a live upstream mitmproxy the request arrives at its port but produces no HTTP flow there. I checked this directly with a spy addon before deciding. A test claiming otherwise would be asserting something untrue, so instead it is documented on the keyword, where a suite might otherwise expect it.
transparentandsocks5are passed through and covered only at the options level — they need the operating system or a client configured for them, which CI cannot provide.Verification
ruff check .,robocop check atestandmypyclean.pytest: 160 passed, 97.95% coverage.🤖 Generated with Claude Code