Skip to content

feat: allow selecting the Linux credential store explicitly - #144

Merged
Brooooooklyn merged 1 commit into
mainfrom
feat/linux-store-selection
Sep 13, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
feat/linux-store-selection

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Closes #142 (option 1 of the request).

Problem

On Linux, Entry/AsyncEntry pick their credential store through an automatic fallback: the D-Bus Secret Service is tried first, and any error silently falls back to the kernel keyutils keyring. The two stores give different guarantees — keyutils is in-memory only and credentials vanish on reboot — and the public API offers no way to require one store or even learn which one won. An app that needs durable storage can report a successful login and then "forget" the credential after a reboot, with no visible cause.

What this PR adds

An options bag on the Entry/AsyncEntry constructors and withTarget:

new Entry('svc', 'user')                                          // unchanged: auto-fallback
new Entry('svc', 'user', { linux: { store: 'secret-service' } })  // require Secret Service, throw if unavailable
new Entry('svc', 'user', { linux: { store: 'keyutils' } })        // require the kernel keyring
Entry.withTarget('target', 'svc', 'user', { linux: { store: 'keyutils' } })
  • Fail loudly, never substitute: when a store is pinned, only that store is constructed and construction errors propagate — no silent fallback.
  • Unchanged default: omitting the option keeps today's Secret Service → keyutils auto-fallback.
  • Cross-platform type surface: the option is accepted on every platform and only meaningful on Linux (the generated d.ts is one cross-platform file).
  • No global state: pinned entries are built directly via CredentialStoreApi::build, leaving the process-global default store untouched.
  • withTarget + keyutils: the target maps to the kernel key description modifier, so distinct targets keep distinct credentials (added a Linux regression test for this).

Also in this PR

  • De-duplicated the per-file store setup logic from entry.rs / async_entry.rs into src/entry_builder.rs — no behavior change on non-Linux platforms; the Windows withTarget placeholder hack is preserved.
  • Regenerated the napi-rs JS loader and type definitions with the current @napi-rs/cli (template refresh), and set napi.constEnum: false so LinuxStore emits as the union 'secret-service' | 'keyutils' — plain string literals typecheck.
  • README: new "Linux backend selection" section, including the keyutils non-persistence caveat.

Verification

  • cargo fmt --check, cargo clippy --all-targets, cargo test — clean.
  • ava 19/19 on macOS: option accepted-and-ignored on non-Linux, unknown store values rejected at the JS boundary; tsc --noEmit over the test suite confirms the literal syntax typechecks and invalid values don't.
  • New Linux-gated tests (execute in CI): pinned secret-service/keyutils round-trips for Entry and AsyncEntry, and keyutils target isolation. The pinned paths could not be exercised on a real Linux machine during development — they are compiler-checked code paths gated behind cfg(target_os = "linux") plus the existing keyring-core store APIs, so CI is the first real run.

Out of scope (pre-existing, noted for follow-up)

  • findCredentials/findCredentialsAsync always talk to the Secret Service directly and never see the keyutils fallback.
  • The auto-fallback path passes target modifiers to whichever store won, which the keyutils store rejects — unchanged here.
  • The regenerated @napi-rs/cli 3.1.5 loader template no longer returns the NAPI_RS_NATIVE_LIBRARY_PATH binding before falling through to the WASI fallback; fixing that belongs in the CLI/template, not this feature.

Note

Medium Risk
Changes how Linux credentials are stored and when constructors throw; mis-pinned stores or expecting keyutils durability could break apps, though defaults are unchanged and the option is ignored off Linux.

Overview
Adds an optional EntryOptions bag on Entry/AsyncEntry constructors and withTarget, so Linux callers can pin credentials to secret-service or keyutils instead of relying on the default Secret Service → keyutils auto-fallback. When a store is pinned, construction fails if that backend is unavailable (no silent fallback); omitting the option keeps existing behavior. Pinned entries are built via CredentialStoreApi::build without touching the process-global default store.

Rust entry creation is centralized in entry_builder.rs (store setup and Windows withTarget username preservation moved out of entry.rs / async_entry.rs). For keyutils + withTarget, the target maps to the kernel key description modifier so targets stay isolated.

Ships updated TypeScript types (LinuxStore as string literals via napi.constEnum: false), README guidance on Linux backends and keyutils non-persistence, AVA tests for pinned stores / cross-platform ignore / invalid store values, and a regenerated napi-rs index.js loader template.

Reviewed by Cursor Bugbot for commit 69063f7. Bugbot is set up for automated code reviews on this repo. Configure here.

Add an options bag to Entry/AsyncEntry constructors and withTarget:

  new Entry(svc, user, { linux: { store: 'secret-service' } })
  new Entry(svc, user, { linux: { store: 'keyutils' } })

When a store is pinned, only that store is constructed and any failure
propagates, so requiring an unavailable store throws instead of silently
falling back to the kernel keyring. Without the option the existing
auto-fallback selection is unchanged. The option is accepted on every
platform but only meaningful on Linux; for the keyutils store a
withTarget target maps to the kernel key description.

Also regenerate the napi-js binding loader and type definitions with the
current @napi-rs/cli and emit string enums as union types
(napi.constEnum=false) so plain string literals typecheck.
@cursor

cursor Bot commented Sep 13, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8d29fecf-1e0a-42e3-a8bd-067b2043361d)

@Brooooooklyn

Copy link
Copy Markdown
Owner Author

@codex review

@Brooooooklyn
Brooooooklyn merged commit fbe064e into main Sep 13, 2026
21 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/linux-store-selection branch September 13, 2026 16:10
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.

Linux: allow selecting the credential backend explicitly instead of silently falling back to keyutils

1 participant