fix(auth): enforce role ceiling on user update and fix OAuth callback XSS #426

Merged
Claude-Run merged 1 commit from fix/PMS-625-auth-audit-privesc-xss into main 2026-07-04 03:30:34 +02:00
Member

Auth/login/2FA/session security audit (PMS-625). Two exploitable gaps found and fixed; the full findings table (including follow-ups to file) lives in docs/dev-docs/security/auth-audit-PMS-625.md.

F1 (privilege escalation): the admin PUT /api/v1/auth/users/{id} path checked only is_admin(), not the PMS-503 can_grant ceiling that POST /users already enforces. A tenant admin could set any user's role - including their own via PUT /users/{self}, which is not the role-sanitizing /me handler - to super_admin, a platform-level cross-tenant account. Mirror the create-side ceiling on the update path so an above-ceiling grant returns 403.

F2 (reflected XSS): google_login::callback_html embedded serde_json output into an inline

Auth/login/2FA/session security audit (PMS-625). Two exploitable gaps found and fixed; the full findings table (including follow-ups to file) lives in docs/dev-docs/security/auth-audit-PMS-625.md. F1 (privilege escalation): the admin PUT /api/v1/auth/users/{id} path checked only is_admin(), not the PMS-503 can_grant ceiling that POST /users already enforces. A tenant admin could set any user's role - including their own via PUT /users/{self}, which is not the role-sanitizing /me handler - to super_admin, a platform-level cross-tenant account. Mirror the create-side ceiling on the update path so an above-ceiling grant returns 403. F2 (reflected XSS): google_login::callback_html embedded serde_json output into an inline <script> and wrongly assumed serde_json HTML-escapes <, >, and &. The OAuth error branch reflects the fully attacker-controlled error_description query param into the payload, so /auth/google/callback?error=x&error_description=</script><script>... executes as markup on the API origin. Route both the payload and origin JSON through escape_json_for_script (<, >, &, U+2028, U+2029 -> \uXXXX); the output is byte-for-byte equivalent JSON but cannot close the script element. Tests: tests/auth.rs::update_user_enforces_role_ceiling (admin->super_admin 403, admin->manager 200); google_login unit tests callback_html_neutralizes_script_breakout and escape_json_for_script_is_reversible_json. #PMS-625
fix(auth): enforce role ceiling on user update and fix OAuth callback XSS
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 49s
Check / fmt + clippy + build + tests (pull_request) Successful in 1m48s
Integration / integration tests (pull_request) Successful in 9m15s
Create release / Gate (release-branch merges only) (pull_request) Successful in 1s
Create release / Create release from merged PR (pull_request) Has been skipped
8fe83cd6f7
Auth/login/2FA/session security audit (PMS-625). Two exploitable gaps found and fixed; the full findings table (including follow-ups to file) lives in docs/dev-docs/security/auth-audit-PMS-625.md.

F1 (privilege escalation): the admin PUT /api/v1/auth/users/{id} path checked only is_admin(), not the PMS-503 can_grant ceiling that POST /users already enforces. A tenant admin could set any user's role - including their own via PUT /users/{self}, which is not the role-sanitizing /me handler - to super_admin, a platform-level cross-tenant account. Mirror the create-side ceiling on the update path so an above-ceiling grant returns 403.

F2 (reflected XSS): google_login::callback_html embedded serde_json output into an inline <script> and wrongly assumed serde_json HTML-escapes <, >, and &. The OAuth error branch reflects the fully attacker-controlled error_description query param into the payload, so /auth/google/callback?error=x&error_description=</script><script>... executes as markup on the API origin. Route both the payload and origin JSON through escape_json_for_script (<, >, &, U+2028, U+2029 -> \uXXXX); the output is byte-for-byte equivalent JSON but cannot close the script element.

Tests: tests/auth.rs::update_user_enforces_role_ceiling (admin->super_admin 403, admin->manager 200); google_login unit tests callback_html_neutralizes_script_breakout and escape_json_for_script_is_reversible_json.

#PMS-625
Claude-Run deleted branch fix/PMS-625-auth-audit-privesc-xss 2026-07-04 03:30:34 +02:00
Sign in to join this conversation.
No reviewers
No labels
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
psa-systems/mokosh-server!426
No description provided.