Skip to content

feat: default coder.useKeyring to true and harden shared credential handling - #1107

Open
EhabY wants to merge 2 commits into
mainfrom
feat/keyring-default-on
Open

feat: default coder.useKeyring to true and harden shared credential handling#1107
EhabY wants to merge 2 commits into
mainfrom
feat/keyring-default-on

Conversation

@EhabY

@EhabY EhabY commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #1106.

What changes

  • coder.useKeyring defaults to true. On macOS and Windows with Coder CLI 2.29 or later, the session token goes to the OS keyring through the CLI. Linux and older CLIs keep using a file.
  • Every CLI 2.29+ invocation passes --use-keyring=<bool>, so the setting wins over CODER_USE_KEYRING.
  • The CLI store is either shared (the CLI's own directory, or one the user chose with --global-config or CODER_CONFIG_DIR) or private (a file in the extension's per-deployment directory). A private store never asks for the keyring; the CliAuth type enforces it. For a user directory the CLI decides between file and keyring (a file today, the keyring once feat(cli): allow keyring session token storage together with --global-config coder#29105 lands), and the extension never guesses which.
  • Each stored session records tokenSource (extension or cli). Sessions stored before this field existed parse as extension.
  • Logout runs coder logout against a shared store only for a token the extension created and the CLI still holds, verified with one coder login token call on 2.32+; below that the stored provenance is trusted. A session borrowed from the CLI is removed from the extension without signing the CLI out.
  • Keyring entries are read only for https URLs. The CLI keys them by host with the scheme dropped, so an http lookup would return the https deployment's token and send it in cleartext. File reads are left to the CLI, which checks the stored URL against --url from 2.32, so tokenRead moves from 2.31.0 to 2.32.0 and 2.31 users get the login prompt instead of CLI token pickup.
  • Adopting the CLI's session for a different user than the previous session asks first, reusing the link sign-in modal.
  • A failed credential store at login shows an error with Open Settings. A logout that cannot remove every credential shows Show Output.

Flags per situation

Keyring User directory CLI Store Flags the extension adds
any any < 2.29 private --global-config <ext dir> --url <url>
on any ≥ 2.29 shared --url <url> --use-keyring=true
off no ≥ 2.29 private --global-config <ext dir> --url <url> --use-keyring=false
off yes 2.29 to 2.31 private --global-config <ext dir> --url <url> --use-keyring=false
off yes ≥ 2.32 shared --url <url> --use-keyring=false

"Off" includes Linux. A private store drops the user's own --global-config item; a shared store keeps it, and CODER_CONFIG_DIR reaches the CLI through the environment.

Trade-offs

  • Signing in to the extension updates the CLI's session for that deployment, and signing out revokes it when the extension created it. This is the behavior behind the earlier rollback (chore: revert keyring behavior #828); it is now documented in the setting and gated on token provenance.
  • A locked keyring fails the connection with an error instead of silently falling back to a file. The opt-out is coder.useKeyring: false.
  • A future CLI that honors --use-keyring alongside --global-config moves user-directory setups to keyring storage. Nothing breaks, but a terminal CLI still reading the file needs one coder login.
  • The keyring_token login method folds into cli_token, and the credential telemetry category attribute becomes store (shared or private).

Testing

pnpm test, pnpm typecheck, pnpm lint, and pnpm format pass. Developed on Linux; not yet verified on a keyring-capable machine: the shared token showing up in coder login token, CODER_CONFIG_DIR pass-through, and the locked-keychain error.

🤖 Generated with Claude Code

@EhabY
EhabY force-pushed the feat/keyring-default-on branch from 78489be to 7128c90 Compare September 10, 2026 16:22
…andling

Store session tokens in the OS keyring by default on macOS and Windows,
passing --use-keyring explicitly to CLI 2.29 and later. Model the CLI
store as shared (the CLI's own directory, or a user directory on 2.31+)
or private (a file in the extension's per-deployment directory), and
treat CODER_CONFIG_DIR like a user --global-config.

Record who minted each stored token so logout runs coder logout against
a shared store only for a token the extension created and the CLI still
holds. Ask before adopting the CLI's session for a different user. Show
an error with Open Settings when the CLI cannot store the token at
login, and a Show Output button when logout cannot remove every
credential.

Closes #1106
@EhabY
EhabY force-pushed the feat/keyring-default-on branch from 7128c90 to d38afa2 Compare September 10, 2026 17:00
@EhabY
EhabY marked this pull request as ready for review September 10, 2026 17:08
@EhabY
EhabY force-pushed the feat/keyring-default-on branch from d38afa2 to 7b0efe7 Compare September 10, 2026 17:25
The CLI keys keyring entries by host without the scheme, so an http
lookup returns the https deployment's token. File reads are left to the
CLI, whose file mode checks the stored URL against --url from 2.32.
@EhabY
EhabY force-pushed the feat/keyring-default-on branch from 7b0efe7 to 5000734 Compare September 10, 2026 17:26
@matifali
matifali requested a review from code-asher September 10, 2026 17:38

@code-asher code-asher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me. I think maybe we should change whether we also log out the cli, but not blocking imo.

tbh I have not actually tested the keyring, being on Linux and all, but might find some time to run a VM later.

transport.kind === "keyring" ? "keyring" : "file",
);
await this.cliLogin(transport, url, token, configs, options);
const cli = await this.resolveCli(url, configs);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When I sign in for the first time I get an error popup "failed to store credentials: no cli binary found". Maybe we should ignore ENOENT errors? Same if I log out without having connected once. Nothing is actually wrong at these points.

Arguably if the cli is deleted after a connect, then I could see considering it an error if it disappears, but at the same time we will download and configure it again, so maybe not a big deal.

I also get two log entries about it, one a warning (probably when trying to read the token), and then another after logging in (probably the write). I feel like these should be infos at most and be a bit more descriptive (something like "tried to read token, but the cli has not been downloaded or was deleted") at least if we have not connected once before.

const token = await this.runTokenRead(transport.binPath, args, options);
if (!token) {
// Keyring entries drop the scheme, so an http lookup returns the https token.
if (cli.auth.useKeyring && !url.startsWith("https:")) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if this check should be in the cli instead? If it spits out an incorrect token given some input, that feels like a cli bug.

Then again, people probably update the VS Code extension faster so if this is a pressing issue then it might make sense to have it in both places (and I suppose we may need it in Toolbox too).

span.setProperty("store", cli.auth.store);
if (!(await this.ownsCliSession(cli, session, signal))) {
this.logger.info("Kept the CLI session for", url);
return true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should be return false I think? Because the doc comment on deleteToken says it returns true if every store was cleared, which is not the case here.

const confirmed = await this.confirmSignIn(
deployment.url,
{
title: "Sign in with the Coder CLI session?",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice nice 👍

private async cliLogin(
transport: CliTransport,
/** Reads the CLI's token via `coder login token` (CLI 2.32+). Undefined on any failure. */
public async readToken(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similar to my other comment, we get a warning in the log if the cli has no session token, wonder if we could suppress this.

Then again, maybe not worth parsing the error output of login token, that does seem kinda janky.

if (!cli) {
return { kind: "none" };
/** A shared store is ours only if the CLI still holds the token this extension created. */
private async ownsCliSession(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is neat but I feel like it is also unpredictable. When a user goes to log out, sometimes it will log out the cli, sometimes not, depending on what they did potentially a long time ago which they may have long forgotten.

Or, it seems possible that a user may log in with VS Code, then rely on that shared token for JetBrains, then one day log out of VS Code and be surprised JetBrains also lost its token, for example.

Maybe we can prompt the user about whether to also clear the token from the shared storage or keyring, warning that anything else relying on the token would also be logged out?

Comment thread src/settings/cli.ts
return (
isKeyringSupported() && configs.get<boolean>("coder.useKeyring", false)
);
return isKeyringSupported() && configs.get<boolean>("coder.useKeyring", true);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reasoning out loud about the effects of this change: for users currently logged in, they still get their token from the secret store, and when they connect we store it in the keyring and configure the cli to use the keyring, so they should not be suddenly logged out or anything like that. Does that sound right?

Or in other words, this only affects how we share tokens with the cli, it does not currently impact the plugin's own internal storage/copy of tokens.

The only potential breakage is if the keyring is misconfigured or something, then connecting could start failing despite no changes from the user. But that seems reasonable, and they can fix by opting out, we even have a notification for it.

Comment thread CHANGELOG.md

### Changed

- Store session tokens in the OS keyring by default on macOS and Windows. The

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The wording to me makes it sound like the keyring is the only place they are stored now but we do still store them in VS Code's secret storage from what I can tell (looking at persistSessionAuth).

Could just be a me issue though lol. But maybe "Additionally store session tokens in the OS keyring by default for use by the cli...does not change how the plugin stores its own tokens" or something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default coder.useKeyring to true

2 participants