Full-stack RBAC audit covering frontend UX, backend architecture, and adversarial analysis. Implementation plan phased by severity (Critical → High → Medium → Low). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
228 lines
9.2 KiB
Markdown
228 lines
9.2 KiB
Markdown
# 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'<h1>{tree_name}</h1>')
|
|
html.append(f'<p><strong>Ticket:</strong> {session.ticket_number}</p>')
|
|
html.append(f'<p><strong>Client:</strong> {session.client_name}</p>')
|
|
```
|
|
|
|
If any field contains `<script>` tags, the exported HTML becomes an XSS payload. Since exports are likely pasted into ticketing systems or emailed, this is a stored XSS vector.
|
|
|
|
**Impact:** Arbitrary JavaScript execution in any system that renders the exported HTML.
|
|
|
|
#### 3. Default Secret Key in Source Code
|
|
|
|
**Location:** `backend/app/core/config.py:29`
|
|
|
|
If `.env` is missing or `SECRET_KEY` isn't set, JWTs are signed with a publicly known default string. Anyone can forge admin tokens.
|
|
|
|
**Impact:** Complete authentication bypass if deployed without proper `.env` configuration.
|
|
|
|
---
|
|
|
|
### HIGH
|
|
|
|
#### 4. No Access Control on `start_session`
|
|
|
|
**Location:** `backend/app/api/endpoints/sessions.py:69-109`
|
|
|
|
The endpoint checks tree existence and active status but does NOT check if the user has permission to access the tree. Any authenticated user can start a session on any active tree (including private/team-only) if they know the UUID, receiving the full `tree_structure` in the session's `tree_snapshot`.
|
|
|
|
**Impact:** Information disclosure — bypasses all tree visibility rules.
|
|
|
|
#### 5. No Token Revocation
|
|
|
|
**Location:** `backend/app/api/endpoints/auth.py:188-193`
|
|
|
|
Logout is a client-side no-op. JWTs remain valid until expiration (15 min access, 7 day refresh). Combined with no account deactivation, a terminated employee retains 7 days of access.
|
|
|
|
**Impact:** Cannot immediately revoke access to client troubleshooting data.
|
|
|
|
#### 6. No Account Deactivation (`is_active`)
|
|
|
|
**Location:** `backend/app/api/deps.py:66-72`
|
|
|
|
The `get_current_active_user` dependency is a no-op — no `is_active` field exists on the User model. The only way to block a user is to delete their database row, which cascades all their data.
|
|
|
|
**Impact:** No graceful way to disable accounts.
|
|
|
|
#### 7. No Rate Limiting on Auth Endpoints
|
|
|
|
Login, registration, and invite code validation have no rate limiting. The unauthenticated `/invites/validate/{code}` endpoint allows unlimited invite code enumeration.
|
|
|
|
**Impact:** Brute force attacks and invite code enumeration.
|
|
|
|
#### 8. `is_team_admin` Cannot Be Set
|
|
|
|
**Location:** `backend/app/models/user.py:28`
|
|
|
|
The field exists and permission checks reference it, but no API endpoint can set it. The entire "team admin" permission tier is dead code in production.
|
|
|
|
**Impact:** Category management, step category management, and team-scoped tag management are admin-only in practice.
|
|
|
|
---
|
|
|
|
### MEDIUM
|
|
|
|
#### 9. Frontend Ignores All Roles
|
|
|
|
**Location:** `frontend/src/components/layout/ProtectedRoute.tsx`, `frontend/src/router.tsx`
|
|
|
|
`ProtectedRoute` only checks `isAuthenticated`. No role-based route guards, no conditional UI rendering, no admin pages. All users see the same interface regardless of role.
|
|
|
|
Specific issues:
|
|
- Edit button appears on ALL tree cards for ALL users (`TreeLibraryPage.tsx:309-318`)
|
|
- "Create Tree" button visible to viewers who will get 403 (`TreeLibraryPage.tsx:146-155`)
|
|
- No 403 error handling in the Axios interceptor
|
|
- No admin dashboard despite backend admin APIs existing
|
|
|
|
#### 10. `None == None` Team Visibility Bug
|
|
|
|
**Location:** `backend/app/api/endpoints/steps.py:29-37`
|
|
|
|
```python
|
|
if step.visibility == 'team':
|
|
return step.team_id == user.team_id or user.role == 'admin'
|
|
```
|
|
|
|
When both `step.team_id` and `user.team_id` are `None`, this evaluates to `True`. Users with no team can see "team" steps that also have no team.
|
|
|
|
#### 11. Admin List vs. Get Tree Inconsistency
|
|
|
|
`build_tree_access_filter` (used in `list_trees`) does NOT include an admin bypass, but `get_tree` does. Admins can access individual trees by ID but cannot discover them via the list endpoint.
|
|
|
|
#### 12. No Audit Logging
|
|
|
|
No record of administrative actions: tree deletion, invite code creation/revocation, category changes, permission modifications, or failed login attempts.
|
|
|
|
#### 13. No Delete UI for Trees or Steps
|
|
|
|
`treesApi.delete()` and `stepsApi.delete()` exist in the frontend API client but are never wired to any button or UI component. Trees and steps cannot be deleted from the frontend.
|
|
|
|
#### 14. Tree Node Deletion Without Confirmation
|
|
|
|
**Location:** `frontend/src/components/tree-editor/NodeList.tsx:288-298`
|
|
|
|
Clicking the trash icon in the tree editor immediately deletes the node with no confirmation dialog.
|
|
|
|
---
|
|
|
|
### LOW
|
|
|
|
| # | Issue | Location |
|
|
|---|-------|----------|
|
|
| 15 | `viewer` role accepted but barely enforced | `schemas/user.py`, all endpoints |
|
|
| 16 | No password complexity beyond 10-char minimum | `schemas/user.py:13` |
|
|
| 17 | Soft delete doesn't cascade-clean tags, folder memberships | `trees.py` delete endpoint |
|
|
| 18 | Debug CORS endpoint exposed in production | `main.py:86-94` |
|
|
| 19 | Tag search `ilike` doesn't escape `%` and `_` wildcards | `tags.py:97` |
|
|
| 20 | No team existence validation for admin step creation | `steps.py` create endpoint |
|
|
|
|
---
|
|
|
|
## MSP-Specific Concerns
|
|
|
|
1. **Weak multi-tenancy:** Admin role transcends all teams. Team A's admin could access Team B's client troubleshooting data (ticket numbers, client names, IP addresses in scratchpads).
|
|
|
|
2. **No data retention/purge:** Session scratchpads and decision notes contain sensitive client information with no cleanup mechanism.
|
|
|
|
3. **No team-level session visibility:** Supervisors can't review their team's sessions — only the session owner can view them.
|
|
|
|
4. **Step Library IP leakage:** Public steps expose team-specific troubleshooting procedures (commands, scripts, procedures) to all teams with no approval workflow.
|
|
|
|
---
|
|
|
|
## Challenged Assumptions
|
|
|
|
| Assumption | Reality |
|
|
|-----------|---------|
|
|
| "Invite codes protect registration" | They don't prevent role escalation — the role field is independent |
|
|
| "Team scoping = tenant isolation" | Admin role transcends all teams with no boundary |
|
|
| "Soft delete is sufficient" | Leaves data artifacts (sessions with tree_snapshot, folder memberships, tag assignments) |
|
|
| "JWT statelessness is acceptable" | For an MSP tool with client data, inability to immediately revoke access is a liability |
|
|
| "Frontend doesn't need role awareness" | Without it, every user sees affordances they can't use — confusing UX and attacker map |
|
|
|
|
---
|
|
|
|
## Test Coverage Gaps
|
|
|
|
The existing test suite does NOT cover:
|
|
- Cross-user resource access (user A accessing user B's sessions/folders/steps)
|
|
- Non-admin attempting tree deletion
|
|
- Viewer role restrictions
|
|
- Admin role escalation at registration
|
|
- Team-based access controls (no team fixtures exist)
|
|
- Step library permission checks (no step tests at all)
|
|
- Category/tag/folder cross-user access
|
|
- Concurrent session access across users
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- Frontend source: `frontend/src/`
|
|
- Backend endpoints: `backend/app/api/endpoints/`
|
|
- Auth dependencies: `backend/app/api/deps.py`
|
|
- Models: `backend/app/models/`
|
|
- Tests: `backend/tests/`
|