Prefer reading a secret from standard input, or from a credential store, over a command line flag. Arguments are visible to other processes and often saved in shell history. An environment variable is better than a flag but still readable by other code the same user runs.
Where each option leaks
| Method | Example | Common leak points |
|---|---|---|
| Flag | tool login --token abc123 | Shell history, process listings, CI command echo, screen recordings |
| Environment variable | TOOL_TOKEN=abc123 tool run | Child processes inherit it, crash dumps, debug output that prints the environment |
| Standard input | tool login --with-token < file | Fewer places, but the source file or pipe still matters |
| Credential store | Prompt once, store in keychain | Depends on the store's access controls |
Flags are the weakest because the secret becomes part of the command text. Many systems let any user on the machine list running commands with their arguments, and shells commonly keep a history file.
What to build
- Do not offer a token flag, or mark it deprecated. If users insist, accept it but print a warning that shell history will keep it.
- Read from stdin with an explicit option. A flag like
--with-tokenthat reads one line from standard input makes the safe path obvious. - Support a prompt on a terminal. Turn off echo while the user types.
- Support an environment variable for CI only. Name it in the docs as the unattended option and warn about child processes.
- Offer a file path option with a permission check. If the file is readable by others, refuse or warn.
- Support a credential helper so interactive users never type the secret again. See the post on where to store a CLI token.
Scrub what you print
Even when input is safe, output can leak. Debug logging that dumps request headers prints the Authorization header. Error messages that echo the command line print flags. Write a small redaction function and run all log lines through it. Test it with a fake token and grep the output.
On CI, remember that masking is a backstop only. GitHub's documentation says to register a value with add-mask before it is printed, and that masking depends on matching the exact value. If your CLI transforms a secret before printing, such as encoding or splitting it, the mask may not match. Do not rely on masking to clean up output you control.
A note on documentation
Examples are copied. If your README shows --token with a realistic value, users will paste that form into scripts. Show stdin or environment variable examples, with obvious placeholders.
Quick check
Is an environment variable a safe place for a production secret?
It is better than a flag, but anything that runs as the same user or inherits the environment can read it. Prefer short-lived tokens and a credential store where you can.
Will CI log masking catch a leaked token?
Only if the exact value was registered first. Treat masking as a safety net and keep secrets out of output in the first place.
For a view of the third-party code your CLI includes, Safeguard's free scan at gold.safeguard.sh/scan produces a dependency inventory.
Sources
Last reviewed: 19 October 2026.