Security Overview
CoreCube handles organizational knowledge including sensitive documents, credentials, and audit trails. Security is not a feature — it is a prerequisite.
Principles
- Defense in depth — No single control is trusted alone. Authentication, authorization, encryption, input validation, and output encoding each operate independently.
- Least privilege — Users, API keys, and connectors receive the minimum access required. Default permissions are restrictive.
- Fail secure — On error, deny access. A failed scope lookup denies retrieval. A user with no scope assignments accesses zero knowledge.
- Auditability — Every security-relevant action is logged: authentication, authorization decisions, credential access, and configuration changes.
- No security by obscurity — The CE codebase is open (AGPL-3.0). Security relies on cryptographic primitives and sound design.
Authentication
Admin Console — cookie-based sessions
| Control | Specification |
|---|---|
| Password hashing | bcrypt via Bun.password (cost factor 10) |
| Password policy | Minimum 12 characters. Requires uppercase, lowercase, digit, special character |
| Brute-force protection | 5 login attempts/min per IP. Account lockout after 10 consecutive failures (15 min) |
| Session storage | Server-side in SQLite sessions table. Session ID is 256-bit cryptographically random |
| Session cookie | httpOnly: true, secure: true (production), SameSite: Lax |
| Session lifetime | Configurable (default: 7 days). Sliding expiry — refreshed on each request |
| Session invalidation | All sessions invalidated on password change or role change |
| Max concurrent sessions | 10 per user. Oldest evicted when limit is exceeded |
Headless API — API key authentication
| Control | Specification |
|---|---|
| Key format | cc_ prefix + 32 bytes of cryptographic randomness (base62-encoded) |
| Key storage | SHA-256 hash stored. Raw key is never stored, shown only once at creation |
| Key lookup | Constant-time comparison (crypto.timingSafeEqual) to prevent timing attacks |
| Key expiration | Optional expires_at. Expired keys return 401 immediately |
| Key revocation | Setting is_active = 0 immediately invalidates the key |
Authorization
Role-based access control (RBAC)
| Role | Admin Console | Headless API | Admin operations |
|---|---|---|---|
| Admin | Full access | Via API key | Full |
| Editor | Connections, knowledge, search | Via API key | No users or settings |
| Viewer | Read-only | Via API key | None |
| External Client | None | Scoped via API key | None |
Two-dimensional access control
Every connection has a security label = compartment + sensitivity level:
Connection: "Confluence — HR Policies"
Compartment: hr
Sensitivity level: confidential
Security label: hr/confidential
A knowledge scope defines which compartments and maximum sensitivity a user or API key can access:
Scope: "HR Team"
Allowed compartments: [hr, all-staff]
Max sensitivity level: confidential
→ Can access: hr/public, hr/internal, hr/confidential, all-staff/*
→ Cannot access: hr/restricted, finance/*, engineering/*
Access to a chunk requires that the chunk's connection matches both:
- Connection's compartment is in the scope's allowed compartments
- Connection's sensitivity level is at or below the scope's max sensitivity level
Three-layer enforcement
A failure in any single layer does not compromise data isolation:
Layer 1 — Application-level scope resolution
- API key → scope → allowed compartments + max sensitivity → allowed connection IDs
- Evaluated on every request. Scope changes take effect immediately — no caching.
Layer 2 — PostgreSQL Row-Level Security (RLS)
- RLS policies on
documentsandevidence_chunksfilter byconnection_id - Every query session sets
corecube.allowed_connectionsbefore executing search - The database rejects unauthorized rows even if application code has a bug
- RLS cannot be bypassed by SQL injection — enforced by the PostgreSQL engine
Layer 3 — Audit verification
- Every query logs the compartments and sensitivity levels of accessed sources
- Periodic automated audit checks verify no query accessed data outside its authorized compartments
Encryption
Credentials at rest
| Property | Specification |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Key | 256-bit key from ENCRYPTION_KEY env var or DATA_DIR/encryption.key |
| IV | Unique 96-bit random IV per encryption operation |
| Authentication tag | 128-bit GCM tag |
| Encrypted fields | connections.credentials, connections.webhook_secret, llm_providers.api_key |
Data in transit
- HTTPS required in production for all endpoints
- PostgreSQL:
sslmode=requireenforced in production - TLS 1.2 minimum, TLS 1.3 preferred
HTTP security headers
All responses include:
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=() |
Content-Security-Policy | default-src 'self'; script-src 'self'; ... |
Cache-Control | no-store (API responses) |
CSRF protection
Admin routes use the double-submit cookie pattern:
- Server generates a cryptographically random CSRF token on login, stored in a
csrfcookie (SameSite: Strict,httpOnly: false) - Admin UI includes the token in
X-CSRF-Tokenheader on every state-changing request - Server validates header value against cookie value. Mismatch → 403
The headless API and MCP endpoint are not affected — they use bearer token authentication, not cookies.
Audit trail
Every security-relevant event is logged:
| Event | What is logged |
|---|---|
| Login / logout | User ID, IP, user agent |
| Failed login | Email, IP, failure reason |
| Account lockout | Email, IP, lockout duration |
| API key creation/revocation | Key ID, prefix, scope, acting admin |
| User creation/modification | Target user ID, fields changed, acting admin |
| Role change | Old role, new role, acting admin |
| Scope change | Connections added/removed, acting admin |
| Settings change | Setting key, old/new value (secrets redacted) |
| Rate limit triggered | IP or key ID, endpoint |
| Webhook rejection | Connection ID, IP, rejection reason |
Audit logs are append-only with configurable retention (default: 90 days). Navigate to Admin Console → Audit Log to search and export.
Scope audit view
Navigate to Admin Console → Scopes → Audit View to answer "Who can see what?"
| User/Key | Scope | Compartments | Max Level | Documents |
|---|---|---|---|---|
| OpenWebUI (cc_a1b2..) | all-staff | all-staff, engineering | internal | 3,204 |
| Finance Bot (cc_c3d4..) | finance-team | finance, all-staff | confidential | 892 |
| Exec Assistant (cc_e5f6..) | exec | executive, all-staff | restricted | 1,156 |