fix(server): serve bug_info bodies on their own verdict - #17
Merged
Conversation
bug_info classified one fetch and returned another. The Read verdict came
from the CLASSIFY_FIELDS request; the body came from a second request
issued afterwards. A bug embargoed in between was still returned in full,
because the server's key is privileged enough that Bugzilla keeps serving
it — the guard had simply stopped looking. Per-id classification widened
that window, since a bug named early in a call is now classified several
round trips before its body arrives.
Put every fetched body back through the guard and serve it on its own
verdict. This costs no upstream request: the body is a superset of the
classification fields, so the re-check reads what is already in hand.
A body that now earns only summary is served as the summary view a fresh
call would return; one that earns nothing becomes the uniform restricted
entry, and the flip is logged server-side (I3) since it is the anomaly
worth knowing about. download_attachment already re-checked its blob for
the same reason.
The assembly moves into assemble_bug_info, a free function, so the
envelope can be tested without standing up an MCP request context.
Also stop forwarding the body fetch's error to the client. Bugzilla's
message names the bug and states whether it exists ("Bug #N does not
exist."), which is precisely the distinction I2 exists to erase; it is now
logged server-side and the affected ids fall to the uniform denial rather
than aborting the call.
The bodies are still fetched in ONE batch, so a Bugzilla that fails the
whole request over a single bad id still costs every Read-granted body in
that call — they all fall to the uniform denial together. That is
availability, not disclosure: only ids the caller already passed
classification for can poison it. Splitting that fetch per id would
multiply requests for bodies the caller is entitled to anyway, so it is
left alone deliberately.
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.
The race
bug_infoclassified one fetch and returned another. The Read verdict camefrom the
CLASSIFY_FIELDSrequest; the body came from a second request issuedafterwards. A bug embargoed in between was still returned in full — the
server's key is privileged enough that Bugzilla keeps serving it, and the
guard had simply stopped looking.
The previous PR widened this: with per-id classification, a bug named early in
a call is classified several round trips before its body arrives.
The fix
Every fetched body goes back through the guard and is served on its own
verdict. It costs no upstream request — the body is a superset of the
classification fields, so the re-check reads what is already in hand. Outcomes:
download_attachmentalready re-checked its blob response for this reason;bug_infonow matches it. The assembly moved intoassemble_bug_info, a freefunction, so the envelope is testable without an MCP request context.
Also: an I2 leak in the same block
The old code returned
Failed to fetch bug info\nReason: {e}to the client,and that
{e}carries Bugzilla's own message — includingBug #N does not exist.That is exactly the distinction I2 exists to erase. It is loggedserver-side now and the affected ids fall to the uniform denial, which also
means one unfetchable body no longer aborts the call.
This wasn't in the original finding list; it surfaced during review of the
previous PR. It lives in the lines this PR rewrites, so fixing it here avoids
touching them twice.
What this does NOT fix
The bodies are still fetched in one batch, so a Bugzilla that fails the
whole request over a single bad id still costs every Read-granted body in that
call — they all fall to the uniform denial together. That is availability, not
disclosure: only ids the caller already passed classification for can poison
it. Splitting it per id would multiply requests for bodies the caller is
entitled to anyway, so it is left alone deliberately.
Verification
140 tests (+7). The load-bearing one hands the assembler a Read verdict and a
body carrying an embargo group — it fails against the old code by
construction, which served
full.get(id)unconditionally. Others pin thesummary downgrade, a body granting neither read nor summary, byte-identical
restricted entries across all three failure modes (including field count, so
no mode is distinguishable), and request-order output on a mixed call.