Skip to content

What credentials does not do

A list of the things this module is regularly expected to do and does not, and of the combinations that do not work. Everything here is deliberate; none of it is a gap waiting to be filled. If you need one of these, the answer is that it belongs in your tool or in a Backend you supply.

Can it resolve a credential for me at runtime?

No. There is no resolver in this module and no precedence chain — nothing that tries the env-var reference, then the keychain, then a literal value, then a well-known fallback variable.

What the module gives you is the vocabulary (Mode), the keychain round-trip (Store / Retrieve / Delete) and the setup-flow helpers. The order in which your tool consults those sources at runtime is your tool's policy, and it is written in your tool. Descriptions elsewhere in the phpboyscout toolkit of a *.env*.keychain*.key cascade, or of a doctor check that warns about stray literal credentials, are describing go-tool-base, which consumes this module — not this module.

Can it read or write my config file?

No. Nothing here opens a file. ClearKeysExcept is the only config-adjacent helper and it writes through a KeyWriter you pass in, blanking keys to "" because Viper has no unset primitive. Choosing the config key names, writing the selected mode and persisting the result are all yours.

Can I have two backends, or a fallback chain?

No. The registry holds exactly one Backend; RegisterBackend replaces whatever was there and the last registration wins. There is no chain, no priority and no automatic fallback to the stub when a backend fails.

To get "try Vault, fall back to the OS keychain", write a Backend that wraps the others and register that one — see implement a custom backend. Note that a wrapper is process-wide too: you cannot select a backend per call.

Can I list what is stored?

No. Backend has four methods — Store, Retrieve, Delete, Available — and none of them enumerates. You can only read back a service/account pair you already know. If your tool needs to show a user what it has stored, keep the list of references in your own config; that is what the service/account reference in config is for.

There is likewise no migration helper: moving a credential from literal mode to the keychain is a read from one place and a Store to the other, written by you.

Does it rotate, expire, cache or retry?

No, none of the four.

  • Rotation and expiry are the backend's business. The module stores and reads a string; it has no notion of a credential's age or validity.
  • Caching does not happen — every Retrieve reaches the backend. Add caching in your own code if a remote backend makes that expensive, and mind that a cached secret survives a rotation.
  • Retry does not happen. A transient network failure from a remote backend surfaces as an error on the first attempt; wrap your client before registering it.
  • Access logging and policy are absent. If you need an audit trail of who read which secret when, that is a property of the store you plug in.

Does it protect the secret once it is in memory?

No. Go strings are immutable and the runtime may copy them, so reliable in-memory zeroing is not something the module can promise and it does not pretend to. A secret returned by Retrieve is an ordinary string and lives until the garbage collector decides otherwise.

The threat this module addresses is a plaintext secret sitting in a config file that travels — into a dotfiles repo, a backup, a bug report, a screen share. It does not address an attacker who is already inside your process or on your host.

Does it encrypt literal mode?

No. ModeLiteral writes plaintext into your config file, by definition. The answer to "I do not want a plaintext secret on disk" is a different mode, not an encrypted literal. Literal mode exists for backward compatibility and for throwaway or air-gapped hosts where there is no external secret store, and it is refused under CI.

Does cancelling a context stop a keychain call?

No — it abandons it. Store, Retrieve, Delete and Probe return at your deadline, but the underlying platform call keeps running on its own goroutine until it finishes, which against a locked keychain may be never. An abandoned Store can still commit after you have given up on it.

That is the honest cost of bounding a call the platform gives no way to cancel, and the reasoning is in why keychain calls are bounded. The practical consequence: a process that cancels in a tight loop against a blocked keychain accumulates goroutines.

Does it validate a mode read back from config?

No. There is no ParseMode and no validator. A Mode whose string value is not env, keychain or literal is carried around unchanged and matches none of the constants — including in RefuseLiteralUnderCI, which compares against ModeLiteral exactly. Validate what you read.

Combinations that do not work

You want Result Do this instead
ModeKeychain in a build without the credentials/keychain blank import Store/Retrieve/Delete return ErrCredentialUnsupported; Probe is false Add the blank import, or register a custom Backend
ModeLiteral under CI=true RefuseLiteralUnderCI returns a hinted error, and ModeChoices never offers it Use ModeEnvVar with the platform's secret injection
A masked secret prompt on piped stdin DefaultPrompter.InputSecret falls back to a plain line read — nothing is masked, because there is no terminal echo to suppress Accept it in tests and CI; implement Prompter if you need different behaviour
Cancelling DefaultPrompter mid-prompt The context is checked between prompts, not during a read; a blocked read stays blocked Close the input stream, or implement a Prompter over a cancellable reader
t.Parallel() in a test that calls credtest.Install Races on the process-wide registry; tests observe each other's backend Keep Install tests serial, or inject a MemoryBackend directly
A real keychain backend still registered after credtest.Install cleanup Cleanup installs a stub, not the previously registered backend Re-register the keychain backend in any later test that needs it
A keychain on a headless Linux host with no Secret Service Probe returns false and keychain is hidden Expected; see test the keychain on a headless host
Probe or a keychain call with context.Background() No deadline — a locked keychain blocks for as long as the platform takes Derive a context from KeychainOpTimeout