Skip to content

fix(server): name anonymous landing in identity preflight failures - #403

Open
scarabeusiv wants to merge 5 commits into
plusky:mainfrom
scarabeusiv:fix/preflight-anonymous-landing
Open

scarabeusiv wants to merge 5 commits into
plusky:mainfrom
scarabeusiv:fix/preflight-anonymous-landing

Conversation

@scarabeusiv

Copy link
Copy Markdown

What

The identity preflight now diagnoses a login_required refusal (envelope code 410) for what it is — the request reached Bugzilla anonymously, so the API key did not arrive in the form the deployment honours — instead of blaming the whoami endpoint's existence or a wrong login. The valid_login-false bail names the same possibility, since an anonymous request answers false there too.

Why

When the network path between bugwarden and Bugzilla strips the chosen credential form (an egress proxy dropping custom headers, a gateway rewriting query strings), every request lands anonymous: reads silently narrow to the anonymous view and writes fail with "You must log in before using this part of Bugzilla", which reads like a guard-policy or permissions denial and sends debugging in the wrong direction. The placement switch itself already exists (--use-auth-header / BUGZILLA_USE_AUTH_HEADER; the api_key query parameter is the default) — what was missing is any surface naming this failure for what it is, and any guidance on which deployment honours which form.

How

  • preflight keys on the BugzillaError envelope code (410; Bugzilla answers it as HTTP 401). Both server-custody arms name the anonymous landing, the placement in use, and the remedy; the non-410 wording is unchanged.
  • Operator-facing startup text only: UPSTREAM_HINTS still gives auth codes the bare line to MCP callers, and no key material reaches the new text (I12). No guard behaviour changes.
  • README and DESIGN.md record how to choose a placement (query parameter for stock Bugzilla and BMO, Bearer header for Red Hat Bugzilla) and what a stripped one looks like.

Verified

  • cargo fmt --check, clippy (workspace and --features gen, -D warnings), rustdoc (-D warnings, both), typos on the changed files: clean.
  • cargo test --workspace --all-targets --locked: 1,048 passed, 0 failed; --doc tests green.
  • The three new wiremock tests were each confirmed to fail when their piece of the diagnosis is reverted (code-410 branch disabled; the false-bail sentence removed).
  • cargo deny check: advisories fails on pristine main as well (RUSTSEC-2026 advisories against the locked rustls); this change touches no dependencies.

@plusky plusky left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The code and tests hold up. There is no key, URL or Bugzilla text in the new messages (I12), and preflight still fails closed (I4). Keying on envelope code 410 is the right choice: it is login_required in 5.0, 5.2 and BMO, and REST answers it as 401. Each of the three new tests fails when its piece is reverted, and both commits pass the gates on their own.

One blocking point, docs only. docs/DESIGN.md (Identity preflight / client contract): "stock Bugzilla's own header form (X-Bugzilla-API-Key) is not sent". Stock Bugzilla 5.0 and 5.2 have no header form at all. They accept the key only as the Bugzilla_api_key / api_key parameter (Bugzilla/Auth/Login/APIKey.pm, WebService/Util.pm fix_credentials). The X-Bugzilla-API-Key header is BMO's (bmo/Bugzilla/Auth/Login/APIKey.pm, X_BUGZILLA_API_KEY). Suggested wording: "stock Bugzilla 5.0/5.2 has no header form at all; BMO's own header form (X-Bugzilla-API-Key) is not sent."

Non-blocking, take them or leave them:

  • README and DESIGN say the query parameter is what BMO accepts. BMO still maps api_key, but its API docs call the query parameter deprecated, a stopgap for old callers. The supported forms there are X-Bugzilla-API-Key or an OAuth2 bearer token, so --use-auth-header against BMO also lands anonymous. "still accepts, but documents as deprecated" would be more accurate.
  • preflight_declared_login_transport_error_names_the_endpoint only asserts "GET /rest/valid_login", which both arms print. So a login_required() that answered true for every error would survive on the declared arm. An assert!(!msg.contains("anonymously")) there would pin it, mirroring the whoami test.
  • server.rs: the new rustdoc paragraph ending "…names the possibility too." runs straight into "[BugWarden::new] already refuses…" with no blank /// line, so the two render as one paragraph. DESIGN has the same thing just before "Errors: …".

