Skip to content

feat: proxy modes and proxy authentication - #30

Merged
MobyNL merged 3 commits into
mainfrom
feat/proxy-modes
Aug 18, 2026
Merged

feat: proxy modes and proxy authentication#30
MobyNL merged 3 commits into
mainfrom
feat/proxy-modes

Conversation

@MobyNL

@MobyNL MobyNL commented Aug 18, 2026

Copy link
Copy Markdown
Owner

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 Proxy takes mode — one mode or a list — and proxy_auth.

Start Mitm Proxy    mode=reverse:http://127.0.0.1:5000
Start Mitm Proxy    mode=upstream:http://corporate-proxy:3128
Start Mitm Proxy    proxy_auth=tester:secret

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

proxyauth is 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 to Options raises KeyError: '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.

transparent and socks5 are 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 atest and mypy clean. pytest: 160 passed, 97.95% coverage.

🤖 Generated with Claude Code

MobyNL and others added 3 commits August 18, 2026 21:04
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
MobyNL merged commit 4980c25 into main Aug 18, 2026
15 checks passed
@MobyNL
MobyNL deleted the feat/proxy-modes branch August 18, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant