fix(security): per-channel write gate on channel mutators (MM-SEC-4)#2907
fix(security): per-channel write gate on channel mutators (MM-SEC-4)#2907
Conversation
The legacy channel-mutator endpoints all gated on a static
`channel_0:read|write` permission while operating on `:id` from the
URL. A user with `channel_0:write` could rename, re-PSK, delete,
import-into, or reorder ANY channel — including ones the operator
deliberately walled off. Authenticated-user privilege escalation; not
anonymous-exploitable, but it broke the per-channel permission model
the rest of the codebase honors.
Switches each endpoint from `requirePermission('channel_0', ...)` to
`requireAuth()` plus a per-row `hasPermission(req.user, 'channel_${id}',
...)` check using the URL's actual `:id`/`:slotId`. Admins always pass.
Endpoints touched (server.ts):
- GET /api/channels/:id/export — read perm for the SPECIFIC channel
- PUT /api/channels/:id — write perm for the SPECIFIC channel
- DELETE /api/channels/:id — write perm for the SPECIFIC channel
- POST /api/channels/:slotId/import — write perm for the destination slot
- POST /api/channels/reorder — write perm for EVERY moved slot
For reorder: the affected-slot set is the union of source and
destination indices for any pair where `newOrder[i] !== i`. The caller
must hold write permission for every slot in that set. Permutations
are cycle-closed, so this is symmetric — checking destination slots
implicitly covers source slots, but the explicit union is clearer.
Refs: MM-SEC-4 in docs/security/2026-05-05-mm-sec-1-2-3-review.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job Pull Request ReviewReview Tasks
Overall AssessmentThis is an excellent security fix that properly addresses MM-SEC-4 privilege escalation. The implementation is sound, well-documented, and follows established patterns in the codebase. Security Analysis ✅Strong Points:
Reorder endpoint logic is particularly well-designed:
Code Quality ✅Excellent implementation:
Error handling example: return res.status(403).json({
error: 'Insufficient permissions',
code: 'FORBIDDEN',
required: { resource: channelResource, action: 'write' },
message: `Write access required for channel ${channelId}`
});Performance Considerations ✅Efficient implementation:
The Edge Cases & Potential Issues
|
System Test ResultsMeshMonitor System Test ResultsTest Run: 2026-05-05 20:25:58 EDT Test Summary
✅ Overall Result: PASSEDAll deployment configurations are working correctly! Test DetailsConfiguration Import:
Quick Start Test:
Security Test:
V1 API Test:
Reverse Proxy Test:
Reverse Proxy + OIDC Test:
Virtual Node CLI Test:
Backup & Restore Test:
Database Migration Test:
DB Backing Consistency Test:
|
…2909) Bumps all five canonical version sources (package.json, package-lock.json, helm/meshmonitor/Chart.yaml, desktop/src-tauri/tauri.conf.json, desktop/package.json) from 4.2.0 to 4.2.1. Adds a critical-priority security entry to docs/public/news.json covering the MM-SEC-1/2/3/4 series merged in #2904/#2905/#2906/#2907 plus the regression lock-in in #2908. Pinned to minVersion 4.0.0 so every 4.x deployment sees the advisory until upgraded — the affected code paths (anonymous /api/settings, /api/channels, /api/poll, plus the channel-mutator perms) shipped in the 4.0 release line. The news entry covers per-finding impact, pre-patch operator mitigations, and post-upgrade rotation guidance (channel PSKs that were exposed cannot be undone retroactively — operators who ran a public-viewer dashboard should rotate). Links to the full SECURITY_ADVISORY.md and the four merged PRs. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
channel_0:read|writepermission while operating on the URL's:id. A user withchannel_0:writecould mutate any channel — privilege escalation between authenticated users that bypassed the per-channel permission model.requireAuth()+ per-rowhasPermission(req.user, 'channel_${id}', ...)using the URL's actual id. Admins always pass.Background
Reported as MM-SEC-4 in
docs/security/2026-05-05-mm-sec-1-2-3-review.md(the follow-up flagged in the MM-SEC-2 advisory). Not anonymous-exploitable — anonymous defaults don't grant write — but it broke the per-channel permission model the rest of the codebase honors.Endpoints
GET /api/channels/:id/exportchannel_0:readchannel_${id}:readPUT /api/channels/:idchannel_0:writechannel_${id}:writeDELETE /api/channels/:idchannel_0:writechannel_${id}:writePOST /api/channels/:slotId/importchannel_0:writechannel_${slotId}:writePOST /api/channels/reorderchannel_0:writechannel_${i}:writefor every moved slotFor
reorder: the affected-slot set is the union of source and destination indices for any pair wherenewOrder[i] !== i. The caller must hold write permission for every slot in that set. Permutations are cycle-closed, so the symmetric check is equivalent — the explicit union is clearer.Files
src/server/server.ts— five endpoint patches.Test plan
npx tsc --noEmitcleanchannel_2:write,PUT /api/channels/2succeeds;PUT /api/channels/0returns 403/api/channels/reorderswapping slots 0 and 1 — 403 (lackschannel_0:write); swapping slots 2 and 3 still succeeds (assuming both are granted)Behavior change
Users who previously relied on having
channel_0:writeto manage every channel will now need explicit per-channel grants. This matches the documented per-channel permission model and the existing v1 API behavior. Operators may need to add per-channel writes for accounts that were previously over-privileged via channel_0.Out of scope
server.tsmonolith. The fix is a straight pattern application of the per-row check that the v1 channels routes already use and have coverage for.🤖 Generated with Claude Code