Skip to content

test: stop aiming I12 transport tests at recycled ports - #124

Merged
plusky merged 2 commits into
mainfrom
fix/issue-115
Aug 18, 2026
Merged

test: stop aiming I12 transport tests at recycled ports#124
plusky merged 2 commits into
mainfrom
fix/issue-115

Conversation

@plusky

@plusky plusky commented Aug 18, 2026

Copy link
Copy Markdown
Owner

What

Stop the I12 transport-error tests from connecting to an ephemeral 127.0.0.1 port that has been freed back to the OS.

wiremock 0.6 binds 127.0.0.1:0 and keeps those listeners in a process-wide pool for the process lifetime. The five I12 sites bound an ephemeral port, dropped the listener, and fired a request at the freed address expecting ECONNREFUSED. Under load that port can belong to a newly created pooled listener (~1–4 failures per 300 stress runs; independently reproduced in #93):

  • unwrap_err() panics on Ok carrying another test's corpus
  • .expect(1) over-counts a foreign request
  • worst, api_key_absent_from_transport_error_i12 goes green without sanitizing a transport error at all

All five sites now connect to 127.0.0.2:1 — still loopback (127.0.0.0/8), so the kernel refuses immediately (or returns ENETUNREACH if the extra address is not configured). A pooled listener binds only 127.0.0.1 and can never occupy that address.

  • crates/bugwarden-core/tests/guard_wiremock.rsget_bugs / whoami / valid_login I12 tests
  • crates/bugwarden/tests/preflight_wiremock.rs — preflight whoami I12
  • crates/bugwarden/tests/tools_wiremock.rs — MCP-visible whoami I12
  • Shared helper in crates/bugwarden/tests/common/mod.rs for the two server binaries

A bounded TCP probe refuses to return an address that accepted or timed out. The client call itself is capped at 2s so a proxy/routing hang cannot become the 30s client timeout. The assertion requires reqwest's error sending request (Display and Debug) so an empty or HTTP-status error cannot pass a bare !contains(KEY). A mutation that retargets the helper at 127.0.0.1 fails the helper.

Why

#93 / #112 only stopped asserting on [0] of a polluted recording. The bind-then-drop(listener) hazard remained at every I12 site, including a silent coverage loss on a security invariant.

Invariants

I12 is unchanged: the Bugzilla API key must not appear in a real transport-level error. Fail-closed behavior is untouched. No production code changed except adding the time feature to bugwarden-core's tokio dev-dependency so the tests can bound the connect.

Adversarial review (pre-PR)

  • No remaining bind-drop-connect on 127.0.0.1:0 in guard_wiremock, preflight_wiremock, or tools_wiremock.
  • Probe URL is built from the probed socket (they cannot drift). assert_ne!(ip, 127.0.0.1) kills a bind-then-drop mutation.
  • I12 is asserted on a real reqwest transport error (error sending request), not a timeout-with-empty-text.
  • Hang bound: 500ms TCP probe + 2s client timeout. Not a public TEST-NET address.

Accepted findings (fixed before this PR): URL/probe drift; a 127.0.0.1 mutation that would still pass; missing hang budget; duplicated helper across the two server test binaries.

Rejected: capturing the warn-log in tools_wiremock to assert the internalized whoami error text. That test's contract is the client-visible surface (I2 uniform denial). The transport error is already asserted in the three core tests and the preflight test.

Verification

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo clippy -p bugwarden --features gen --all-targets -- -D warnings
  • cargo test --workspace --all-targets --locked
  • cargo deny check

Closes #115

wiremock's process-wide pool binds 127.0.0.1:0 for the process lifetime.
The I12 transport-error tests bound an ephemeral 127.0.0.1 port, dropped
the listener, and connected at the freed address expecting ECONNREFUSED.
Under load that port can belong to a newly created pooled listener:
unwrap_err panics on another test's corpus, .expect(1) over-counts a
foreign request, or — worst — the I12 test goes green without sanitizing
a transport error at all.

Point those tests at 127.0.0.2:1 instead. That address is still loopback
(127.0.0.0/8) so the kernel refuses immediately (or returns ENETUNREACH),
and a pooled listener binds only 127.0.0.1 so it can never occupy it. A
bounded probe refuses to return an address that accepted or timed out;
the client call itself is capped at 2s so a proxy/routing hang cannot
become the 30s client timeout; and the assertion requires reqwest's
"error sending request" so an empty or HTTP-status error cannot pass a
bare !contains(KEY). A mutation that retargets the helper at 127.0.0.1
fails the helper. I12 is unchanged: the key still must not appear in
Display or Debug of a real transport error.

Closes #115
@plusky plusky added the ai-assisted Authored or substantially written with an AI coding agent label Aug 18, 2026
127.0.0.2 is extra loopback on Linux but is not aliased on macOS, so
the I12 probe hung until timeout on rust-macos. Switch to 127.0.0.1:1:
a privileged port both OSes refuse immediately if nothing is listening,
and that a non-root wiremock 127.0.0.1:0 pool cannot occupy. The helper
now asserts the port stays privileged so a bind-then-drop of an
ephemeral port still fails.
@plusky
plusky merged commit 137e552 into main Aug 18, 2026
13 checks passed
@plusky
plusky deleted the fix/issue-115 branch August 18, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted Authored or substantially written with an AI coding agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

guard_wiremock's freed-port tests can hit a recycled pooled listener instead of ECONNREFUSED

1 participant