笔记

别让终端历史成为你的"密码本":避免记录敏感命的实用指南

2026-05-17 #Bash#Zsh#Powershell#Shell#开发者安全

前言:供应链攻击之下,每一行命令都可能是入口

2025 年至今,针对 npm、PyPI、GitHub Actions、Docker Hub 的供应链攻击事件持续走高:被污染的依赖、被劫持的 CI Token、被钓鱼的维护者账号……攻击者越来越偏好「绕过你的代码、直接拿你的凭证」的低成本路径。

而开发者最容易忽略的一处 “凭证仓库” ,恰恰是每天都在用、却几乎从不审计的:Shell 历史记录文件

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

这些文件以 明文 保存你输入过的所有命令——包括误粘贴的 API Key、带密码的 URL、订阅链接、数据库连接串。一旦你的电脑被恶意 npm 包、伪装 VSCode 插件、或被入侵的 CLI 工具读取到这些路径,攻击者无需提权就能直接打包带走。

但是有时也的确会需要在命令中输入密钥,比如 export API_KEY=,本文给出两件可立刻落地的小事:

  1. 写命令时:让敏感命令一开始就不进历史。
  2. 写完之后:定期扫一遍历史文件,看看自己是不是早就漏过。

一、macOS / Linux:在命令前加一个空格

bashzsh 都支持 HISTCONTROL / HIST_IGNORE_SPACE —— 一旦开启,以空格开头的命令不会被写入历史文件,也不会出现在 历史回溯里

很多发行版或 shell 框架可能已经启用启用了这个特性。验证一下:

1
2
3
4
5
# bash - 期望包含 ignorespace 或 ignoreboth
echo "$HISTCONTROL"

# zsh - 期望看到 histignorespace
setopt | grep histignorespace

如果没有,加入你的 ~/.bashrc~/.zshrc

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

之后,敏感命令前面加一个空格即可:

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

退后退出本次终端(将命令写入历史记录文件),重新打开,使用下面的命令验证:

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

二、Windows PowerShell:用 AddToHistoryHandler 自己实现

PowerShell 没有内置「前导空格不入历史」的开关,但 PSReadLine 提供了 AddToHistoryHandler,可以自定义哪些命令进历史。

配置步骤

打开(或创建)你的 PowerShell profile:

1
notepad $PROFILE

写入:

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

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

return $true
}

保存后,重启 PowerShell。之后像这样以空格开头输入的命令,不会写入当前会话历史,也不会落盘到 ConsoleHost_history.txt

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

PSReadLine 文档明确说明:handler 返回 $false 等同于 SkipAdding当前会话和历史文件都不会保留这条命令。

这只影响之后输入的新命令;已经写进 ConsoleHost_history.txt 的内容不会被清理。


三、回头审计:你的历史里早就有什么?

加了开关只是治未病。下一步是翻账——很多人第一次扫描自家 ~/.zsh_history 都会被吓到。

下面这一组命令面向 zsh,bash 用户把路径换成 ~/.bash_history 即可。

1. 先看文件大小,心里有数

1
ls -lh ~/.zsh_history

2. 排查常见密钥/密码关键词

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. 排查典型 Token 格式

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

覆盖:OpenAI / Anthropic 风格 sk-…、GitHub Fine-grained PAT、Classic PAT、AWS Access Key、Google API Key。

4. 排查带账号密码的 URL

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

形如 https://user:password@host/... —— 在 git clone、curl、psql 里非常常见。

5. 排查环境变量赋值

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

export FOO_TOKEN=...API_KEY=... 这类一行式赋值。

6. 排查 curl / 请求头泄漏

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

7. 排查云服务 / 部署工具的登录与密钥命令

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

8. 排查订阅 / 科学上网链接

1
grep -nEi '(subscription|shadowrocket|clash|mihomo|vless://|vmess://|trojan://|ss://|trojan|proxy)' ~/.zsh_history

9. 排查 URL 中的 UUID(常见于订阅链接)

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

四、命中了之后怎么办?

主动审查后,可以选择删除本地的历史记录,如果担心凭证存在泄漏风险。 可以按照优先级建议排查:

  1. 真实的 token / API key(OpenAI、Anthropic、GitHub PAT、NPM Token……)—— 立刻去对应平台 revoke 并重新签发。
  2. 订阅 URL / VPN 节点链接 —— 重置 UUID 或 token,旧链接会一直被滥用流量。
  3. 数据库连接串、Redis / MongoDB URI —— 改密码,必要时调整网络访问白名单。
  4. 带密码的 HTTP(S) URL —— 改密码,并检查 git remote、~/.netrc 等地方是否同步残留。
  5. 云服务凭证(AWS Access Key、GCP Service Account JSON 内容)—— 走云厂商的 rotate 流程,并检查 CloudTrail / Audit Log 是否有异常调用。

五、把好习惯固化下来

  • 行首加空格这件事,要练成肌肉记忆:复制任何含密钥的命令,先按一下 Space 再粘贴。
  • 能用文件读取的就不要写在命令行:curl --header @auth.txtgh auth login --with-token < token.txtdocker login --password-stdin 都是更好的形态。
  • 真敏感的密钥,长期方案是放进 **macOS Keychain / Windows Credential Manager / pass / 1Password CLI / gh secret**,让命令行里只出现引用名,不出现明文。
  • 给自己设一个每月一次的「翻历史」提醒——供应链事件大多在事发后才被披露,定期回头审计比一次性清理更重要。

终端历史不是你的备忘录,是攻击者的字典。少留一点东西在里面,下次出事的时候,你就少一份要连夜轮换的清单。

Like 评论
分享