Security Model
Security principles
Section titled “Security principles”- Defense in depth — multiple layers of protection
- Least privilege — RBAC with granular permissions
- Zero trust between tenants — PostgreSQL Row-Level Security
- Encryption everywhere — at rest and in transit
- Tamper-evident audit — hash-chained audit log
Tenant isolation
Section titled “Tenant isolation”Row-Level Security (RLS)
Section titled “Row-Level Security (RLS)”Every tenant-scoped table has a PostgreSQL RLS policy:
ALTER TABLE credentials ENABLE ROW LEVEL SECURITY;CREATE POLICY tenant_isolation ON credentials USING (tenant_id = current_setting('app.tenant_id')::UUID);The API middleware sets the tenant context on every request:
SET LOCAL app.tenant_id = '<tenant-uuid>';This ensures that even if application code has a bug, one tenant’s data cannot leak to another — the database enforces isolation.
Isolation layers
Section titled “Isolation layers”| Layer | Mechanism |
|---|---|
| Database | PostgreSQL RLS — queries filtered by tenant_id |
| API | Auth middleware extracts tenant from API key/session |
| RBAC | Per-route permission checks against user’s role |
| Object storage | Bucket prefix: tenants/{tenant_id}/ |
| Rate limiting | Per-tenant counters |
| Audit | tenant_id + user_id in all log entries |
Authentication
Section titled “Authentication”API keys
Section titled “API keys”bsk_live_<48 hex characters>- Storage: SHA-256 hash only — plaintext never stored
- Validation: Hash incoming key, lookup in
api_keystable - Scoping: Optional permission subset (e.g., only
credentials:issue) - Shown once: Key displayed at creation, never retrievable again
Session tokens
Section titled “Session tokens”- Generation: 32 bytes of cryptographic randomness (hex-encoded)
- Storage: SHA-256 hash in
sessionstable - TTL: Configurable (default 24 hours)
- Invalidation: Deleted on logout
Password hashing
Section titled “Password hashing”- Algorithm: Argon2id (memory-hard, GPU-resistant)
- Salt: Unique per password (SaltString from OsRng)
- Verification: Constant-time comparison
Encryption
Section titled “Encryption”At rest
Section titled “At rest”| Data | Protection |
|---|---|
| DID private keys | Stored as encrypted bytes in PostgreSQL |
| API keys | SHA-256 hashed (one-way) |
| Passwords | Argon2id hashed (one-way) |
| Credentials | JSONB in PostgreSQL (column-level encryption optional) |
| Audit logs | Hash-chained for tamper detection |
In transit
Section titled “In transit”| Channel | Protection |
|---|---|
| API requests | TLS 1.3 (enforced via HSTS) |
| Database connections | TLS (configurable) |
| Inter-service (containers) | Container network (isolated) |
Audit trail
Section titled “Audit trail”Every mutation is logged with a hash chain for tamper detection:
Entry N-1 Entry N┌──────────────────┐ ┌──────────────────┐│ id: uuid-1 │ │ id: uuid-2 ││ action: issued │ │ action: revoked ││ prev_hash: null │───────→│ prev_hash: uuid-1││ timestamp: ... │ │ timestamp: ... │└──────────────────┘ └──────────────────┘If any entry is modified or deleted, the chain breaks — detectable by
verifying prev_hash references.
Logged actions
Section titled “Logged actions”| Action | Trigger |
|---|---|
user.registered | New user registration |
credential.issued | Credential issuance |
credential.verified | Credential verification |
credential.revoked | Credential revocation |
did.created | DID creation |
did.deactivated | DID deactivation |
consent.granted | Consent recorded |
consent.revoked | Consent withdrawn |
member.invited | Team invitation |
member.removed | Team member removed |
member.role_changed | Role changed |
api_key.created | API key created |
api_key.revoked | API key revoked |
RBAC (Role-Based Access Control)
Section titled “RBAC (Role-Based Access Control)”5 built-in roles with 25 granular permissions:
| Role | Key capabilities |
|---|---|
| Owner | Everything — billing, tenant deletion, all operations |
| Admin | Team management, all operations (no tenant deletion) |
| Member | Credential operations, DIDs, compliance (no team management) |
| Viewer | Read-only access |
| API-only | Programmatic operations (no team management) |
Custom roles can combine any subset of the 25 permissions.
Security headers
Section titled “Security headers”All API responses include:
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
X-XSS-Protection | 1; mode=block |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=() |
See also
Section titled “See also”- Cloud: Authentication — API keys and sessions
- Cloud: Team & RBAC — roles and permissions
- Cloud: Errors & Limits — rate limiting
- Compliance Frameworks — regulatory requirements
- Trust & Delegation — trust model