fix(blocklist): require admin for global block rule management - #234
fix(blocklist): require admin for global block rule management#234Waiel5 wants to merge 3 commits into
Conversation
`/api/blocklist` was mounted alongside the ordinary member routes, without the `requireAdmin` guard that `/api/suppressions`, `/api/webhook` and `/api/oauth-apps` each carry. The blocklist is global instance security state in exactly the way those are, so this reads as an omission rather than a decision: any authenticated member could list, add and remove block rules for the whole deployment. The sharpest edge is `DELETE /api/blocklist/mail`, which permanently deletes every currently-hidden message from blocked senders — database rows and the R2 objects behind them — and applies no inbox scoping at all. A member could therefore destroy mail belonging to inboxes they hold no permission to read, with no undo. Guarded with the same two-line `app.use` pair the neighbouring admin routers use, so it applies to every authentication method rather than only one. The existing tests in this file all authenticate as an admin, because createTestUser defaults to role "admin" — which is why an unguarded router passed a full suite. The added cases run as a member across all four routes and fail against the previous mounting.
|
Note on the red check: it is the Exactly one check-run exists on this commit. I can't clear it myself — Locally: 18 passed across |
|
Appreciate the contribution, and I think this is totally valid. saasmail is meant to be a collaborative inbox for SaaS teams to manage mail together mostly. Wondering what your use case is when using saasmail? |
|
Sure — I am running an instance on my own domain and building a native iOS client against it (https://github.com/Waiel5/saasmail-mobile). Everything I have opened here came out of that: building a client is a good way to walk the whole API surface, and it surfaces things the dashboard never exercises because the dashboard and the worker ship from the same commit and a third-party client does not. The collaborative-inbox model is exactly why this one bothered me. On a single-operator instance a member-vs-admin distinction is academic, but the moment there is a team, So it is less about my use case and more that the guard looked like an oversight against its three neighbours rather than a decision. |
There was a problem hiding this comment.
Pull request overview
Restricts global blocklist management and destructive mail purging to administrators.
Changes:
- Adds
requireAdminguards to all blocklist routes. - Adds member-denial and admin-access tests.
- Documents the security fix.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
worker/src/index.ts |
Applies admin middleware to blocklist routes. |
worker/src/__tests__/blocklist-router.test.ts |
Tests authorization across all blocklist operations. |
CHANGELOG.md |
Records the security fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
/api/blocklistis mounted alongside the ordinary member routes, without therequireAdminguard that/api/suppressions,/api/webhookand/api/oauth-appseach carry. The blocklist is global instance security state in exactly the way those are, so this reads as an omission rather than a decision: any authenticated member can list, add and remove block rules for the whole deployment.The sharpest edge is
DELETE /api/blocklist/mail, which permanently deletes every currently-hidden message from blocked senders — database rows and the R2 objects behind them — and applies no inbox scoping at all. A member can therefore destroy mail belonging to inboxes they hold no permission to read, with no undo.Changes
/api/blocklistand/api/blocklist/*withrequireAdmin, using the same two-lineapp.usepair the neighbouring admin routers use, so it applies to every authentication method rather than only one.app.routedown next to the other admin-guarded routers so the grouping matches the policy.Release impact
Patch — bug fix or internal change, no new behaviour
Bug Fix
Test plan
yarn test worker/src/__tests__/blocklist-router.test.ts+blocklist-lib+purge-blocked— 18 passedprettier --checkcleanNotes for reviewers
This is a behaviour change for any non-admin currently using these endpoints, so it is worth a conscious call rather than a rubber stamp — but given the purge route deletes mail deployment-wide, admin-only looks like the intended policy.
Same blind spot as the attachments PR: every existing test here authenticates as an admin, because
createTestUserdefaults torole: "admin", so an unguarded router passed the suite. The added cases run as a member across all four routes.Checklist
yarn db:generate) if the schema changed — n/aCHANGELOG.mdunder## [Unreleased]