Skip to content

fix(server): stop assignee updates bypassing the assign capability - #10

Merged
plusky merged 1 commit into
mainfrom
fix/update-fields-assignee-r3
Aug 8, 2026
Merged

fix(server): stop assignee updates bypassing the assign capability#10
plusky merged 1 commit into
mainfrom
fix/update-fields-assignee-r3

Conversation

@plusky

@plusky plusky commented Aug 8, 2026

Copy link
Copy Markdown
Owner

update_issue_fields gated on Capability::Fields and banned only security, project, reporter — but assignee was not banned, so a caller granted fields but denied assign could reassign issues by sending the exact body assign_issue would have sent, and the Assign gate never ran. parent (re-parenting across hierarchies) and resolution (edit-screen resolve) had the same shape.

The banned set is now derived structurally: capability_owned_fields is an exhaustive match on Capability returning 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 new capabilities! macro generates Capability::ALL from 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 justifies parent and resolution explicitly.

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 deny green. Dev-deps added: rmcp client feature and wiremock for the jirakeep crate (one-line lock diff, no new packages; production dependency graph byte-identical).

Process: AI-assisted — implemented and adversarially reviewed against docs/DESIGN.md by independent Claude agents (final verdict: approve, with empirical pre-fix-failure checks at the MCP boundary). Full verification suite green at the commit.

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 plusky added security Guard, key custody, or disclosure surface ai-assisted Found or drafted with AI assistance; claims verified against source, review independently labels Aug 8, 2026
@plusky
plusky enabled auto-merge (rebase) August 8, 2026 20:13
@plusky
plusky merged commit 639b89d into main Aug 8, 2026
11 checks passed
@plusky
plusky deleted the fix/update-fields-assignee-r3 branch August 8, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted Found or drafted with AI assistance; claims verified against source, review independently security Guard, key custody, or disclosure surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant