Repository navigation
cloud: store the sync token in the OS keyring on every platform - #37
Merged
Merged
Conversation
Spec §20 has always said "OS keychain where available". Only macOS satisfied it: Windows and Linux dropped the token into a plaintext ~/.ark/credentials.toml. This is the conformance fix. Resolution stays ARK_TOKEN -> keyring -> restricted file, and credentials stay keyed by sync-service host, so one `ark login` still covers every repository on that service. Three things change beyond the platform coverage: - The token no longer appears in argv. The old macOS path ran `security add-generic-password -w <token>`, which put the secret in the process table for any user on the machine to read for the duration of the call. go-keyring feeds `security -i` on stdin instead. - A keyring that fails is no longer silent. The old code guarded the success path with `err == nil` and no else, so a locked or denied keychain fell straight through to plaintext with no word to anyone. That is the bug the GitHub CLI maintainers now regret (cli/cli#10108, cli/cli#13317); we say so on stderr before touching the file. A keyring that answers and holds nothing is a miss, not a failure, and stays quiet. - Storage is legible from both ends. `ark login` already named where it wrote the token; `ark status` now names where it resolved one from, as `token_source` in --json (a new field; nothing renamed) and a `token` line for humans. Neither prints the token. Two smaller consequences of getting a real keyring: - Once the keyring holds a host's token, `ark login` removes that host's entry from the fallback file, and the file itself once it is empty. The keyring outranks it, so the copy was a secret on disk nothing would ever read again — this is the upgrade path for anyone who logged in on Windows or Linux before today. - ARK_NO_KEYRING=1 skips the keyring for machines where the file is the deliberate choice: a headless box with no Secret Service, a CI image where a prompt would hang. That path is quiet, because it was asked for. zalando/go-keyring is pure Go (wincred, godbus, x/sys), so the no-CGO invariant holds; all six release targets build with CGO_ENABLED=0. 99designs/keyring would not have — it uses cgo on macOS. internal/cli grows a TestMain that redirects HOME/USERPROFILE and opts out of the keyring, because `ark status` now resolves a credential and no test should be able to reach the developer's real one. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01LbNXVAkmXtXnSfCWRMwBc9
This was referenced Aug 27, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/v1-spec.md§20 has always said "OS keychain where available". Only macOSsatisfied it — Windows and Linux dropped the sync token into a plaintext
~/.ark/credentials.toml. This is a spec-conformance fix, not a new feature.Modelled on the GitHub CLI, which has stored tokens in the system credential
store by default since v2.26.
argvARK_TOKENResolution stays
ARK_TOKEN→ keyring → restricted file, and credentials staykeyed by sync-service host, so one
ark loginstill covers every repositoryon that service.
Four defects
Keyring was macOS-only. Now all three, via
zalando/go-keyring.The token was passed in
argv.security add-generic-password … -w <token>put the secret in the process table, readable by any user on the machine for
the duration of the call. go-keyring feeds
security -ion stdin instead.Verified against the real keychain — see below.
Keyring failure was silently swallowed.
err == nilguarded the successpath with no
else, so a locked or denied keychain fell straight through toplaintext with no word to anyone. That is the bug the GitHub CLI maintainers
now regret (cli/cli#10108,
cli/cli#13317). We now say so on
stderr before touching the file:
A keyring that answers and simply holds nothing is a miss, not a failure,
and stays quiet — otherwise the warning that matters gets trained away.
Tests could clobber real credentials on Windows — fixed in chore: support Windows development end to end #34, which
landed first. See Credit below.
Improving on
ghrather than copying itLoud fallback, as above.
ghdegrades silently; we don't.ark statusreports where the token came from, the waygh auth statusdoes.
ark loginalready named where it wrote the token; resolution is nowequally legible.
Also in here
keyring outranks the file, so a leftover entry is a secret on disk that
nothing will ever read again. This is the upgrade path for anyone who logged
in on Windows or Linux before today. Other remotes in the file are untouched;
the file itself goes when it empties.
ARK_NO_KEYRING=1skips the keyring —gh's--insecure-storage, as anenv var so it covers resolution too. For a headless box with no Secret
Service, or a CI image where a prompt would hang. That path is quiet,
because it was asked for.
internal/cligains aTestMainthat redirectsHOME/USERPROFILEandopts out of the keyring, because
ark statusnow resolves a credential and notest should be able to reach the developer's real one.
Interface changes
statusReportgainstoken_source(env|keyring|file|none,omitted when no remote is configured). Added, not renamed.
ark login --jsongainsstorage(keyring|file) as the machine-readablehalf of
stored_in.stored_inkeeps its existing values byte-for-byte —still
macOS keychainor the file path.docs/v1-spec.md§20 rewritten in this PR: explicit precedence list, theno-argv rule, the loud-fallback requirement, and
ARK_NO_KEYRING.No CGO
zalando/go-keyringis pure Go —danieljoos/wincred,godbus/dbus/v5,golang.org/x/sys.99designs/keyringwould have broken the invariant — ituses cgo on macOS.
Verification
On CI, both jobs pass: Linux (
gofmt,vet,build,go test -race ./...)and the native Windows job #34 added, which runs
gofmt,vet,buildandthe full
go test ./...onwindows-latest. That job exercises the Windows ACLcode on the fallback file for real —
creds_file_windows_test.goreads the DACLback and asserts a single current-user ACE — and runs the whole credential suite
under
USERPROFILE. Before #34 landed, CI here was Linux-only and none of thatwas reachable.
Locally on macOS (darwin/arm64):
go build ./...,go test ./... -count=1,gofmt -l .(empty),go vet ./...— all cleanCGO_ENABLED=0:linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64,windows/arm64— all six buildgo vetunderGOOS=windows(amd64 + arm64),linux,darwin— this iswhat type-checks the Windows-only ACL code and its test from a Mac
ark loginstores there withno warning and writes no file;
ark statusreportstoken_source: keyring;the stored item is
go-keyring-base64:…, proving the secret went in on stdinrather than argv; an entry written the old way (raw, via
argv) stillresolves, so existing macOS users are not logged out by this change; and a
planted plaintext token is migrated into the keychain and cleared.
Not verified against a live credential store: Windows Credential Manager and
the Linux Secret Service. The tests substitute a fake keyring — deliberately,
so the suite can never touch a developer's real credentials, and so a locked or
denied keyring can be simulated at all — which means CI proves those platforms
compile, vet, and pass every test, but not that
wincredand D-Bus actuallystore and return a secret. Only the macOS store has been driven live, by me,
against the real Keychain. @tylercdmx has the Windows machine if a live
ark login/ark statuscheck is wanted before this lands; on Linux it needsa session bus, which CI does not have.
Credit
@tylercdmx identified the Windows ACL gap on the fallback file and the
USERPROFILEtest-isolation bug (os.UserHomeDirreadsUSERPROFILEonWindows, not
HOME, sogo test ./...could read and write a developer's livecredentials). Both landed in #34, which this builds directly on top of — this PR
does not duplicate that work. #34 closes #33.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LbNXVAkmXtXnSfCWRMwBc9