Architecture

A deep dive into how Keychains.dev works under the hood — identity, permissions, proxy validation, and the security model.

Four layers of trust

Access flows through four layers. Each builds on the previous — compromise at one layer doesn't break the others.

  1. Machine Identity
    Ed25519 SSH keypairs + rolling challenge-response + hardware fingerprinting. Proves which machine is acting. Challenge nonces are single-use — replayed signatures fail.

  2. Permission Request
    Stateful, user-approved authorization stored in Redis. Scoped or wildcard mode. Carries version tracking — any mutation bumps the version, invalidating in-flight tokens.

  3. Delegate Identity
    Immutable bootstrap identity with its own Ed25519 keypair and proof-of-possession. Allows remote systems to act with a reduced scope ceiling derived from the parent.

  4. Permission Token
    Short-lived JWT (5-15 min) for execution. Contains request ID and version — no scopes. Scopes are resolved from Redis at proxy time, ensuring revocations take effect instantly.

Proxy validation algorithm

Every proxied request goes through this pipeline. All checks are stateful — the proxy is the final authority.

  1. Verify JWT signature
  2. Fetch Permission Request from Redis by requestId
  3. Validate state: status === active, version matches, not expired
  4. If delegateId: validate delegate chain + parent status
  5. Validate machine challenge state
  6. Resolve approvedScopes from Redis (not from token)
  7. Check required scopes vs approved:
    • All present: proceed
    • Wildcard + missing: 403 + approval_url
    • Scoped + missing: 403 no recourse
    • Delegate ceiling exceeded: 403 scope_refused
  8. Inject credentials server-side (OAuth tokens / API keys / basic auth)
  9. Execute upstream call
  10. Write audit log (non-blocking via after())

Wildcard permissions

Wildcard permissions start with zero scopes and grow as the user approves each one. Every scope still requires explicit consent.

  1. Agent calls proxy requiring github::repo
  2. Proxy returns 403 with approval URL
  3. User visits URL, approves github::repo
  4. Scope added to approvedScopes, version bumps
  5. Agent retries — proxy re-reads Redis — success

Core data model

Entity Storage Key fields
Machine Redis id, userId, publicKey, fingerprint, challengeNonce
PermissionRequest Redis id, machineId, mode, status, requestedScopes, approvedScopes, version
Delegate Redis id, parentPermissionId, mode, publicKey, approvedScopes, ttl
PermissionToken JWT (stateless) requestId, machineId, delegateId, version, exp
AuditLog MongoDB userId, machineId, permissionId, provider, method, endpoint, status

Security properties

  • No secrets in tokens
    Permission tokens contain IDs and versions — never scopes, never credentials. Scopes are resolved server-side.

  • Version pinning
    Every mutation bumps the permission version. Tokens with stale versions are rejected. Revocation takes effect in under 1 second.

  • Cascade revocation
    Revoking a parent automatically revokes all delegates. No orphaned access, no cleanup needed.

  • Rolling challenges
    Each machine auth uses a single-use nonce. Concurrent auth from two locations triggers automatic revalidation.

  • Scope immutability (scoped)
    Once approved, scoped permissions can never expand. The set is frozen — auditable and predictable.

  • Audit everything
    Every proxied call is logged in MongoDB with full context. Dashboard provides real-time visibility.

Credential injection

The proxy resolves and injects credentials based on the target API's requirements. Supported methods:

  • OAuth 2.0 / 2.1
    Access tokens refreshed automatically. Token rotation handled server-side.

  • API Keys
    Injected as headers or query params per provider spec.

  • Basic Auth
    Username/password pairs injected as Authorization headers.

Key & Token Security
API Reference