Security Model
Keystore is built on a zero-trust principle: agents never see real API credentials. Every credential is encrypted at rest, decrypted only at the proxy layer in-memory for the duration of a single request, and never exposed to agent code or logs.
Encryption at Rest
All provider credentials (API keys, connection strings, access tokens) are encrypted using AES-256-GCM before storage. Each credential record stores three components:
| Component | Description |
|---|---|
encryptedData | The ciphertext produced by AES-256-GCM |
iv | A unique initialization vector generated per encryption operation |
authTag | The GCM authentication tag that ensures integrity and authenticity |
Encryption and decryption use the Web Crypto API (crypto.subtle), which runs in a hardware-backed secure context on Cloudflare Workers. The encryption key is stored as an environment variable on the API service and is never sent to clients or agents.
Credential Flow:
Dashboard → API (encrypt with AES-256-GCM) → Database (ciphertext + IV + authTag)
Proxy request → API (decrypt in-memory) → inject into outgoing request → discardAgent Token Hashing
Agent tokens follow the format ks_ followed by 64 hexadecimal characters (32 random bytes). When a token is created:
- The full token (
ks_...) is returned to the user once and never stored. - The token is hashed using SHA-256 before being persisted in the database.
- On every proxy request, the incoming token is hashed and compared against stored hashes.
This means that even if the database is compromised, attackers cannot recover valid agent tokens from the stored hashes.
Token Lifecycle:
Create: random 32 bytes → hex → "ks_" + hex → SHA-256 hash → store hash
Verify: incoming token → SHA-256 hash → compare with stored hashZero-Trust Proxy Architecture
The proxy sits between the agent and the provider API. At no point does the agent have access to real credentials:
Agent (ks_ token) → Proxy → Validate token (SHA-256 lookup)
→ Resolve org + provider
→ Decrypt credential (AES-256-GCM, in-memory)
→ Inject into outgoing request headers
→ Forward to real provider API
→ Return response to agent
→ Discard decrypted credentialKey properties of this architecture:
- Credentials are decrypted only in the proxy process memory and discarded after the request completes.
- Agents cannot extract credentials from proxy responses. The proxy strips all credential-related headers before returning data to the agent.
- Custom headers (
x-keystore-token) are removed from outgoing requests so providers never see Keystore-specific data.
Audit Logs
Every sensitive operation is recorded in an immutable audit log. Each entry includes:
| Field | Description |
|---|---|
userId | The user who performed the action |
action | The operation (e.g., credential.rotated, agent.status_changed, api_key.revoked) |
resourceType | The type of resource affected (agent, provider_account, api_key, webhook) |
resourceId | The specific resource identifier |
metadata | Additional context (e.g., new status, provider slug) |
ipAddress | The IP address of the request |
createdAt | Timestamp of the event |
Audit log entries are scoped to an organization and cannot be modified or deleted through the API.
Logged Actions
agent.status_changed-- agent paused, resumed, or revokedagent.deleted-- agent permanently removedcredential.rotated-- provider credentials replacedapi_key.created-- newks_token generatedapi_key.revoked-- token permanently disabledprovider_account.removed-- provider unlinked from agentwebhook.created,webhook.deleted-- webhook endpoint changeswebhook.delivery.retried-- manual webhook retry triggered
Kill Switches
Agents can be stopped instantly through three mechanisms:
1. Pause an Agent
Set the agent status to paused. All proxy requests for that agent will be rejected with HTTP 403 until resumed.
# Via CLI
keystore agents pause <agent-id>
# Via API
curl -X PATCH https://api.keystore.io/v1/agents/<id>/status \
-H "Authorization: Bearer $TOKEN" \
-d '{"status": "paused"}'2. Revoke an Agent
Set the agent status to revoked. This is a permanent disable -- the agent token will never work again.
keystore agents revoke <agent-id>3. Revoke the Token
Delete the API key itself. The SHA-256 hash is removed from the database, making the token permanently unresolvable.
keystore keys revoke <key-id>All three methods take effect immediately. The proxy checks agent status on every request (with a 5-minute cache TTL for performance), but revoking the token bypasses caching entirely since the hash lookup will fail.
Budget Enforcement
Each agent can have a monthly budget cap (budgetCentsMonthly). The proxy checks the agent's current month spend (tracked in Redis) before forwarding each request. If the spend exceeds the budget, the request is rejected with HTTP 429.
Budget Check Flow:
Proxy → Redis: GET agent:{id}:spend:{YYYY-MM}
→ Compare with budgetCentsMonthly
→ If over budget: reject with 429
→ If within budget: forward request
→ After response: INCRBYFLOAT agent:{id}:spend:{YYYY-MM}Budget tracking uses Redis with automatic key expiry (32 days) so stale month data is cleaned up automatically.
IP Allowlisting
Agents can be configured with an allowedIps list. When set, the proxy will reject requests originating from IP addresses not on the list. This provides an additional layer of defense for production deployments where agent infrastructure runs on known IP ranges.
Rate Limiting
Rate limits (RPM and RPD) are enforced at the proxy using Upstash Redis sliding and fixed window algorithms. See the Rate Limits page for details.
Network Security
- The API runs on Cloudflare Workers with automatic DDoS protection and TLS termination.
- The proxy runs on Fly.io with TLS encryption for all inbound and outbound connections.
- Internal communication between the proxy and API uses a shared
INTERNAL_SECRETfor authentication. - CORS is enforced dynamically, allowing only registered custom domains, the Keystore dashboard, and localhost in development.
Credential Rotation
Credentials can be rotated without any downtime or agent configuration changes:
- Upload new credentials via the dashboard or API.
- Keystore encrypts the new credentials and swaps the reference atomically.
- The old encrypted credential record is deleted.
- The Redis cache expires within 5 minutes, after which the proxy uses the new credentials.
The agent continues using the same ks_ token throughout -- no redeployment needed.