Skip to content

Key-type permission ceilings: existing keys freeze at creation and silently 403 new scopes #28

Description

@rameerez

Found in production (LicenseSeat, 2026-08-19): keys store their scopes array at creation, so when a host expands config.key_types[:sometype][:permissions], every existing key of that type keeps the old list and starts 403ing endpoints that new keys can call — silently, from the client's perspective, because the key itself is valid. Eight customer keys had been refusing heartbeats and machine-file calls for weeks; the client SDKs were correct the whole time.

For key types that hosts don't let users scope-curate (auto-created, limit: 1 — the "publishable key" pattern), the ceiling IS the contract, and freezing a snapshot of it is a footgun. Options, roughly in order of preference:

  1. Ceiling-authoritative authorization for key types that opt in (e.g. scopes: :ceiling in the key_type config): authenticate_api_key!(scope:) checks the live permission list instead of the stored array. Stored scopes stay as metadata. Zero drift, ever.
  2. A gem-shipped reconciler (ApiKeys.reconcile_scopes! + docs telling hosts to run it after ceiling changes / on boot / on a schedule) that add-only merges the ceiling into non-revoked keys of that type.
  3. At minimum, a loud README warning on key_types: "expanding permissions does not affect existing keys — backfill them."

The host-side workaround shipped in LicenseSeat is (2) as a daily job plus a health check that counts stale keys; happy to upstream either shape.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RnnxM5g6WNW4vvYYFBM1tn

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions