Security Model
Cachecore is designed for multi-tenant production deployments. This page covers tenant isolation, authentication, key storage, and data retention.
Tenant isolation
Every cache entry is scoped to a namespace, a SHA-256 hash derived from tenant ID, policy version, system prompt, and tool definitions. Two tenants with different tenant_id values in their JWTs cannot access each other's cache entries. The namespace acts as a cryptographic partition key.
See Namespaces for the full derivation.
JWT authentication
Every gateway request requires a Cachecore JWT:
Authorization: Bearer cc_live_xxxxx.eyJ...
Cachecore verifies the HS256 signature. The JWT payload contains:
| Claim | Description |
|-------|-------------|
| tenant_id | Tenant identifier for namespace derivation |
| project_id | Project scoping |
| policy_version | Cache policy version |
| fresh_ttl_secs | Per-tenant fresh TTL override (optional) |
| stale_window_secs | Per-tenant stale window override (optional) |
| exp | JWT expiry timestamp |
Expired JWTs are rejected. SWR background refresh jobs also check exp: if the originating JWT has expired, the refresh is skipped.
OpenAI key storage
When you upload your OpenAI API key via the dashboard:
- Cachecore validates the key with OpenAI
- The key is stored in AWS Secrets Manager
- The key is never written to an application database, never logged, and never displayed again
The gateway retrieves the key from Secrets Manager at request time using the project ID from your JWT.
Admin API authentication
The /admin/* endpoints require a separate bearer token:
Authorization: Bearer <ADMIN_TOKEN>
The admin token is configured via the ADMIN_TOKEN environment variable on the gateway. It is separate from tenant JWTs. Missing or incorrect tokens return 403 Forbidden.
Rate limiting
Two layers of rate limiting protect the gateway:
| Scope | Default | Behaviour |
|-------|---------|-----------|
| Per-tenant | Configurable via JWT claims | Applied after JWT validation |
| Unauthenticated (bypass) | 100 req/min per IP | Applied to requests without Authorization header |
Exceeded limits return 429 Too Many Requests.
Data retention
| Data | Storage | Retention |
|------|---------|-----------|
| L1 cache entries | Redis | fresh_ttl_secs + stale_window_secs (default: 1 hour) |
| L2 vector entries | Redis HNSW | Same TTL as L1; deleted on invalidation |
| Observation log | Redis Stream (cc:obs) | Capped at ~10,000 entries, rolling window |
| OpenAI API keys | AWS Secrets Manager | Until manually deleted |
Cachecore does not persist LLM request or response content beyond the Redis TTL. There is no long-term storage of prompts, completions, or conversation history.
What Cachecore does not do
- Does not share cache entries between tenants
- Does not log prompt or response content
- Does not store conversation history beyond cache TTL
- Does not access your OpenAI key outside of proxied requests