test(auth): pin loopback host spellings and drop the unreachable ::1 arm #12

Merged
jlxq0 merged 1 commit from test-loopback-host-spellings into main 2026-08-26 05:25:16 +00:00
Owner

Closes #3 for this repository.

What changed

The two rows of the shared redirect-URI case table that nothing here asserted:

  • loopback_spellings_normalise_to_one_host127.1, 0177.0.0.1 and
    2130706433 land on the same outcome as 127.0.0.1, in both directions
    (as a requested redirect_uri, and as an entry an operator wrote), and
    127.0.0.2 still does not.
  • bracketed_ipv6_loopback_is_a_loopback_host[::1] port-relaxes, [::2]
    does not, the relaxation does not cross address families, and the unbracketed
    form does not parse as a URL at all.

Both behave correctly today, so this records behaviour rather than fixing a
bug. It is worth recording because the behaviour is the url crate's host
normalisation and not anything this repo wrote: a crate bump could change which
spellings the allowlist admits with nothing else going red.

is_loopback_host also loses its unbracketed "::1" arm. Both call sites pass
Url::host_str(), which returns the bracketed form for every IPv6 host, so the
arm was unreachable. In a security predicate an unreachable arm reads as
coverage that is not there.

With these two, all nine rows of the table in
the decision comment on #3
are pinned in this repo, with no dependency on any other repo.

Verified by mutation

Each mutation was run against the full suite, and each killed exactly one test.

1. Compare the authority as written instead of the normalised host
(raw_host(entry) != raw_host(uri) → return false, inserted ahead of the
Url::parse):

test loopback_spellings_normalise_to_one_host ... FAILED
panicked at src/oauth_redirect.rs:467:13: should accept http://127.1:49152/callback
test result: FAILED. 102 passed; 1 failed

Every other test stayed green, which is the point: nothing else in the suite
was pinning the normalisation.

2. Drop the "[::1]" arm from is_loopback_host:

test bracketed_ipv6_loopback_is_a_loopback_host ... FAILED
panicked at src/oauth_redirect.rs:492:77
test result: FAILED. 102 passed; 1 failed

That the removal of the unbracketed "::1" arm changes nothing is the
complementary evidence: the full suite is green without it, and
parse_allowlist("http://::1:8787/callback").is_err() asserts the form cannot
arrive.

Not in this change

The cross-repo comparison of the case table floated in #3 — a check living in
mantis, which depends on all five and is depended on by none. It is marked
optional there and it is not this repo's to carry.

Gates

cargo fmt --check, cargo clippy -D warnings, cargo test --all-features
(103 passed), cargo audit, cargo deny check bans licenses sources — all
green locally on rustc 1.98.0.

Closes #3 for this repository. ## What changed The two rows of the shared redirect-URI case table that nothing here asserted: - `loopback_spellings_normalise_to_one_host` — `127.1`, `0177.0.0.1` and `2130706433` land on the same outcome as `127.0.0.1`, in both directions (as a requested `redirect_uri`, and as an entry an operator wrote), and `127.0.0.2` still does not. - `bracketed_ipv6_loopback_is_a_loopback_host` — `[::1]` port-relaxes, `[::2]` does not, the relaxation does not cross address families, and the unbracketed form does not parse as a URL at all. Both behave correctly today, so this records behaviour rather than fixing a bug. It is worth recording because the behaviour is the `url` crate's host normalisation and not anything this repo wrote: a crate bump could change which spellings the allowlist admits with nothing else going red. `is_loopback_host` also loses its unbracketed `"::1"` arm. Both call sites pass `Url::host_str()`, which returns the bracketed form for every IPv6 host, so the arm was unreachable. In a security predicate an unreachable arm reads as coverage that is not there. With these two, all nine rows of the table in [the decision comment on #3](https://forge.oddie.app/jlxq0/caldav-mcp/issues/3#issuecomment-10569) are pinned in this repo, with no dependency on any other repo. ## Verified by mutation Each mutation was run against the full suite, and each killed exactly one test. **1. Compare the authority as written instead of the normalised host** (`raw_host(entry) != raw_host(uri) → return false`, inserted ahead of the `Url::parse`): ```text test loopback_spellings_normalise_to_one_host ... FAILED panicked at src/oauth_redirect.rs:467:13: should accept http://127.1:49152/callback test result: FAILED. 102 passed; 1 failed ``` Every other test stayed green, which is the point: nothing else in the suite was pinning the normalisation. **2. Drop the `"[::1]"` arm from `is_loopback_host`:** ```text test bracketed_ipv6_loopback_is_a_loopback_host ... FAILED panicked at src/oauth_redirect.rs:492:77 test result: FAILED. 102 passed; 1 failed ``` That the removal of the *unbracketed* `"::1"` arm changes nothing is the complementary evidence: the full suite is green without it, and `parse_allowlist("http://::1:8787/callback").is_err()` asserts the form cannot arrive. ## Not in this change The cross-repo comparison of the case table floated in #3 — a check living in `mantis`, which depends on all five and is depended on by none. It is marked optional there and it is not this repo's to carry. ## Gates `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test --all-features` (103 passed), `cargo audit`, `cargo deny check bans licenses sources` — all green locally on `rustc 1.98.0`.
test(auth): pin loopback host spellings and drop the unreachable ::1 arm
All checks were successful
CI / cargo (pull_request) Successful in 42s
CI / docker (pull_request) Successful in 1m3s
012c391c06
The redirect-URI case table had two rows nothing in this repo asserted: the
alternate IPv4 loopback spellings, and bracketed IPv6.

Both behave correctly today, so this records behaviour rather than fixing a
bug. It is worth recording because the behaviour is the `url` crate's host
normalisation and not anything this repo wrote: `127.1`, `0177.0.0.1` and
`2130706433` all reach `is_loopback_host` as `127.0.0.1`, and a crate bump
could change which spellings the allowlist admits with nothing going red.

`is_loopback_host` also carried an unbracketed `"::1"` arm. Both call sites
pass `Url::host_str()`, which returns the bracketed form for every IPv6 host,
so the arm was unreachable — in a security predicate that reads as coverage
that is not there, and the next person to touch the function would assume the
unbracketed form is handled because the code says so.

Refs #3
jlxq0 merged commit 7e784ed0bc into main 2026-08-26 05:25:16 +00:00
jlxq0 deleted branch test-loopback-host-spellings 2026-08-26 05:25:17 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/caldav-mcp!12
No description provided.