Skip to content

fix(store): reject empty secrets on Linux, Windows and posixage - #694

Merged
Benehiko merged 4 commits into
mainfrom
fix/keychain-empty-secret-linux-windows
Oct 14, 2026
Merged

Benehiko merged 4 commits into
mainfrom
fix/keychain-empty-secret-linux-windows

Conversation

@Benehiko

@Benehiko Benehiko commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #687, which added ErrEmptySecret to the macOS keychain. This makes Save and Upsert reject an empty secret value in every store: the Linux secret service, the Windows credential manager, and posixage.

Stacked on #687. Until #687 merges, this PR's diff also shows its commits; only the commits after 6057f1a are new here.

  • Error moved to store: ErrEmptySecret now lives in the store package as store.ErrEmptySecret, so posixage can return it without importing keychain. keychain.ErrEmptySecret from fix(keychain): update existing items in place on Upsert #687 is removed rather than re-exported; it has not been released.
  • Linux: Save (which Upsert calls) marshals the secret first and returns ErrEmptySecret before connecting to the secret service, so an empty value never opens a D-Bus connection or triggers an unlock prompt.
  • Windows: encodeSecret returns ErrEmptySecret for an empty value, so Save and Upsert refuse it before writing anything or deleting old chunk credentials.
  • posixage: Save (which Upsert calls) returns ErrEmptySecret before taking the lock or prompting for encryption keys.
  • Docs: the Store.Save / Store.Upsert interface docs now say an empty value is rejected with ErrEmptySecret.

This is a behaviour change: callers that saved empty values on Linux, Windows, or posixage now get an error. The test doubles (store/mocks and plugins/pass/teststore) are unchanged.

Testing

  • New cross-platform subtest in keychain_test.go: Save and Upsert with a nil and an empty value both return ErrEmptySecret, and nothing is stored.
  • New Linux test TestKeychainSaveRejectsEmptySecret against the fake secret service: no connection is opened and nothing is created or updated.
  • New posixage test TestSaveRejectsEmptySecret: Save and Upsert with a nil and an empty value return ErrEmptySecret, the encryption callback is never called, and nothing is written.
  • Ran go test ./... in store/ on macOS, and go vet ./... cross-compiled for Linux and Windows. The Linux and Windows keychain tests themselves only run in CI.

AI disclosure: the code, tests and this description were written by Claude (Anthropic) through the alki coding agent, at my direction.

Save and Upsert on the Linux secret service and the Windows credential manager now return ErrEmptySecret for an empty value, matching macOS. On Linux the check runs before connecting to the secret service, so an empty value never triggers an unlock prompt.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
Move ErrEmptySecret to the store package so every store can return it, keeping keychain.ErrEmptySecret as an alias. posixage Save and Upsert now return it for an empty value before taking the lock or prompting for encryption keys.

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
@Benehiko Benehiko changed the title fix(keychain): reject empty secrets on Linux and Windows fix(store): reject empty secrets on Linux, Windows and posixage Oct 13, 2026

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Assessment: 🟢 APPROVE

Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
Comment thread store/errors.go

// ErrEmptySecret is returned by Save and Upsert when the secret's value is
// empty.
var ErrEmptySecret = errors.New("secret value must not be empty")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ErrEmptySecret is defined in store with its own errors.New, unlike ErrCredentialNotFound above, which aliases secrets.ErrNotFound from x/secrets.

Not-found is shared because it has to be recognised outside the store: a plugin passes the store's not-found on, the resolver API maps secrets.ErrNotFound to CodeNotFound, and the client maps it back. plugin.ErrNotFound and client.ErrSecretNotFound are the same value for the same reason.

An empty secret is a store-level validation error: it is returned by Save/Upsert before anything is written, and nothing beyond the store needs to recognise it. If plugins or the engine API should ever report it specially, it can move to x/secrets and be aliased here the same way.

Written by Claude (Anthropic) through the alki coding agent, at the direction of @Benehiko.

@Benehiko
Benehiko requested a review from joe0BAB October 13, 2026 15:07
@Benehiko
Benehiko enabled auto-merge October 13, 2026 15:07
@Benehiko
Benehiko merged commit 85da73d into main Oct 14, 2026
31 checks passed
@Benehiko
Benehiko deleted the fix/keychain-empty-secret-linux-windows branch October 14, 2026 07:59
joe0BAB added a commit that referenced this pull request Oct 14, 2026
The store now returns store.ErrEmptySecret from Save and Upsert on every
platform (#694). With --force, set asked the engine to authorize the
overwrite before the store could reject the value, so the user approved
an overwrite that then failed. Check the value right after reading it
instead, before any engine round-trip.

Move the value switch into a readSecret helper to keep SetCommand under
the gocyclo limit.

Signed-off-by: Johannes Großmann <grossmann.johannes@t-online.de>
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.

3 participants