Docs/Security/Security Model

Security Model

How Keystore protects API credentials with AES-256-GCM encryption, SHA-256 token hashing, audit logs, kill switches, and zero-trust agent architecture.


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:

ComponentDescription
encryptedDataThe ciphertext produced by AES-256-GCM
ivA unique initialization vector generated per encryption operation
authTagThe 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.

text
1
2
3
Credential Flow:
  Dashboard  API (encrypt with AES-256-GCM)  Database (ciphertext + IV + authTag)
  Proxy request  API (decrypt in-memory)  inject into outgoing request  discard

Agent Token Hashing

Agent tokens follow the format ks_ followed by 64 hexadecimal characters (32 random bytes). When a token is created:

  1. The full token (ks_...) is returned to the user once and never stored.
  2. The token is hashed using SHA-256 before being persisted in the database.
  3. 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.

text
1
2
3
Token Lifecycle:
  Create: random 32 bytes  hex  "ks_" + hex  SHA-256 hash  store hash
  Verify: incoming token  SHA-256 hash  compare with stored hash

Zero-Trust Proxy Architecture

The proxy sits between the agent and the provider API. At no point does the agent have access to real credentials:

text
1
2
3
4
5
6
7
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 credential

Key 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:

FieldDescription
userIdThe user who performed the action
actionThe operation (e.g., credential.rotated, agent.status_changed, api_key.revoked)
resourceTypeThe type of resource affected (agent, provider_account, api_key, webhook)
resourceIdThe specific resource identifier
metadataAdditional context (e.g., new status, provider slug)
ipAddressThe IP address of the request
createdAtTimestamp 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 revoked
  • agent.deleted -- agent permanently removed
  • credential.rotated -- provider credentials replaced
  • api_key.created -- new ks_ token generated
  • api_key.revoked -- token permanently disabled
  • provider_account.removed -- provider unlinked from agent
  • webhook.created, webhook.deleted -- webhook endpoint changes
  • webhook.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.

bash
1
2
3
4
5
6
7
# 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.

bash
1
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.

bash
1
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.

text
1
2
3
4
5
6
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_SECRET for 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:

  1. Upload new credentials via the dashboard or API.
  2. Keystore encrypts the new credentials and swaps the reference atomically.
  3. The old encrypted credential record is deleted.
  4. 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.