fix(roles): prevent privilege escalation via role permissions #93

Merged
icub3d merged 1 commit from feature/75-role-perm-escalation into main 2026-07-08 03:35:19 +00:00
Owner

Summary

Fixes the critical privilege-escalation hole reported in #75.

create_role and update_role passed the requested permission bits straight to storage, gating only on MANAGE_ROLES and role position — never on whether the caller actually held those bits. A user with MANAGE_ROLES but not ADMINISTRATOR could:

  1. POST /roles with permissions = ADMINISTRATOR, positioned just below their own role;
  2. self-assign it via PUT /members/{self}/roles/{new_role} (only a position check);
  3. re-issue any request → full server control.

Fix

New ensure_can_grant(auth, requested) guard:

  • Administrators may grant any permission.
  • Everyone else may only grant permission bits they currently hold. A non-admin's effective permissions never includes ADMINISTRATOR, so (requested & !auth.permissions) != 0 rejects any unheld bit (including ADMINISTRATOR) with 403.

Wired into both create_role (before position handling) and update_role (when permissions is present, alongside the existing @admin-immutable guard).

Tests

Added regression tests in server/src/roles/mod.rs:

  • manage_roles_user_cannot_grant_administrator → 403
  • manage_roles_user_cannot_grant_unheld_permission → 403, plus the allowed case (granting only held bits) → 200
  • manage_roles_user_cannot_escalate_via_update → 403

Verified the tests fail without the guard (revert-check) and pass with it. Full server suite green (118 tests), cargo clippy -p server --all-targets -- -D warnings clean.

Closes #75

## Summary Fixes the critical privilege-escalation hole reported in #75. `create_role` and `update_role` passed the requested permission bits straight to storage, gating only on `MANAGE_ROLES` and role *position* — never on whether the caller actually held those bits. A user with `MANAGE_ROLES` but **not** `ADMINISTRATOR` could: 1. `POST /roles` with `permissions = ADMINISTRATOR`, positioned just below their own role; 2. self-assign it via `PUT /members/{self}/roles/{new_role}` (only a position check); 3. re-issue any request → full server control. ## Fix New `ensure_can_grant(auth, requested)` guard: - Administrators may grant any permission. - Everyone else may only grant permission bits they currently hold. A non-admin's effective `permissions` never includes `ADMINISTRATOR`, so `(requested & !auth.permissions) != 0` rejects any unheld bit (including `ADMINISTRATOR`) with `403`. Wired into both `create_role` (before position handling) and `update_role` (when `permissions` is present, alongside the existing `@admin`-immutable guard). ## Tests Added regression tests in `server/src/roles/mod.rs`: - `manage_roles_user_cannot_grant_administrator` → 403 - `manage_roles_user_cannot_grant_unheld_permission` → 403, plus the allowed case (granting only held bits) → 200 - `manage_roles_user_cannot_escalate_via_update` → 403 Verified the tests fail without the guard (revert-check) and pass with it. Full server suite green (118 tests), `cargo clippy -p server --all-targets -- -D warnings` clean. Closes #75
fix(roles): prevent privilege escalation via role permissions
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 36s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 1m1s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 1m6s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 22m33s
Security Audit / Semgrep (SAST) (pull_request) Failing after 22m35s
CI / Client — test & lint (pull_request) Failing after 22m35s
CI / Server — test & lint (pull_request) Failing after 22m35s
d01e513fd5
create_role and update_role passed the requested permission bits straight
to storage, gating only on MANAGE_ROLES and role position. A user with
MANAGE_ROLES but not ADMINISTRATOR could mint a role carrying ADMINISTRATOR
(positioned below their own), self-assign it, and escalate to full control.

Add ensure_can_grant: administrators may grant any permission; everyone
else may only grant permission bits they themselves hold. Wired into both
create_role and update_role.

Adds regression tests covering: granting ADMINISTRATOR (403), granting an
unheld permission (403), the allowed case of granting only held bits (200),
and escalation via update_role (403).

Closes #75

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
icub3d merged commit ab4b17f846 into main 2026-07-08 03:35:19 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom!93
No description provided.