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
export ZERO_OAUTH_STORAGE=keyring on macOS.
- Log in to one provider whose OAuth flow returns an ID token alongside the
access and refresh tokens.
- 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.
Summary
With
ZERO_OAUTH_STORAGE=keyringon macOS, a second OAuth login can fail tosave.
Store.Savereturnskeyring: set: secret too large for the macOS security tool's 4095-byte command line; use the file backend instead, and notoken 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
keyringBlobputs every provider and MCP token into one JSON blob under asingle keyring entry (
internal/oauth/store.go:472), base64-encoded. On macOSthe secret rides inside a
security -icommand line, whose parser reads at most4095 bytes, so
Keyring.Setrefuses anything larger rather than let the toolsplit 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
storeFilewith realistic OIDC credentials (a JWT access token, anID token, an opaque refresh token, scopes, expiry, account):
A single large login can exceed the cap on its own, and two ordinary ones do.
Reproduction
export ZERO_OAUTH_STORAGE=keyringon macOS.access and refresh tokens.
Expected: both logins are stored.
Actual: the second
Savefails withsecret too large, nothing is written, andfurther saves keep failing until the store is cleared or moved to file storage.
Notes
Linux is unaffected:
secret-toolreads the secret from stdin, so there is nocommand 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.