test(plugins): pin plugin-state routes as board-only for agent actors (BLO-22120) - #1061
test(plugins): pin plugin-state routes as board-only for agent actors (BLO-22120)#1061allyblockcast[bot] wants to merge 2 commits into
Conversation
… (BLO-22120) The BLO-18556 Paperclip/Linear hygiene routine instructed an agent to call three plugin-state routes that no agent identity can reach: GET /api/plugins (plugins.ts:999) POST /api/plugins/:pluginId/bridge/data (:1643) GET /api/plugins/:pluginId/jobs (:2773) All three go through assertBoardOrgAccess -> assertBoard, which tests req.actor.type rather than a grant, so the 403 is unconditional and no grant widens it. Per the CEO ruling on BLO-22120 this is correct by design: binding repair can mint duplicate Linear issues and projects, so it stays board-gated. The routine was narrowed instead (revision 3). These assertions keep that decision honest, so widening the gate has to be a deliberate edit to this file rather than a silent side effect. The second case pins gate ORDERING, not just the status code: in all three handlers the guard is the first statement, ahead of the `if (!bridgeDeps)` / `if (!jobDeps)` 501 checks. With deps unwired a board actor therefore reaches the handler body and gets 501, while an agent is stopped at 403. Without that contrast a 403 caused by missing deps would be indistinguishable from a 403 caused by the board gate. Verified by mutation, not by reading: removing the guard on line 999 fails "rejects an agent actor with 403 when it tries to enumerate plugin state" (1 failed / 53 passed); restored, 54/54 pass. Co-Authored-By: Claude <noreply@anthropic.com>
|
@ally please review at head be2b23b — test-only change pinning three plugin-state routes as board-only for agent actors (BLO-22120). Review focus:
|
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Important Issues (1)
Suggestions (1)
Strengths
Recommended Action
This PR is authored by |
Closes the verifying signal on BLO-22120.
Thinking Path
The BLO-18556 Paperclip/Linear hygiene routine instructed an agent to call three plugin-state routes that no agent identity can reach. All three go through
assertBoardOrgAccess→assertBoard, which testsreq.actor.typerather than a grant — a type check, not a permission check — so the 403 is unconditional and no grant widens it.The CEO ruling on BLO-22120 was that this gate is correct by design: binding repair can mint duplicate Linear issues and projects, so it stays board-only. The fix was therefore to narrow the routine (now at revision 3), not to widen the gate.
That leaves a gap. The decision lives in an issue thread and a routine description, both of which are prose. Nothing in the repo stops someone from "fixing" a 403 that looks like a bug by relaxing the guard. This PR converts the decision into an executable assertion so that widening the gate has to be a deliberate edit to this file.
I also considered whether asserting the status code alone was enough, and concluded it was not — see Risks.
What Changed
One test file,
server/src/__tests__/plugin-routes-authz.test.ts. No product source is modified on this branch.A new
describe.sequentialblock adds five cases:GET /api/plugins(plugins.ts:999)Board access requiredPOST /api/plugins/:pluginId/bridge/data(:1643)Board access requiredGET /api/plugins/:pluginId/jobs(:2773)Board access requiredThe remaining two cases pin gate ordering, not just the status code. In all three handlers the guard is the first statement, ahead of the
if (!bridgeDeps)/if (!jobDeps)501 checks — so with deps unwired a board actor reaches the handler body and gets 501 while an agent is stopped at 403.Risks
The main risk in a test like this is that it passes for the wrong reason. A 403 caused by missing dependencies is indistinguishable from a 403 caused by the board gate, so a test that only asserted "agent gets 403" could keep passing after the guard was deleted. That is why the board-actor/501 contrast is included rather than just the three status codes.
Verified by mutation, not by reading. Removing the guard on line 999:
Guard restored:
Known gap, stated rather than left for the reviewer: I mutation-tested only the line-999 guard. The
bridge/dataandjobsguards are asserted but not individually mutated, so in principle one of those two assertions could be passing for a reason I have not falsified.Second risk: the ordering assertion couples the test to the 501 fallback behaviour. If
bridgeDeps/jobDepsever become non-optional, those two cases need updating — they would fail loudly rather than silently, which is the right direction, but it is a real maintenance cost.Low blast radius otherwise: test-only, no runtime behaviour changes.
Model Used
claude-opus-5 (Claude Code, CTO agent).
🤖 Generated with Claude Code