Security¶
Gremia implements a zero-trust security model with defense in depth. This page covers the security mechanisms across all three components.
Security Architecture¶
graph TB
subgraph Builder["Builder (Web)"]
JWT1[JWT Auth]
MFA[MFA / TOTP]
ZOD[Zod Validation]
end
subgraph Cloud["Cloud (Orchestrator)"]
JWT2[JWT Verification]
ENC[AES-256-GCM Encryption]
AUDIT[Audit Logging]
RLS[Row-Level Security]
RL[Rate Limiting]
end
subgraph Shell["Shell (Desktop)"]
MTLS[mTLS Client Cert]
SESS[Session Key Exchange]
CERT[Cert Rotation]
end
Builder -->|HTTPS + JWT| Cloud
Shell -->|WSS + mTLS + AES-256-GCM| Cloud
Authentication¶
JWT Tokens¶
All API requests require a valid JWT token in the Authorization: Bearer header.
- Algorithm: Configurable (default HS256)
- Issued by: Supabase Auth
- Claims:
sub(user ID),email,team_id,exp(expiration) - Verification: Server-side via
python-jose
Multi-Factor Authentication (MFA)¶
MFA is supported via TOTP (Time-Based One-Time Password):
- User enrolls via Settings > Security > Enable MFA
- Scans QR code with an authenticator app (Google Authenticator, Authy, etc.)
- On subsequent logins, a 6-digit code is required after password entry
Enterprise requirement
For Enterprise plans, MFA can be made mandatory for all team members via organization security policies.
mTLS Certificate Rotation¶
Shell-to-Cloud communication uses mutual TLS for identity verification:
sequenceDiagram
participant S as Shell
participant C as Cloud
S->>S: Generate EC P-256 key pair
S->>S: Create CSR (CN=shell-{id})
S->>C: POST /api/v1/certs/sign (CSR + JWT)
C->>C: Sign CSR with CA cert (24h validity)
C->>C: Record serial in DB (revocation tracking)
C-->>S: certificate_pem + ca_certificate_pem
S->>S: Store cert material
Note over S: Rotation loop every 5 min
S->>S: Check: expires in < 1 hour?
S->>C: Renew CSR
Parameters¶
| Parameter | Value |
|---|---|
| Key algorithm | EC P-256 (ECDSA) |
| Certificate validity | 24 hours |
| Renewal window | 1 hour before expiry |
| Check interval | 5 minutes |
| Max retry failures | 5 |
| Retry backoff | Exponential (2^n seconds) |
Revocation¶
Certificates can be revoked by serial number. The Cloud checks the revocation list during WebSocket handshake via the X-Client-Cert header (set by Nginx).
Encryption¶
At Rest — AES-256-GCM¶
Sensitive data stored in the database is encrypted with AES-256-GCM:
- Manifest content — Encrypted before storage
- Audit details — Encrypted to prevent data leakage
- Consent records — Protected per GDPR requirements
Implementation details:
- Encryption keys are versioned (v0 = previous, v1 = current)
- Key registration happens at application startup
- Re-encryption uses the latest key version
In Transit — TLS 1.3¶
| Channel | Protocol | Details |
|---|---|---|
| Builder to Cloud | HTTPS (TLS 1.3) | Standard REST API |
| Shell to Cloud | WSS (TLS 1.3) + AES-256-GCM | Tunnel messages double-encrypted |
| Cloud to Supabase | TLS 1.3 | Database connections |
Session Key Exchange¶
After WebSocket handshake, the Cloud generates an ephemeral AES-256 session key:
- Cloud generates 32 random bytes
- Key is base64-encoded and sent to Shell
- Both sides use the key for all subsequent messages
- Key is discarded on disconnect
Each message is encrypted as an EncryptedEnvelope:
Key Rotation¶
Encryption Key Rotation (90-day cycle)¶
Encryption keys should be rotated every 90 days:
- Admin generates a new key (min 32 hex characters)
- Calls
POST /api/v1/admin/rotate-keywith the new key - The system:
- Registers the new key as the active version
- Demotes the current key to "previous"
- Re-encrypts all sensitive rows in batches
- Audit entry records who rotated, when, and how many rows
# Generate a new key
NEW_KEY=$(openssl rand -hex 32)
# Rotate
curl -X POST https://api.gremia.io/api/v1/admin/rotate-key \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"new_key\": \"$NEW_KEY\"}"
Certificate Rotation (24-hour cycle)¶
Client certificates auto-rotate every 24 hours as described above. No manual intervention is required.
Rate Limiting¶
The API implements per-endpoint rate limiting:
| Endpoint | Limit | Window |
|---|---|---|
/api/v1/chat |
60 requests | 1 minute |
/api/v1/certs/sign |
10 requests | 1 minute |
/api/v1/tunnel/ws |
5 connections | 1 minute |
| All other endpoints | 120 requests | 1 minute |
Exceeding the limit returns HTTP 429 (Too Many Requests) or WebSocket close code 4029.
Audit Logging¶
Every security-relevant action is recorded in the audit log:
| Field | Description |
|---|---|
actor_id |
User who performed the action |
action |
Action type (login, key_rotation, consent_grant, etc.) |
resource_type |
What was acted upon |
detail |
Human-readable description |
result |
success, failure, or denied |
timestamp |
UTC timestamp |
Audit entries are flushed periodically in batches for performance. The flush interval is configurable.
No PII in audit logs
Audit log detail fields must never contain personally identifiable
information (email, name, IP address). Use opaque IDs only.
Input Validation¶
All external input is validated before processing:
| Layer | Validation |
|---|---|
| Builder (TypeScript) | Zod schemas for manifests, chat messages |
| Cloud (Python) | Pydantic v2 models for all request bodies |
| Shell (Rust) | serde + type system for tunnel messages |
| Database | Parameterized queries only (no string interpolation) |
Security Checklist¶
- [x] AES-256-GCM encryption at rest
- [x] TLS 1.3 for all network communication
- [x] mTLS with 24-hour certificate rotation
- [x] JWT authentication with configurable algorithms
- [x] MFA support (TOTP)
- [x] Rate limiting on all endpoints
- [x] Audit logging for all security events
- [x] Input validation at every layer
- [x] Parameterized queries (no SQL injection)
- [x] No secrets in source code (env vars only)
- [x] No PII in logs or audit details
- [x] CORS with explicit origin allowlist
- [x] Request ID tracking for traceability
- [x] GZip compression for responses