Trust model¶
This page explains what each storage mode protects against, so you can reason about
which to offer for a given deployment rather than memorising a table. The short
version: the package's job is to keep a secret out of your config file, and to make
the one mode that does not do that (ModeLiteral) a deliberate, CI-refused choice.
The threat the package addresses¶
The primary threat is a plaintext secret committed to, or left in, a config file. Config files get checked into git, copied between machines, attached to bug reports, and synced to backups. A long-lived API token or VCS credential sitting in one is the classic credential-leak vector.
The three modes are three answers to "where does the secret live instead?":
| Mode | Config records | Secret lives in | Leak surface |
|---|---|---|---|
ModeEnvVar |
a variable name | the process environment (shell profile, CI secret store, mounted secret) | whatever can read the environment |
ModeKeychain |
a service/account reference |
the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) | the OS keychain's own access control |
ModeLiteral |
the secret itself | the config file | anyone who can read the file |
ModeEnvVar is the default because it has the best portability-to-safety ratio: the
secret never touches the config, and the same setup works identically in local dev
and CI.
Why literal mode is refused under CI¶
A literal write in CI almost always ends badly: config files in CI are frequently
echoed to build logs, cached between jobs, or uploaded as artefacts — any of which
turns "plaintext on disk" into "plaintext in a shared, retained log". So
IsCI (true when
CI=true) gates two things:
ModeChoicesomits literal from the menu under CI, so it is never offered.RefuseLiteralUnderCIreturns a hinted error if literal is selected under CI anyway — via a config file, a script, or a future code path that bypassed the menu.
The second is the actual control; the first is a UX nicety. Both funnel through the same helper so the rule lives in exactly one place and cannot drift.
What the package does not protect against¶
Being clear about the boundaries matters more than a longer feature list:
- A compromised host. If an attacker can read your process environment or unlock your keychain, no storage mode helps — the secret is retrievable by design, because your tool must retrieve it too. The modes reduce the at-rest, in-config surface, not the live process surface.
- Secret rotation, audit, or policy. The package stores and resolves; it does not
rotate, expire, or log access. A backend that needs those (Vault, a cloud KMS) is a
custom
Backendyou supply. - Memory scrubbing. Go strings are immutable and may be copied by the runtime, so reliable in-memory zeroing is not something the package promises.
- Encrypting the config file.
ModeLiteralis plaintext by definition; the package's answer to "don't store plaintext" is "use a different mode", not "encrypt the literal".
Recommended mode by deployment¶
| Deployment | Recommended | Why |
|---|---|---|
| Local desktop | env-var reference, or OS keychain | secret out of config; keychain adds OS-level access control |
| CI/CD pipeline | env-var reference only | populated by the platform's secret injection; literal refused |
| Container / Kubernetes | env-var reference | external secret (K8s Secret, CSI) mounted as an env var |
| Throwaway / air-gapped | literal accepted | no external secret store; the plaintext-on-disk risk is accepted knowingly |
| Regulated / audited build | env-var reference only | do not import the keychain subpackage — see the keychain opt-out |
Sentinel errors and safe fall-through¶
Resolution is a fall-through chain, and two sentinels let callers distinguish the cases without ever leaking secret material:
| Error | Meaning | Caller does |
|---|---|---|
ErrCredentialUnsupported |
no keychain backend registered | fall through to the next source |
ErrCredentialNotFound |
backend healthy, entry absent | fall through, or treat as "not configured" |
Both wrap cleanly with errors.Is, and neither embeds credential material in its
message — a discipline every Backend implementation must uphold too.