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
Retrievereaches 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 |