The red rust-deny check is not yours. main fails it too, on RUSTSEC-2026-0346 to 0349 against rustls 0.23.45, and this PR touches no dependencies.

A Bugzilla probe refused with login_required (envelope code 410)
reached Bugzilla anonymously: the API key did not arrive in a form
the deployment honours. Preflight diagnosed it as something else —
the whoami arm blamed the endpoint's existence, the declared arm a
wrong login or a generic endpoint failure — and a valid_login false,
which an anonymous request also produces, named only the account.

Both server-custody arms now key on code 410 and name the anonymous
landing and the API key placement (the api_key query parameter by
default, an Authorization: Bearer header under --use-auth-header),
including the transit that can strip the chosen form. The
valid_login-false bail names the same possibility alongside the
login. DESIGN.md records the diagnosis in the client contract and
the Identity preflight section; three wiremock tests pin it, each
verified to fail when its piece of the wording is removed.
The API key setup now says which deployment honours which placement
(the query parameter for stock Bugzilla and BMO, the Bearer header
for Red Hat Bugzilla), what a stripped placement looks like — every
request anonymous, writes refused with code 410 — and that the
identity preflight names the cause at startup.
Stock Bugzilla 5.0/5.2 has no header form at all; the
X-Bugzilla-API-Key header is BMO's. BMO still accepts the query
parameter, but documents it as deprecated; its supported forms are
that header and OAuth2 bearer tokens.
…nymous-landing diagnosis

Both preflight arms print GET /rest/valid_login in their transport
and login_required contexts, so the endpoint assertion alone cannot
catch a login_required() that answers true for every error. Assert
the transport error never claims an anonymous landing, mirroring the
whoami arm's pin.
…ogether

The server.rs paragraph ending 'names the possibility too.' ran
straight into the BugWarden::new paragraph, and DESIGN ran the same
text straight into 'Errors:'; add the missing blank lines so both
render as the two paragraphs they are.
@scarabeusiv
scarabeusiv force-pushed the fix/preflight-anonymous-landing branch from 6a909b9 to 60c4add Compare October 19, 2026 15:24
@scarabeusiv

Copy link
Copy Markdown
Author

All four points addressed; new head 60c4add. The branch is also rebased onto current main (921c742, the rustls 0.23.46 bump from #404).

  • Blocking (DESIGN attribution) — the Identity preflight / client contract passage now reads: "Stock Bugzilla 5.0/5.2 has no header form at all; BMO's own header form (X-Bugzilla-API-Key) is not sent." I checked README and the code comments for the same misattribution — DESIGN was the only instance.
  • NB1 (BMO query parameter) — README and DESIGN now say BMO still accepts the query parameter but documents it as deprecated, a stopgap for old callers; its supported forms are the X-Bugzilla-API-Key header and OAuth2 bearer tokens, so an API key carried as a Bearer token under --use-auth-header lands anonymous there too.
  • NB2 (test pin) — preflight_declared_login_transport_error_names_the_endpoint now also asserts the message never contains "anonymously". Mutation-verified: forcing login_required() to answer true for every error makes the test fail; restored, it passes.
  • NB3 (paragraph breaks) — blank line added after "...names the possibility too." in the server.rs rustdoc and before "Errors:" in DESIGN.

rust-deny: as you called it, the red check was main's (RUSTSEC-2026-0346..0349 against rustls 0.23.45). On the rebased tree cargo deny check passes. Full local gate green: fmt, clippy (workspace and the gen feature line, -D warnings), 1,050 workspace tests + doc tests, typos.

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.

2 participants