Skip to content

Conversation

@zedkipp
Copy link
Contributor

@zedkipp zedkipp commented Oct 9, 2025

This change implements optional secure storage of the CLI token using the operating system keyring for Windows, with groundwork laid for macOS in a future change. Previously, the Coder CLI stored authentication tokens in plaintext configuration files, which posed a security risk because users' tokens are stored unencrypted and can be easily accessed by other processes or users with file system access.

The keyring is opt-in to preserve compatibility with applications (like the JetBrains Toolbox plugin, VS code plugin, etc). Users can opt into keyring use with a new --use-keyring flag.

The secure storage is platform dependent. Windows Credential Manager API is used on Windows. The session token continues to be stored in plain text on macOS and Linux. macOS is omitted for now while we figure out the best path forward for compatibility with apps like Coder Desktop.

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

#19403

Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

Very cool addition.

Thought I would come by with a perspective of how this might affect the VS Code and JetBrains plugins (and left some other comments while I was here).

In VS Code, we manually write the session file, and expect the CLI to use it. @EhabY to double check me on this.

In JetBrains, we call the login command to create the session file, but I think we also sometimes try to read the session file directly or check that it exists. @fioan89 to double check me on this.

All this to say, should using the keyring be opt-in? We may break the plugins otherwise. The main scenario that comes to my mind is that there is an expired token in the keyring and the VS Code plugin is unable to log in.

@zedkipp zedkipp changed the title feat(cli): store session token in OS keyring with file fallback feat(cli): optionally store session token in OS keyring Oct 24, 2025
@EhabY
Copy link
Contributor

EhabY commented Oct 24, 2025

In VS Code, we manually write the session file, and expect the CLI to use it. @EhabY to double check me on this.

Yes, as part of the OAuth work, I'm thinking of ditching that approach since it's not really secure (writing the token in plaintext to some globally accessible file). The idea I have in mind is to use VS Code's secrets API to store the label -> session_token pairs and pass that to the CLI using an environment variable when executing a command.

@zedkipp
Copy link
Contributor Author

zedkipp commented Oct 24, 2025

Very cool addition.

Thought I would come by with a perspective of how this might affect the VS Code and JetBrains plugins (and left some other comments while I was here).

In VS Code, we manually write the session file, and expect the CLI to use it. @EhabY to double check me on this.

In JetBrains, we call the login command to create the session file, but I think we also sometimes try to read the session file directly or check that it exists. @fioan89 to double check me on this.

All this to say, should using the keyring be opt-in? We may break the plugins otherwise. The main scenario that comes to my mind is that there is an expired token in the keyring and the VS Code plugin is unable to log in.

Thanks for the input @code-asher! Your comments were helpful. After learning about how session tokens are used with Coder Desktop and VS Code/Jetbrains plugins I took this feature a different direction. Essentially keyring use is now opt-in, and there's a future path forward to share the session token across applications. I put more details in the notion link in the PR description.

@zedkipp zedkipp marked this pull request as ready for review October 24, 2025 21:48
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

Awesome, I like how the flag also removes the ambiguity of whether the keyring will be used, very nice 👌

This change implements optional secure storage of the CLI token using the operating system
keyring for Windows, with groundwork laid for macOS in a future change. Previously, the
Coder CLI stored authentication tokens in plaintext configuration files, which posed a
security risk because users' tokens are stored unencrypted and can be easily accessed by
other processes or users with file system access.

The secure storage is platform dependent. Windows Credential Manager API is used on Windows.
The session token continues to be stored in plain text on macOS and Linux. macOS is omitted
for now while we figure out the best path forward for compatibility with apps like Coder
Desktop.
@fioan89
Copy link
Collaborator

fioan89 commented Oct 28, 2025

Hey everyone, sorry for the late feedback.

In JetBrains, we call the login command to create the session file, but I think we also sometimes try to read the session file directly or check that it exists. @fioan89 to double check me on this.

Coder Toolbox does only login, it no longer stores the token in plain text.

The session token continues to be stored in plain text on macOS and Linux.
If it's worth any salt, JetBrains Toolbox also needs to securely store secrets. On macOS they developed a Java wrapper over they secure Keychain framework. On Linux there there are two wrappers over KWallet and libsecret, not sure what is the fallback order (hard to debug through the toolbox bytecode) and there is also a custom keychain like implementation where secrets are stored using an encryption key. This is the fallback if all things fail.

Essentially keyring use is now opt-in

This means apps vs code coder extension and Code toolbox will have to make a deliberate choice whether or not they should use the secure keychain. Will there be a way for these two apps to query the cli and check if keyring is available 🤔 ?

Copy link
Member

@deansheather deansheather left a comment

Choose a reason for hiding this comment

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

I'm confused what the flow is like with the support for multiple logins on separate hosts. How does the agent know which host it's supposed to use?

Windows implementation seems sound though!


// keyringProvider represents an operating system keyring. The expectation
// is these methods operate on the user/login keyring.
type keyringProvider interface {
Copy link
Member

Choose a reason for hiding this comment

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

Does the provider interface need to have the service exposed as a parameter on methods? I imagine it could just be passed to the provider when it's instantiated and then used for all Set/Get/Delete operations.

Copy link
Contributor Author

@zedkipp zedkipp Oct 30, 2025

Choose a reason for hiding this comment

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

My thinking here is I think we're better off storing the service name one level up (where we call out to a keyringProvider), rather than prescribing each keyringProvider to implement a way to store the service name. This closely mirrors what most of the OS keyring API's look like.

@zedkipp
Copy link
Contributor Author

zedkipp commented Oct 30, 2025

I'm confused what the flow is like with the support for multiple logins on separate hosts. How does the agent know which host it's supposed to use?

There's no support for logins on separate hosts right now, but the JSON storage format in the keyring should allow that in the future. I think this is consistent with how the CLI works today (it stores one token in .coderv2/session for the URL in .coderv2/url), but allows future expansion if needed.

Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

Looks good!

One minor observation from using it: it would be nice if we errored earlier when there is no keyring with coder --use-keyring login, currently it lets me go as far as pasting a token, and then it finally fails.

const (
// defaultServiceName is the service name used in the Windows Credential Manager
// for storing Coder CLI session tokens.
defaultServiceName = "coder-v2-credentials"
Copy link
Member

Choose a reason for hiding this comment

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

Should this be set in sessionstore.go I wonder? Since probably we will want to share the default name with all the keyring provider implementations. Or would each implementation have a different default name?

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 question. I'm planning to cross this bridge when I add the macOS support shortly. If they can be the same, I'll move this to sessionstore.go at that time.

@zedkipp
Copy link
Contributor Author

zedkipp commented Oct 30, 2025

Looks good!

One minor observation from using it: it would be nice if we errored earlier when there is no keyring with coder --use-keyring login, currently it lets me go as far as pasting a token, and then it finally fails.

Good observation! I made a small change to fail fast in this scenario, and updated the corresponding test.

@zedkipp zedkipp merged commit 139dab7 into main Oct 30, 2025
48 checks passed
@zedkipp zedkipp deleted the zedkipp/keyring branch October 30, 2025 23:41
@github-actions github-actions bot locked and limited conversation to collaborators Oct 30, 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.

6 participants