Skip to content

Conversation

@zedkipp
Copy link
Contributor

@zedkipp zedkipp commented Oct 31, 2025

Add support for storing the CLI session token in the OS keyring on macOS when the --use-keyring flag is provided.

#19403

https://www.notion.so/coderhq/CLI-Session-Token-in-OS-Keyring-293d579be592808b8b7fd235304e50d5

func (operatingSystemKeyring) Set(service, credential string) error {
// if the added secret has multiple lines or some non ascii,
// macOS will hex encode it on return. To avoid getting garbage, we
// encode all passwords
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanndickson what are your thoughts on keeping this base64 encoding/decoding? It's borrowed from https://github.com/zalando/go-keyring/. I don't think we explicitly need it given our token format.

Copy link
Member

@ethanndickson ethanndickson Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we have a bunch of users who populate Coder login credentials from a script. It doesn't seem impossible for users to accidentally add extra newlines or something if they populate this new format, so I think we should keep it.

type storedCredentials map[string]struct {
CoderURL string `json:"coder_url"`
APIToken string `json:"api_token"`
}
Copy link
Contributor Author

@zedkipp zedkipp Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethanndickson this is essentially what gets stored as a JSON blob (b64 encoded - see my other comment). Looks like this in my keychain. Any problems/concerns for Coder Desktop on macOS, in terms of future session token sharing?
Screenshot 2025-10-31 at 4 27 17 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, looks good to me. For posterity, I discovered the contents of Access Control are more or less irrelevant in this case, as the user will always be prompted when another app (i.e. Coder Desktop) tries to read from the keychain item.

@github-actions github-actions bot added the stale This issue is like stale bread. label Nov 10, 2025
Add support for storing the CLI session token in the OS keyring on macOS
when the --use-keyring flag is provided.
@zedkipp zedkipp force-pushed the zedkipp/keyring-macos branch from af850f4 to dc96f84 Compare November 10, 2025 16:02
@zedkipp zedkipp marked this pull request as ready for review November 10, 2025 23:59
@github-actions github-actions bot removed the stale This issue is like stale bread. label Nov 11, 2025
fixedUsername = "coder-session-token"

execPathKeychain = "/usr/bin/security"
notFoundStr = "could not be found"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it's what Zalando does, and it's cosmetic but this seems broken. Wouldn't the error string be localized?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a little digging on this and it appears the security binary isn't localized. I tried setting a different locale and it made no difference. No issues reported for the Github CLI about this (uses the zalando package under the hood).


// fixedUsername is the fixed username used for all keychain entries.
// Since our interface only uses service names, we use a constant username.
fixedUsername = "coder-session-token"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that it's not just the session token, should we maybe call this coder-login-credentials?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Updated

// encode all passwords
password := base64.StdEncoding.EncodeToString([]byte(credential))

cmd := exec.Command(execPathKeychain, "-i")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first thought is that we should be calling exec.CommandContext with inv.Context, but these processes should all die immediately so it's probably fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think nominally this should be fine. I did see the github CLI has wrapped keyring operations with a timeout, but I am inclined to hold off on adding any timeouts/wrappers https://github.com/cli/cli/blob/85d6766c621a9d67e0d353a12bb388c40ceca2a1/internal/keyring/keyring.go#L2

Comment on lines 11 to 14
const (
execPathKeychain = "/usr/bin/security"
fixedUsername = "coder-session-token"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make these tests internal, so we don't have to redefine these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is primarily driven by other other keyring tests in the sessionstore_test package which import an os specific readRawKeychainCredential function.

Comment on lines +20 to +23
type storedCredentials map[string]struct {
CoderURL string `json:"coder_url"`
APIToken string `json:"api_token"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, re: making these tests internal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter testpackage we have enabled asks for these separate test packages. The approach seems pretty reasonable for these keyring tests.

type storedCredentials map[string]struct {
CoderURL string `json:"coder_url"`
APIToken string `json:"api_token"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, looks good to me. For posterity, I discovered the contents of Access Control are more or less irrelevant in this case, as the user will always be prompted when another app (i.e. Coder Desktop) tries to read from the keychain item.

@zedkipp zedkipp merged commit 5e85663 into main Nov 12, 2025
46 checks passed
@zedkipp zedkipp deleted the zedkipp/keyring-macos branch November 12, 2025 17:48
@github-actions github-actions bot locked and limited conversation to collaborators Nov 12, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants