Repository navigation
feat(rbac): read-only is a permission; routes declare that they read - #561
Merged
Merged
Conversation
The Demo role was held read-only by a URL allow-list in platform code plus role-name checks. Now: - Routes declare `reads` beside their handler (a SELECT goes over POST but changes nothing); the binder passes it as route config. - `app.write`: an account without it may call a route only when it is a GET or declares `reads`. `account.self`: profile, password, reset, verify and sign-out-others. Both are install permissions every role from viewer up has; the Demo role is simply the role without them, and an admin can make any role read-only (or let the demo save) from the role matrix. - A one-time grant gives both to roles customized before they existed, so nothing that could save stops saving; an admin's later removal sticks. - The test that calls every guarded write now keeps the list of `reads` routes reviewed instead of an allow-list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: 21d57dbd-2e03-48aa-98b8-9175bdc26de9) |
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.
Closes the
rbac-route-readsitem from the #554 review.Before
The Demo role was held read-only by two things:
platform/authorization/demo-access.ts, which put feature routes in platform code.isDemoRolein sign-out-others, verify/send, forgot-password and the profile menu.The permission matrix and
can()didn't know about any of this. An admin could not make another role read-only, or let the demo save.Now
readsbeside its handler, for examplerouter.post('/sql/execute', reads, …). A POST that carries a SELECT changes nothing. The router records the marker, and the binder passes it to Fastify as route config.app.write(Save and change): an account without it may call a route only when it is a GET or declaresreads. Every other write is refused before the handler runs, with the same refusal as before.account.self(Manage own account): needed for sign-out-others, verify/send and forgot-password. The profile menu usescan('account.self'), and the banner usescan('app.write').demo-access.tsis removed.api/demo-account.test.tsstill calls every guarded write as a read-only account. It now also checks the declaredreadsroutes against a reviewed list, so marking a new one is a deliberate test change.docs/DEPLOYMENT.mdexplains the permission.Tests
read-only.test.ts: the rule, plus a role withapp.writeremoved is refused writes.rbac.service.test.ts: the one-time grant gives customized roles both permissions, never gives them to Demo, and an admin's later removal sticks.demo-account.test.ts: every guarded write as demo, withreadsroutes passing and the rest refused; the reviewedreadslist; and sign-out-others, verify/send and forgot-password, now keyed onaccount.self.eslint .and the security lint are clean. Unit suite: 6563 pass.🤖 Generated with Claude Code
Note
High Risk
Changes global auth enforcement on every protected write and introduces new permissions with migration backfill; mis-marked
readsroutes could let read-only accounts reach unintended handlers.Overview
Replaces Demo-role name checks and a central URL allow-list with install permissions so any role can be made read-only, not only
demo.app.writeblocks mutating API calls unless the route is GET or marked with a newreadsdeclaration beside the handler (e.g.POST /sql/execute, schema/compare loads, preferences, workspace select). The session guard usesrequestAllowedand route config from the router;demo-access.tsis removed.account.selfgates sign-out-others, verify/send, and forgot-password (and the profile menu) instead ofisDemoRole.Adds
app.writeandaccount.selfto the permission matrix (Demo defaults omit both; viewer+ include both), a one-time RBAC grant for customized roles on upgrade, UI banner/menu updates viacan(), docs, and tests that enumerate reviewedreadsroutes.Reviewed by Cursor Bugbot for commit 14a9b7f. Bugbot is set up for automated code reviews on this repo. Configure here.