Access Control Model
Status: DRAFT
Owner: Engineering
Last Review: 2026-02-21
Applicable Standards: SOC 2 (CC6.1, CC6.2, CC6.3) / GDPR (Art. 25, Art. 32)
1. Purpose
This document describes the access control model that governs who can view, edit, and manage data within the Equa platform. It covers role-based access control (RBAC), the cascading permission model for organization hierarchies, data room access, agent permission controls, unauthenticated guest chat access, and frontend enforcement.
2. Scope
| Component | In Scope | Notes |
|---|
| equa-server | Yes | Authorization module, permission checks, role management |
| equa-web | Yes | Frontend route guards, permission HOC, UI-level gating |
| equabot-gateway | Partial | Agent permission proxy that checks tool permissions |
| PostgreSQL | Yes | Role/permission/member junction tables |
3. RBAC Overview
Source: equa-server/modules/auth/src/authorization.ts, equa-server/modules/common/src/permissions.ts, equa-server/modules/common/src/roles.ts
Equa implements role-based access control (RBAC) with the following characteristics:
- Permissions are granted through roles, not directly to users
- Roles are assigned to members within the context of an organization
- A user can hold different roles in different organizations
- Global roles exist for site-level administration
- Permissions cascade through organization hierarchies
4. Permission Definitions
Source: equa-server/modules/common/src/permissions.ts
4.1 Built-In Permissions (14)
| Permission | UUID | Scope |
|---|
viewOrganization | Built-in | Read organization details |
viewCapTable | Built-in | Read cap table, shareholdings, security types |
viewDocuments | Built-in | Read data room files and directories |
viewMembers | Built-in | Read member list and profiles |
editOrganizationDetails | Built-in | Modify organization settings |
editCapTable | Built-in | Create/modify shareholdings, issue equity |
editDocuments | Built-in | Upload, rename, delete files in data room |
editMembers | Built-in | Add/remove members, change titles |
deleteDocuments | Built-in | Permanently remove files |
signing | Built-in | Sign documents and agreements |
fullVoting | Built-in | Vote on all resolutions |
partialVoting | Built-in | Vote on subset of resolutions |
editBilling | Built-in | Manage subscription and payment methods |
viewIncentivePlan | Built-in | Read ESOP/incentive plan details |
editIncentivePlan | Built-in | Create/modify incentive plans |
4.2 Custom Permissions
Organizations can define custom roles with any combination of the built-in permissions via the Roles entity:
| Field | Type | Purpose |
|---|
id | UUID | Role identifier |
name | string | Display name |
description | string | Role description |
owner | UUID (nullable) | Organization that owns this role |
isShared | boolean | If true, role is available across organizations |
Source: equa-server/modules/persistence/src/schema.ts (Roles entity)
5. Built-In Roles
Source: equa-server/modules/common/src/roles.ts
| Role | Typical Permissions | Use Case |
|---|
admin | All permissions | Organization administrator |
auditor | All view permissions | External auditor with read-only access |
director | View + edit org, cap table, documents | Board member |
guest | viewOrganization only | Minimal access visitor |
investor | View org, cap table, documents | Equity holder viewing their position |
legal | View + edit documents, signing | Legal counsel |
partner | View + edit org, cap table, members | Operating partner |
president | All permissions | Corporate officer |
secretary | View + edit org, documents, signing | Corporate secretary |
treasurer | View + edit org, cap table, billing | Financial officer |
signatory | View org, documents, signing | Document signatory |
memberAnnual | View org, cap table, documents | Annual meeting participant |
memberRead | View org, cap table | Read-only member |
holder | View org, cap table | Equity holder (minimal) |
6. Cascading Permission Model
Source: equa-server/docs/cascading-permission.puml, equa-server/modules/api/src/site/authorization.ts
The Equa platform supports nested organization hierarchies (parent-child relationships). When a user views a parent organization, their effective permissions are calculated by combining direct and indirect permissions.
6.1 Algorithm
- Direct permissions: The union of all permissions granted by roles assigned to the user in the target organization
- Indirect permissions: The intersection of permission sets from all child organizations where the user is a member (the user must have the permission in every child org for it to be indirect)
- Effective permissions: The union of direct and indirect permissions
6.2 Key Function
getUserDirectAndIndirectPermissions() in equa-server/modules/api/src/site/authorization.ts implements this algorithm. Endpoint handlers call requirePermissions() which wraps the endpoint with a permission check before the handler executes.
7. Permission Enforcement
7.1 Backend Enforcement
Source: equa-server/modules/auth/src/authorization.ts
Every API endpoint declares its required permissions. The vineyard-lawn framework calls the permission check function before the handler:
| Function | Purpose |
|---|
requirePermissions() | Wraps endpoints with permission checks |
canWriteSite() | Checks site-level write permissions |
canReadSite() | Checks site-level read permissions |
defineRelationalAuthorizationEndpoints() | Applies permission checks to endpoint arrays |
requireLoggedInSync() | Verifies user is authenticated |
Failure returns HTTP 403 Forbidden.
7.2 Frontend Enforcement
Source: equa-web/src/shared/hocs/with-permissions.tsx, equa-web/src/app/components/routes/routes.tsx
| Mechanism | Implementation | Purpose |
|---|
| Route guards | protected: boolean on route definitions | Redirect unauthenticated users to login |
| Permission HOC | withPermissions([BuiltInPermission.viewDocuments]) | Hide/show components based on org permissions |
| Admin requirement | isSiteAdmin route requirement | Gate admin pages |
| Email verification | userIsEmailVerified(user) check | Block unverified users from protected routes |
Frontend enforcement is a UX convenience only. All security-critical checks are enforced server-side. A user bypassing frontend guards will still be blocked by the backend permission checks.
8. Data Room Access Control
Source: equa-server/modules/persistence/src/schema.ts (DataRoomsMembers entity)
Data rooms provide document storage with per-member access control, separate from the general permission system.
8.1 DataRoomsMembers Entity
| Column | Type | Purpose |
|---|
dataRoomName | string (PK) | Data room identifier |
member | UUID (PK) | Member granted access |
permission | UUID (PK) | Permission level for this member in this data room |
This is a composite primary key — the combination of data room, member, and permission is unique. A member can have multiple permission levels in the same data room.
8.2 Directory-Level Access
Data rooms use a virtual file system (DirectoryItems entity) scoped to organizations:
| Column | Type | Purpose |
|---|
organization | UUID (PK) | Organization scope |
parentPath | string (PK) | Parent directory path |
name | string (PK) | Item name |
file | UUID (nullable) | Reference to Files entity |
type | smallint | File or folder |
All file operations require the appropriate document permission (viewDocuments, editDocuments, deleteDocuments).
9. Global Roles
Source: equa-server/modules/persistence/src/schema.ts (GlobalRolesUsers entity)
For site-level administration, users can be assigned global roles that operate outside the organization context:
| Column | Type | Purpose |
|---|
user | UUID (PK) | User with global role |
role | int (PK) | GlobalRole enum value |
Global roles grant site-wide privileges (e.g., managing all organizations, accessing admin panel). The isSiteAdmin check in the frontend route guard uses this.
10. Agent Permission Proxy
Source: equa-server/modules/agent/src/security/permission-proxy.ts
The AI agent (Equanaut) operates within the same permission model as human users. The permission proxy ensures agents can only execute tools that the owning user has permission for:
| Property | Value |
|---|
| Cache TTL | 60 seconds |
| Resolution | SQL query joining permissions -> permissions_roles -> members_roles -> members |
| Methods | checkToolPermission(), checkMultiplePermissions(), getUserPermissions(), hasOrganizationAccess(), getUserRole() |
Each agent tool declares its required permissions. Before execution, the proxy verifies the user has the necessary permissions in the target organization.
10.1 Unauthenticated Chat Access (Guest Mode)
Source: equa-web/src/modules/equanaut/services/api/chat/messages.ts, equa-web/src/modules/equanaut/index.tsx, equa-web/src/modules/equanaut/services/api/config.ts
As of spec 042 (Guest Access to Equanaut Sidebar), the Equanaut regular-chat path is accessible to unauthenticated (guest) users. This path operates entirely outside the permission proxy described in Section 10.
| Property | Value |
|---|
| Trigger | <EquanautToggle /> rendered in the guest header (header.tsx) |
| Chat mode | Regular chat only (useChat); agent mode (useAgentChat) requires an authenticated user with an organization |
| Endpoint | POST /equanaut-api/api/chat |
| Authentication | None — no Authorization header, no session cookie, no credentials: 'include' |
| Backend auth check | None — the route handler processes the request without verifying identity |
| Tool execution | Not available — the guest chat path never invokes the agent tool pipeline or the permission proxy |
| Organization data | Not accessible — userContext is undefined for guests; the system prompt contains only static platform knowledge |
| Data exposure | None — responses are generated from the static Equanaut system prompt (platform feature descriptions, navigation help, terminology); no user data, organization data, or cap table information is returned |
What guest chat can do:
- Answer general questions about the Equa platform, features, and navigation
- Explain equity terminology (vesting, cap tables, option pools, etc.)
- Provide onboarding guidance for new visitors
What guest chat cannot do:
- Execute agent tools (blocked at the frontend;
useAgentChat is never selected without a user and organization)
- Access organization data, member lists, documents, or cap tables
- Perform any write operations on the platform
- View or modify any authenticated user’s information
Rate limiting:
Rate limiting for the unauthenticated chat endpoint is not yet implemented. This was scoped as tasks T004 and T005 in spec 042 and deferred to a future sprint. Until implemented, the endpoint is theoretically vulnerable to abuse (high-volume requests from unauthenticated clients). The practical risk is limited because the endpoint only returns AI-generated text with no data exposure, but cost and availability concerns apply.
This section requires security review sign-off. The unauthenticated endpoint should be re-evaluated when rate limiting (spec 042, T004/T005) is implemented.
11. Access Control Gaps
| Gap | Risk | Recommendation |
|---|
| No session concurrency limit | A compromised account could have unlimited active sessions | Implement max active sessions per user with oldest-session eviction |
| No IP-based session binding | Sessions are not tied to originating IP | Consider optional IP binding for high-security organizations |
| No permission audit log | Permission grants/revocations are not logged to the audit trail | Log all role assignment changes to EventLogs |
| Shared roles have no approval workflow | isShared=true roles are available across organizations without review | Add approval workflow for shared role creation |
| No time-limited role assignments | Roles are permanent until manually removed | Support role expiration dates for temporary access (e.g., auditor access) |
| No rate limiting on unauthenticated chat endpoint | Guest Equanaut chat (/equanaut-api/api/chat) has no request throttling; vulnerable to cost/availability abuse | Implement per-IP rate limiting (spec 042, tasks T004/T005) |
12. Regulatory References
| Standard | Requirement | Current Status |
|---|
| SOC 2 CC6.1 | Logical access security over protected information assets | Implemented — RBAC with 14 permissions, role-based enforcement |
| SOC 2 CC6.2 | Prior to issuing system credentials, registered and authorized | Implemented — email verification + optional 2FA required |
| SOC 2 CC6.3 | Access to protected information is removed when no longer required | Partial — soft delete on members, but no automated access review |
| GDPR Art. 25 | Data protection by design and by default | Implemented — permission-based access, minimal default access (guest role) |
| GDPR Art. 32 | Appropriate technical measures for data security | Implemented — RBAC, encryption (limited), session management |
13. Revision History
| Date | Version | Author | Changes |
|---|
| 2026-02-21 | 0.1 | Agent (Phase 5 Session A) | Initial draft from code analysis |
| 2026-02-22 | 0.2 | Agent (Spec 042) | Added Section 10.1 (Unauthenticated Chat Access) and rate-limiting gap; pending security review |