Skip to content

oauth: keyring storage cannot save a second login on macOS #937

Description

@euxaristia

Summary

With ZERO_OAUTH_STORAGE=keyring on macOS, a second OAuth login can fail to
save. Store.Save returns keyring: set: secret too large for the macOS security tool's 4095-byte command line; use the file backend instead, and no
token is persisted. Once the store is over the line every subsequent write
fails too, not just the login that crossed it, because the whole blob is
rewritten on each save.

Cause

keyringBlob puts every provider and MCP token into one JSON blob under a
single keyring entry (internal/oauth/store.go:472), base64-encoded. On macOS
the secret rides inside a security -i command line, whose parser reads at most
4095 bytes, so Keyring.Set refuses anything larger rather than let the tool
split the line into two garbage commands (internal/keyring/keyring.go:87).

The blob has no size bound of its own. Nothing caps how many logins it holds, so
it grows past a limit that does not grow with it.

Measured budget

Under the anchor account the command overhead is 56 bytes, leaving 4039 bytes of
base64, which is 3027 bytes of JSON for all logins combined.

Encoding a storeFile with realistic OIDC credentials (a JWT access token, an
ID token, an opaque refresh token, scopes, expiry, account):

credential (access / id / refresh) logins that fit
600 / 0 / 64 bytes 3
1200 / 900 / 300 bytes 1
1800 / 1200 / 400 bytes 0

A single large login can exceed the cap on its own, and two ordinary ones do.

Reproduction

  1. export ZERO_OAUTH_STORAGE=keyring on macOS.
  2. Log in to one provider whose OAuth flow returns an ID token alongside the
    access and refresh tokens.
  3. Log in to a second provider.

Expected: both logins are stored.
Actual: the second Save fails with secret too large, nothing is written, and
further saves keep failing until the store is cleared or moved to file storage.

Notes

Linux is unaffected: secret-tool reads the secret from stdin, so there is no
command line to fill.

The fix has to stay compatible with entries written by existing builds, and
must not leave token material behind in the keychain when the layout changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    issue-approvedReviewed and approved by the core team; community PRs may implement this issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions