diff --git a/docs/plans/2026-02-05-permissions-audit-design.md b/docs/plans/2026-02-05-permissions-audit-design.md new file mode 100644 index 00000000..75652ef9 --- /dev/null +++ b/docs/plans/2026-02-05-permissions-audit-design.md @@ -0,0 +1,227 @@ +# Permissions & RBAC Audit — Design Document + +> **Date:** 2026-02-05 +> **Status:** Draft +> **Scope:** Full-stack permissions audit across trees, steps, sessions, teams, and admin features + +--- + +## Background + +ResolutionFlow has a role-based access control system with three roles (`admin`, `engineer`, `viewer`), a `is_team_admin` boolean, and team-scoped resources. A comprehensive audit was performed across the entire codebase — frontend UX, backend architecture, and adversarial analysis — to assess the current state of permissions enforcement. + +**Audit methodology:** Three independent reviews were conducted: +1. **Frontend UX audit** — every page, component, API client, store, and router +2. **Backend architecture audit** — every endpoint, dependency, model, schema, and test +3. **Devil's advocate critique** — security gaps, edge cases, challenged assumptions + +--- + +## Current State Summary + +### What Works + +- **Session ownership** is properly enforced — users can only see/modify their own sessions +- **Folder ownership** is properly scoped to the creating user +- **Tree CRUD** has reasonable backend permission checks (engineer+ to create, author/admin/team-admin to edit, admin-only delete) +- **Step Library** enforces visibility levels (private/team/public) and ownership for edit/delete +- **Category/Tag management** is properly gated to admin and team admin roles +- **Invite code management** is admin-only + +### What's Broken + +The permission system has critical vulnerabilities, significant gaps, and inconsistencies across resource types. + +--- + +## Findings by Severity + +### CRITICAL + +#### 1. Self-Assignable Admin Role at Registration + +**Location:** `backend/app/schemas/user.py:14`, `backend/app/api/endpoints/auth.py:74-78` + +The `UserCreate` schema accepts a `role` field from client input. The registration endpoint passes it directly to the User model. Any user with a valid invite code can register with `role: "admin"` and gain full admin privileges. + +```python +# Current — VULNERABLE +class UserCreate(UserBase): + role: str = Field(default="engineer", ...) + +# Registration blindly trusts client input +new_user = User(role=user_data.role, ...) +``` + +**Impact:** Complete authorization bypass. A single request escalates to admin. + +#### 2. XSS in HTML Export + +**Location:** `backend/app/api/endpoints/sessions.py:349-405` + +The HTML export function directly interpolates user-provided strings without escaping: +```python +html.append(f'
Ticket: {session.ticket_number}
') +html.append(f'Client: {session.client_name}
') +``` + +If any field contains `