Authorization & RBAC
Deciding what an authenticated principal may do. BitVault uses a layered RBAC + ABAC + ReBAC model evaluated by a deny-by-default policy engine (Cedar). Implements ADR-0010. Closes OWASP API1 (BOLA), API3 (BOPLA), and API5 (BFLA) — three of the top five API risks.
:::warning Deny-by-default Missing authorization checks default to denial, not permission. Every new endpoint must have an explicit authorization annotation. Absence of a grant is a 403, never a 200. :::
1. Policy Model: PEP → PDP, Deny-by-Default
flowchart LR
classDef e fill:#fde68a,stroke:#b45309,color:#111827;
classDef p fill:#bbf7d0,stroke:#15803d,color:#111827;
classDef d fill:#c7d2fe,stroke:#3730a3,color:#111827;
req["request"]:::e --> pep["PEP (gateway / service): intercept every access"]:::e
pep --> pdp["PDP: Cedar evaluate(principal, action, resource, context)"]:::p
rebac["ReBAC graph (sharing / membership)"]:::d --> pdp
rbac["RBAC roles"]:::d --> pdp
abac["ABAC attributes: tags, classification, residency"]:::d --> pdp
pdp --> dec{"permit / forbid (deny-by-default)"}:::p
dec -->|permit + obligations| act["allow (+ watermark / log / step-up)"]:::e
dec -->|forbid| block["403"]:::e
- PEP (Policy Enforcement Point) intercepts every object access at the gateway and within services.
- PDP (Cedar policy engine) evaluates
(principal, action, resource, context)→ permit or forbid. - Deny-by-default: absence of an explicit grant equals denial.
- Three layers compose: RBAC (coarse roles), ABAC (conditions / attributes), ReBAC (relationship graph for sharing and folder inheritance).
2. RBAC Roles
| Scope | Roles |
|---|---|
| Tenant / Organization | owner · admin · member · billing · auditor (read-only audit) |
| Resource (via ReBAC sharing) | owner · editor · commenter · viewer |
| Machine / Service account | Service-account roles + API-key scopes (always a strict subset of the principal’s rights) |
Each tenant has its own role namespace. A principal’s effective permission is role capabilities ∪ ReBAC grants ∩ API-key scope, evaluated by Cedar. Separation of duties is built in: auditor reads the audit log but cannot mutate data; billing cannot read files.
3. Object-Level Authorization (BOLA Defense)
BOLA (Broken Object Level Authorization) is the #1 API risk precisely because it is easy to check “is the user logged in?” and forget “does this user own this specific object?”.
Every resource access runs a PDP check against the specific object — not just the user’s role. Postgres RLS acts as a backstop: even if an application-layer check is omitted, the database returns no rows for objects belonging to another tenant.
- Never trust client-supplied
node_idorshare_id— resolve and verify against the authenticated principal at the PDP. - RLS is the backstop: a forgotten
WHERE tenant_id = ?in application code cannot produce a cross-tenant leak.
4. Field-Level Authorization (BOPLA Defense)
API responses and incoming request bodies use explicit allow-listed DTOs. Fields are never copied from raw input to domain models; each role sees only the fields it is permitted to read or write. This closes OWASP API3 (Broken Object Property Level Authorization) — both excessive data exposure (reading) and mass assignment (writing).
5. Function-Level Authorization (BFLA Defense)
Every gRPC method has an explicit role check. Admin endpoints require the admin or owner role and are additionally audited. The gateway enforces these checks before routing; services re-check on the verified internal auth context. There is no route that defaults to “allow all authenticated users”.
6. Scopes & Least Privilege for Machines
API keys and service accounts carry OAuth-style scopes (files:read, files:write, shares:create, optionally scoped to resource prefixes). A key’s scope is a strict subset of its principal’s rights — it can never exceed them. Default scope on issuance is minimal; additional scopes must be explicitly requested and granted.
7. Privilege-Escalation Prevention
- Role assignment is itself authorized: only
admin/owner, only within the tenant’s own namespace, and no self-elevation. - No confused deputy: services act with the caller’s authority carried in the verified request context, not ambient service privilege.
- Presigned URLs issued after PDP check: the authorization decision happens before URL issuance; the URL is a capability, not a second authorization gate.
- Plugin capability grants: plugins receive only explicitly granted capabilities within the WASM sandbox — they cannot call privileged host functions they were not granted.
8. Public Sharing Authorization
A public share token is treated as a capability: the anonymous holder is authorized for exactly the shared node, the granted permission level, and the time window — nothing else. Every presigned-URL issuance for a share re-validates the share record and the principal’s authority to grant access before issuing the URL.
9. Threats Addressed
| Threat | Control | Residual |
|---|---|---|
| Cross-object / cross-tenant access (BOLA) | PDP object-authz + RLS | Low (defense in depth) |
| Excessive data exposure / mass assignment | DTO allow-lists + field authz | Low |
| Privilege escalation via role change | Deny-by-default + authorized role changes only | Low |
| Admin function called by member (BFLA) | Function-level role checks, deny-by-default | Low |
| Policy bug / misgrant | Policy simulation (“prove no public read of /legal”) + CI policy tests | Low–medium |