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 12) |
| Password policy | Minimum 8 characters. No complexity (uppercase / digit / symbol) requirement is enforced |
| Brute-force protection | The login endpoint is rate-limited to 10 requests/min per IP. There is no account lockout — failed attempts are not counted or used to disable an account |
| Session storage | Server-side PostgreSQL sessions table. Session ID is 256-bit cryptographically random |
| Session cookie | httpOnly: true, secure: true (when served over HTTPS), SameSite: Lax |
| Session lifetime | 30-day absolute lifetime (SESSION_MAX_LIFETIME_HOURS, default 720h) plus a 60-minute idle timeout (SESSION_IDLE_TIMEOUT_MINUTES). Activity extends the idle window, not the absolute lifetime |
| Session invalidation | Changing a password revokes the user's other sessions. A role change revokes sessions only when the role is downgraded to no_access |
Headless API — API key authentication
| Control | Specification |
|---|---|
| Key format | cc_ prefix + 32 random bytes (hex-encoded) |
| Key storage | SHA-256 hash stored. Raw key is never stored, shown only once at creation |
| Key lookup | The presented key is SHA-256 hashed and matched against the indexed key_hash column |
| 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 logging
- Every query writes a retrieval trace recording the compartments and sensitivity levels of the sources it touched
- These traces land in the append-only audit log so access can be reviewed after the fact. There is no scheduled job that proactively scans for cross-compartment access — review is performed on demand against the audit log
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, 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
The CoreCube application sets only CORS headers. It does not emit HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, or Permissions-Policy. Configure these at the reverse proxy that terminates TLS in front of CoreCube. Recommended values:
| 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'; ... |
See Hardening for reverse-proxy configuration.
CSRF protection
CoreCube does not implement a CSRF token. The admin session cookie is set with SameSite: Lax, which prevents other origins from sending it on cross-site state-changing requests, and the API enforces a CORS policy that rejects disallowed origins. Together these are the CSRF mitigation for cookie-authenticated admin routes.
The headless API and MCP endpoint are unaffected — they authenticate with bearer tokens, not cookies, so they are not exposed to CSRF.
Audit trail
Security-relevant events are recorded in the append-only audit log:
| Event | What is logged |
|---|---|
| Login / logout | User ID, IP |
| Failed login | Attempted email, IP, reason (user ID if known) |
| Login rejected (no-access account) | User ID, IP, reason |
| 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 rejections are written to the application log, not the audit log.
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 |