Skip to content

Storage modes reference

The exact values of the three Mode constants, what each one persists, and the two different functions that decide which modes a process may offer. For the reasoning behind the modes rather than their definitions, see the trust model.

What are the three Mode values?

Mode is a defined string type. Its three constants are:

Constant String value The config records The secret lives in
ModeEnvVar env the name of an environment variable the process environment
ModeKeychain keychain a service / account reference whatever Backend is registered — the OS keychain by default
ModeLiteral literal the secret itself the config file, in plaintext

The string values are what a config file persists, so they are part of your on-disk format. ModeEnvVar is the recommended default.

Is an unrecognised mode string rejected?

No. The package provides no parser and no validator for Mode. Reading "Env", "ENV" or "vault" out of a config file yields a Mode that compares unequal to all three constants and is silently carried around as-is. If your tool reads a mode back from config, compare it against the constants yourself and decide what an unknown value means — the package will not do it for you, and RefuseLiteralUnderCI will pass an unrecognised value straight through.

Which modes may this process use? (AvailableModes)

func AvailableModes() []Mode

Reports the modes the process is capable of, in a fixed order:

  1. ModeEnvVar — always present, always first.
  2. ModeKeychain — present only when KeychainAvailable() is true, i.e. a keychain-capable Backend is registered.
  3. ModeLiteralalways present.

Two things it deliberately does not do, and both surprise people:

  • It does not consult IsCI(). ModeLiteral is in the returned slice under CI=true. AvailableModes answers "what is compiled in", not "what may be offered".
  • It does not probe. ModeKeychain appears as soon as a backend is registered, even if the keychain is locked or the Secret Service is absent.

Which modes should I show the user? (ModeChoices)

func ModeChoices(ci, keychainUsable bool, envLabel, keychainLabel, literalLabel string) []ModeChoice

This is the function to build a menu from. Each returned ModeChoice is a Mode plus the Label you supplied. The filtering rules:

Mode Included when Position
ModeEnvVar always always first
ModeKeychain keychainUsable is true second, when present
ModeLiteral ci is false last, when present

Both booleans are yours to supply. The idiomatic call passes credentials.IsCI() and credentials.Probe(ctx) — the package does not call them for you, so passing true, true from a CI job will happily offer literal mode.

Labels are used verbatim and are never defaulted: pass an empty string and the menu renders an entry with no text. ModeEnvVar being first is what makes "press Enter for the default" land on the safe option in DefaultPrompter.

When is ModeKeychain genuinely usable?

Two functions answer different halves of that question, and a setup flow needs both.

KeychainAvailable() Probe(ctx) What it means Offer keychain?
false false (short-circuits) No keychain-capable backend registered — the credentials/keychain subpackage was not imported and no custom backend was registered. No
true false A backend is compiled in, but the live round-trip failed right now: locked keychain, absent Secret Service on a headless Linux host, unreachable remote store, or the call exceeded the context deadline. No
true true A backend is registered and it accepted a full Set → Get → Delete round-trip. Yes

Probe short-circuits to false without touching anything when KeychainAvailable() is false, so it is always safe to call. It can never return true when KeychainAvailable() is false.

What the modes do not come with

  • No resolution chain. This module has no "try the env var, then the keychain, then the literal" resolver. It defines the modes and the keychain round-trip; the precedence your tool applies at runtime is your tool's code. Documentation elsewhere in the toolkit that describes a *.env*.keychain*.key cascade is describing a consumer of this module, not this module.
  • No config reading or writing. Nothing here reads or writes a config file. ClearKeysExcept is the only config-adjacent helper and it writes through an interface you supply.
  • No automatic CI refusal. RefuseLiteralUnderCI is a check you call; nothing in Store, Retrieve or the prompters invokes it for you.