fix(server): stop assignee updates bypassing the assign capability - #10
Merged
Conversation
update_issue_fields now refuses assignee/resolution/parent alongside the privileged fields: each is owned by its dedicated capability gate, so a bare fields grant can no longer assign, resolve, or re-parent (I7), and the change only tightens access (I9). Per DESIGN.md testing bar, rmcp-duplex + wiremock integration tests pin the refusal at the tool boundary, not only in the refused_field helper.
plusky
enabled auto-merge (rebase)
August 8, 2026 20:13
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.
update_issue_fieldsgated onCapability::Fieldsand banned onlysecurity,project,reporter— butassigneewas not banned, so a caller grantedfieldsbut deniedassigncould reassign issues by sending the exact bodyassign_issuewould have sent, and theAssigngate never ran.parent(re-parenting across hierarchies) andresolution(edit-screen resolve) had the same shape.The banned set is now derived structurally:
capability_owned_fieldsis an exhaustive match onCapabilityreturning the fields each capability owns, so adding a future capability forces a compile-time revisit instead of relying on a hand-maintained string list. A newcapabilities!macro generatesCapability::ALLfrom the same variant list so the two cannot drift. DESIGN.md's I7 wording now names the "field whose mutation has its own capability" rule and justifiesparentandresolutionexplicitly.Invariants: I7 (no privileged-field smuggling), I9 (strictly tightening). The refusal is only reachable after
deny_unless, so no I2 existence oracle.Tests: this PR went through three adversarial review rounds. Rounds one and two proved the helper-level tests could not pin the fix (reverting the handler wiring left the suite green). Round three adds
tests/server_mcp.rs— a real rmcp client over an in-process duplex transport with wiremock as Jira — asserting the refusal at the tool boundary with.expect(0)on the PUT endpoint, plus an allowed-edit control test proving refusals aren't vacuous. The final reviewer independently re-verified: gate reverted ⇒ boundary tests fail, helper tests stay green (the exact prior hole), full suite +cargo denygreen. Dev-deps added:rmcpclientfeature andwiremockfor thejirakeepcrate (one-line lock diff, no new packages; production dependency graph byte-identical).Process: AI-assisted — implemented and adversarially reviewed against
docs/DESIGN.mdby independent Claude agents (final verdict: approve, with empirical pre-fix-failure checks at the MCP boundary). Full verification suite green at the commit.