API keys and tokens passed as command-line flags appear in shell history (~/.bash_history, ~/.zsh_history), in ps aux output visible to other users on the same machine, and in CI log output if the command is echoed before execution. CWE-214 (Sensitive Information in Command Line Arguments) documents this exact exposure vector. A contractor running mytool --api-key sk-live-abc123 deploy on a shared build server has just written that key into a file that persists after logout. Environment variables avoid all three exposure paths: they are not logged by the shell history, not visible in ps argument lists, and not echoed in CI unless explicitly printed.
Low because the exposure requires access to the user's shell history or process list — not network-accessible — but live API key leakage via `ps` on shared servers is a real, documented attack.
Accept sensitive values via environment variables and document the supported names in --help:
program
.option('--api-key <key>', 'API key (or set MYTOOL_API_KEY env var)')
.action((opts) => {
const apiKey = opts.apiKey ?? process.env.MYTOOL_API_KEY
if (!apiKey) {
console.error('Error: API key required. Set MYTOOL_API_KEY or pass --api-key')
process.exit(2)
}
})
@cli.command()
@click.option('--api-key', envvar='MYTOOL_API_KEY',
help='API key (or set MYTOOL_API_KEY env var)')
def deploy(api_key):
if not api_key:
raise click.UsageError('API key required: set MYTOOL_API_KEY or pass --api-key')
Follow the TOOLNAME_VARNAME convention in SCREAMING_SNAKE_CASE. Never log the value of the key — log only whether it was found.
ID: cli-quality.config-distribution.env-vars
Severity: low
What to look for: Count all environment variable references in the CLI code. For each, check if the CLI accepts sensitive values (API keys, tokens, passwords) and how they're configured. Look for process.env.MYTOOL_API_KEY, os.environ.get('MYTOOL_TOKEN'), os.Getenv("MYTOOL_KEY"). Verify that sensitive values can be set via environment variables rather than only command-line flags (which appear in shell history and ps output). Check that env var names follow the convention: TOOLNAME_VARNAME in SCREAMING_SNAKE_CASE.
Pass criteria: Sensitive configuration (API keys, tokens, credentials) can be provided via environment variables. Env var names follow the TOOLNAME_VARNAME convention. The precedence order is documented: flags > env vars > config file > defaults — 100% of environment variables must be documented in --help or README. Report: "X environment variables referenced, all Y documented."
Fail criteria: API keys or tokens must be passed as command-line flags (visible in shell history), or env vars are used but with non-standard names, or no documentation of env var support.
Skip (N/A) when: The CLI does not accept any sensitive configuration — no API keys, tokens, or credentials. All checks skip when no CLI entry point is detected.
Detail on fail: "API key must be passed as --api-key flag — no environment variable support. The key will appear in shell history and ps output" or "Env var support exists but uses non-standard name 'key' instead of 'MYTOOL_API_KEY'"
Remediation: Environment variables keep secrets out of shell history:
// Node.js — env var with flag override
program
.option('--api-key <key>', 'API key (or set MYTOOL_API_KEY env var)')
.action((opts) => {
const apiKey = opts.apiKey || process.env.MYTOOL_API_KEY
if (!apiKey) {
console.error('Error: API key required. Set MYTOOL_API_KEY or pass --api-key')
process.exit(2)
}
})
Document the supported env vars in --help output or a mytool env subcommand.