Notes

Don't Let Your Terminal History Become Your "Password Book": A Practical Guide to Avoiding Recording Sensitive Commands

2026-05-17 #Bash#Zsh#Powershell#Shell#Developer Security

Introduction: Under Supply Chain Attacks, Every Command Line Could Be an Entry Point

Since 2025, supply chain attacks targeting npm, PyPI, GitHub Actions, and Docker Hub have continued to rise: poisoned dependencies, hijacked CI Tokens, phished maintainer accounts… Attackers increasingly prefer the low-cost path of "bypassing your code and grabbing your credentials directly."

And one of the credential repositories that developers most easily overlook is the one they use every day but almost never audit: Shell history files.

  • macOS / Linux: ~/.zsh_history, ~/.bash_history
  • Windows PowerShell: %APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt

These files save all the commands you've entered in plain text—including accidentally pasted API Keys, URLs with passwords, subscription links, and database connection strings. Once your computer is read by a malicious npm package, a disguised VSCode extension, or a compromised CLI tool, attackers can directly pack them up and take them away without needing privilege escalation.

However, there are times when you indeed need to enter secrets in commands, such as export API_KEY=. This article offers two practical steps you can implement immediately:

  1. When writing commands: Prevent sensitive commands from entering history in the first place.
  2. After writing: Periodically scan your history files to check if you have leaked something.

1. macOS / Linux: Add a Space Before the Command

Both bash and zsh support HISTCONTROL / HIST_IGNORE_SPACE — once enabled, commands starting with a space will not be written to the history file, nor will they appear in the history search.

Many distributions or shell frameworks may already have this feature enabled. Verify it:

1
2
3
4
5
# bash - expect to contain ignorespace or ignoreboth
echo "$HISTCONTROL"

# zsh - expect to see histignorespace
setopt | grep histignorespace

If not, add it to your ~/.bashrc or ~/.zshrc:

1
2
3
4
5
# bash
# ignorespace + ignoredups
export HISTCONTROL=ignoreboth
# zsh
setopt HIST_IGNORE_SPACE

Afterward, simply add a space in front of sensitive commands:

1
2
 export OPENAI_API_KEY="sk-..."
curl -H "Authorization: Bearer $TOKEN" https://api.example.com

Then exit the current terminal (which writes commands to the history file), reopen it, and verify using the following commands:

1
tail -n 5 ~/.zsh_history
1
tail -n 5 ~/.bash_history

2. Windows PowerShell: Implement It Yourself with AddToHistoryHandler

PowerShell does not have a built-in switch for "ignore commands with leading space", but PSReadLine provides AddToHistoryHandler, allowing you to customize which commands go into history.

Configuration Steps

Open (or create) your PowerShell profile:

1
notepad $PROFILE

Write:

1
2
3
4
5
6
7
8
9
Set-PSReadLineOption -AddToHistoryHandler {
param([string]$Line)

if ($Line.StartsWith(' ')) {
return $false
}

return $true
}

Save and restart PowerShell. Afterward, commands entered with a leading space like this will not be written to the current session history, nor will they be saved to ConsoleHost_history.txt:

1
2
# ...
curl https://example.com/secret-url

The PSReadLine documentation explicitly states that returning $false from the handler is equivalent to SkipAdding, and the command will not be retained in either the current session or the history file.

This only affects new commands entered afterward; content already written to ConsoleHost_history.txt will not be cleared.


3. Review and Audit: What's Already in Your History?

Adding the switch only prevents future issues. The next step is to audit existing files—many people are shocked the first time they scan ~/.zsh_history.

The following commands are for zsh. Bash users can simply replace the path with ~/.bash_history.

1. Check file size first to get an idea

1
ls -lh ~/.zsh_history

2. Check for common key/password keywords

1
grep -nEi 'api[_-]?key|apikey|secret|token|password|passwd|pwd|authorization|bearer|credential|client_secret|access[_-]?key|private[_-]?key|OPENAI_API_KEY|ANTHROPIC_API_KEY|GITHUB_TOKEN|GH_TOKEN|NPM_TOKEN|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|DATABASE_URL|MONGODB_URI|REDIS_URL' ~/.zsh_history

3. Check for typical token formats

1
grep -nE 'sk-[A-Za-z0-9_-]{20,}|github_pat_[A-Za-z0-9_]{20,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}' ~/.zsh_history

Coverage: OpenAI / Anthropic style sk-..., GitHub Fine-grained PAT, Classic PAT, AWS Access Key, Google API Key.

4. Check for URLs containing usernames and passwords

1
grep -nEi 'https?://[^[:space:]/@:]+:[^[:space:]/@]+@' ~/.zsh_history

Format like https://user:password@host/... — very common in git clone, curl, and psql.

5. Check for environment variable assignments

1
grep -nEi '(\$?[A-Z0-9_]*(KEY|TOKEN|SECRET|PASSWORD|PASS|PWD|URL|URI|AUTH|CREDENTIAL)[A-Z0-9_]*=|export [A-Z0-9_]+=)' ~/.zsh_history

Capture one-line assignments like export FOO_TOKEN=... or API_KEY=....

6. Check for curl / request header leaks

1
grep -nEi '(curl|wget|httpie|http) .*(Authorization|Bearer|X-API-Key|api-key|token)' ~/.zsh_history

7. Check cloud services / deployment tools login and credentials commands

1
grep -nEi '\b(aws|az|gcloud|vercel|netlify|supabase|firebase|docker|npm|pnpm|gh)\b.*\b(login|auth|token|secret|credentials|env)\b' ~/.zsh_history
1
grep -nEi '(subscription|shadowrocket|clash|mihomo|vless://|vmess://|trojan://|ss://|trojan|proxy)' ~/.zsh_history
1
grep -nEi 'https?://[^[:space:]]*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[^[:space:]]*' ~/.zsh_history

4. What to Do If You Find Hits?

After performing an audit, you can choose to delete local history if there is a risk of credentials leaking. Here are recommendations prioritized by urgency:

  1. Active tokens / API keys (OpenAI, Anthropic, GitHub PAT, NPM Token…) — Immediately revoke them on the corresponding platform and reissue.
  2. Subscription URLs / VPN node links — Reset the UUID or token, as old links may continue to have their traffic abused.
  3. Database connection strings, Redis / MongoDB URIs — Change passwords, and adjust network access whitelists if necessary.
  4. HTTP(S) URLs with passwords — Change passwords, and check if they are left synchronized in git remote, ~/.netrc, etc.
  5. Cloud service credentials (AWS Access Key, GCP Service Account JSON content) — Perform cloud provider rotation procedures, and inspect CloudTrail / Audit Logs for abnormal calls.

5. Build and Solidify Good Habits

  • Adding a leading space is something that needs to be practiced until it becomes muscle memory: before pasting any command containing a secret, press Space first.
  • If you can read from a file, don't write it on the command line: curl --header @auth.txt, gh auth login --with-token < token.txt, and docker login --password-stdin are all better approaches.
  • For truly sensitive keys, the long-term solution is to put them in macOS Keychain / Windows Credential Manager / pass / 1Password CLI / gh secret, so only reference names appear in the command line, not plaintext.
  • Set a monthly reminder for yourself to "audit history"—supply chain incidents are usually disclosed long after they occur, so periodic audits are more important than a one-time cleanup.

Terminal history is not your notepad; it's an attacker's dictionary. Leave less in it, and the next time an incident happens, you'll have a much shorter list of things to rotate overnight.

Comments
Share

Comments