Skip to content

auth: count which proxy key authenticated during a rotation window (previous-key hits per machine on /health) #439

Closed

Description

@jsboige

Problem

CLAUDISH_PROXY_KEY_PREVIOUS (be940b2, 12/09) keeps a retiring proxy key accepted during a rotation window, so seats can be repointed one at a time. Nothing records which key authenticated a request: matchesProxyKey() (handlers/shared/proxy-keys.ts) returns a boolean, fork/middleware/proxy-auth.ts logs nothing, /health exposes nothing, and captures carry no key class.

So the step that ends the window (unset _PREVIOUS + recreate) is a blind cut. Every holder of the old key that was missed gets 401 at that moment: a seat whose settings.json was not updated, a sidecar or relay .env, an sk-agent config, an external consumer. You only find them by breaking them.

Ask

When a request authenticates with the previous key (not the primary):

  1. Increment a counter keyed by the X-Claudish-Machine header, or by the User-Agent product token when that header is absent (e.g. claude-cli, AsyncOpenAI, open-code-review). Never by IP, never by any key-derived value.
  2. Expose it on /health as proxyKeyPreviousHits: { "<bucket>": <count>, ... } plus proxyKeyPreviousLastAt per bucket, counts only. Omit the field when _PREVIOUS is unset.
  3. Emit one forceConsole marker [ProxyKey] previous key used by <bucket> (n=<count>) at most once per bucket per hour, so docker logs can count it after a restart has reset the in-memory counter.

Cover both auth sites that accept the key: the proxy-auth middleware, and the native proxyKey→Anthropic swap if it also matches _PREVIOUS.

Acceptance

  • Pins: primary key → no count; previous key → count under the machine header bucket; no header → UA bucket; _PREVIOUS unset → field absent; the marker is rate-limited per bucket.
  • Mutation: remove the increment → a pin goes red.
  • No key material, no key fingerprint, no IP in the field, the marker or the logs.

Use

The window closes when every fleet bucket stays at 0 for at least 12 h. Any bucket still counting names the holder still to migrate, before the cut instead of after it.

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