Secrets from env or OS keyring only; never as flag values
byob-security.4
security
Problem: mytool --token=abc123 leaks in three ways at once. The
full command line lands in shell history (~/.bash_history,
~/.zsh_history), it's visible to anyone who runs ps or reads
/proc/<pid>/cmdline, and it shows up in CI job logs verbatim.
Tokens in committed config files (.env, config.yaml) leak a fourth
way: git history.
Idea: the CLI refuses to read secrets from flag values. Secrets come from exactly two places:
- OS keyring via
zalando/go-keyring— macOS Keychain, Windows Credential Manager, Linux libsecret. Written by<tool> auth login; read on demand. - Environment variable with a documented name
(e.g.
MYTOOL_TOKEN). Accepted so CI and server deployments can provide credentials without a keyring.
File-based secrets are accepted only via --token-file=<path>
(path, not content) — the CLI reads the file itself so the secret
doesn't round-trip through the shell. Config files store references
(token_keyring_key: mytool-token), never values.
If neither source yields a token, fail fast with an ErrHint
(byob-errors.2) pointing at <tool> auth login. Never fall back to a
prompt that reads the token over a TTY — prompts can't guarantee the
keystrokes aren't logged.
Tradeoffs: one more abstraction (~200 lines via zalando/go-keyring). Eliminates an entire class of leaks.
Design
// internal/auth/token.go
type TokenSource struct {
KeyringService string // e.g. "mytool"
EnvVar string // e.g. "MYTOOL_TOKEN"
}
var ErrNoToken = errors.New("no credentials configured")
func (s *TokenSource) Load(ctx context.Context) (string, error) {
if v := os.Getenv(s.EnvVar); v != "" {
return v, nil
}
v, err := keyring.Get(s.KeyringService, "default")
if err == nil && v != "" {
return v, nil
}
if errors.Is(err, keyring.ErrNotFound) || err == nil {
return "", errhint.With(ErrNoToken,
"run `mytool auth login` to store credentials")
}
return "", fmt.Errorf("reading keyring: %w", err)
}
// pkg/cmd/foo/foo.go — flag registration explicitly lacks a --token flag:
cmd.Flags().StringVar(&opts.TokenFile, "token-file", "",
"path to a file containing the token (alternative to keyring/env)")
// no: cmd.Flags().StringVar(&opts.Token, "token", "", ...)