报错现象
以一次 VS Code 扩展被植入后门的供应链事件为样本,做一次完整的应急体检演练。这类事件的特点是没有明显报错:多数人是在扩展宿主崩溃、安全软件弹窗、或者账单异常之后才察觉。具体事件细节以厂商公告为准,本文只讲排查方法。
常见现象:
- 扩展宿主反复崩溃:
Extension host terminated unexpectedly,或频繁出现Extension host (Local) is not responding,并且崩溃时间点和你打开某个项目吻合。 - 安全软件或 EDR 出现类似
Suspicious outbound connection from Code Helper (Renderer)的告警,目标是一个你没见过的域名。 - API 用量或账单异常:控制台里出现夜间、周末的大量调用,而这些请求不是你发出的。
- 收到 GitHub secret scanning 告警、云厂商的异常登录邮件,或者发现多了自己没创建过的 API key。
- 扩展面板里出现了你没装过的扩展,或某个扩展的版本号在你没有操作的情况下发生了变动。
- 终端里出现陌生的
node、curl常驻进程,ps aux | grep -i node能看到来源不明的进程。 .env、~/.ssh/、~/.aws/的访问时间(atime)在你只是打开项目后发生了变化。
影响范围要按机器来算:一台开发机上的扩展通常能读到你的整个用户目录,所以这台机器上出现过的所有凭据,都要视为可能已经泄露。
可能原因
按出现概率从高到低:
1. 从非官方渠道安装扩展:朋友发的 .vsix、网盘下载的安装包、第三方镜像站,安装时绕过了市场审核。
2. 装到了仿冒扩展:名字、图标、描述和正版高度相似,只有发布者(publisher)不同。
3. 已装扩展自动更新到了被投毒的版本:这一步你并没有做错什么,属于上游供应链问题,也是这类事件里最难防的一种。
4. 扩展依赖的 npm 包被投毒,或者安装、激活时执行了 postinstall 之类的脚本。
5. 工作区配置被动了手脚:.vscode/tasks.json 里配置了打开文件夹就自动执行的任务。
6. 凭据以明文形式散落在 settings.json、.env、shell 配置文件、git 历史里,被扩展读取后直接外传。
7. 本机代理或 DNS 被改,流量被引到中间人。
8. 误报:正常的遥测上报、更新检查、语言服务器外联被当成了攻击行为。
逐条排查与解决
第 1 步:先止血,再取证。 断网或拔掉网线,用安全模式启动,确认问题是否来自扩展:
```bash
code --disable-extensions
```
如果安全模式下一切正常,说明问题大概率在扩展侧。此时先别急着卸载,把扩展目录复制一份留作证据:
```bash
cp -a ~/.vscode/extensions /tmp/ext-backup-$(date +%s)
```
Windows 下路径是 %USERPROFILE%\.vscode\extensions,WSL / 远程开发用的是 ~/.vscode-server/extensions。
第 2 步:清点扩展并核验来源。
```bash
code --list-extensions --show-versions
```
输出格式是 publisher.name@version。逐个对照扩展详情页:发布者名字是否一致、有没有认证标识。仿冒品通常只差一两个字母,比如把 ms-python 写成 ms-pythonn,肉眼很容易漏掉。
再看目录本身,目录名格式为 publisher.name-version,时间戳也能提供信息:
```bash
ls -la ~/.vscode/extensions | sort -k6,7
```
判断依据:如果你没手动更新过,某个目录的修改时间却跳到了最近几天,说明它是自动更新来的。
建立一份基线,以后定期对比:
```bash
code --list-extensions --show-versions | sort > ~/ext-baseline.txt
之后任何时候
code --list-extensions --show-versions | sort > ~/ext-now.txt
diff ~/ext-baseline.txt ~/ext-now.txt
```
第 3 步:静态扫描扩展代码。 先定位可疑扩展目录,再做关键字搜索:
```bash
EXT_DIR=~/.vscode/extensions/publisher.name-version
grep -rInE "child_process|execSync|spawn\(|eval\(|atob\(|base64" "$EXT_DIR" --include="*.js" | head -n 40
grep -rhoE "https?://[a-zA-Z0-9._-]+" "$EXT_DIR" --include="*.js" | sort | uniq -c | sort -rn | head -n 40
```
判断依据:第二条命令会列出扩展里出现过的所有域名。一个格式化工具却访问 *.workers.dev、*.ngrok.io 这类托管平台,或者直连 IP,就很值得进一步检查。正常的扩展一般只会访问自己的官网、更新服务和文档链接。
顺便看一下入口文件和激活条件:
```bash
python3 -m json.tool "$EXT_DIR/package.json" | head -n 80
```
关注三处:activationEvents 里有没有 *(启动即激活,权限面最大)、scripts 里有没有 postinstall、main 指向的文件是不是被压成了一行几万字符的混淆代码。
第 4 步:出网审计。 关键是找到「哪个进程连了哪里」。
```bash
macOS
lsof -i -P -n | grep -i "code"
或者先拿 PID 再看它的连接
pgrep -fl "Code Helper"
lsof -p <PID> -i -P -n
Linux
ss -tnp | grep -i code
Windows PowerShell
Get-NetTCPConnection | Where-Object { $_.State -eq 'Established' } |
Select-Object RemoteAddress, RemotePort, OwningProcess
```
判断依据:看 RemoteAddress 是不是公网地址。拿到 IP 后查归属:
```bash
dig -x <IP>
whois <IP> | head -n 20
```
如果解析出来的是你没听说过的云主机商,而扩展本身又不该联网,就基本可以确认了。
再看它解析了哪些域名,这一步能抓到走 CDN 隐藏的连接:
```bash
sudo tcpdump -n -i any port 53
```
打开项目、等一分钟,观察有哪些域名被查询。这一步不需要抓包解密,只看域名就够判断了。
第 5 步:需要看请求内容时的抓包。 仅在隔离的机器上做:
```bash
mitmproxy -p 8080
```
然后在 VS Code 的 settings.json 里加 "http.proxy": "http://127.0.0.1:8080",重启后观察请求。注意这需要安装代理的 CA 证书,会削弱传输层安全性,排查结束后立刻删除代理配置和证书。
第 6 步:检查工作区配置。 打开陌生项目时,.vscode/ 目录同样能执行代码:
```bash
ls -la .vscode/
cat .vscode/tasks.json .vscode/settings.json 2>/dev/null
```
判断依据:tasks.json 里出现 "runOptions": {"runOn": "folderOpen"},表示打开文件夹就自动跑任务,这是常见的自动化投毒点。另外,如果 VS Code 提示工作区不受信任,别顺手点「信任」。
第 7 步:盘点凭据暴露面。 任何一条命中,都直接按「已泄露」处理:
```bash
env | grep -iE "key|token|secret|password" | sed 's/=.*/=<redacted>/'
grep -nE "KEY|TOKEN|SECRET|PASSWORD" ~/.zshrc ~/.bashrc ~/.bash_profile 2>/dev/null
git ls-files | grep -i "\.env"
git log -p -S "sk-" --oneline | head -n 20
```
第 8 步:按顺序轮换凭据。 原则是「先吊销、再新建、再替换使用点、最后确认旧凭据彻底失效」,顺序按影响面从上游到下游:
1. 先撤销能直接产生费用的:模型服务商的 API key。在控制台删除旧 key 并生成新 key。Claude Code 的登录凭据可能保存在系统钥匙串或配置目录里,两处都要检查;退出登录再重新登录,具体路径以官方文档为准。
2. 云平台:aws sts get-caller-identity 确认当前身份,到 IAM 控制台把 access key 置为 Inactive 后再删除;gcloud auth list 和 gcloud auth revoke --all;az account show 确认订阅。
3. 代码托管:gh auth status 看当前登录,gh auth logout 登出;在 GitHub 的 Developer settings 里撤销全部 PAT;SSH key 用 ssh-keygen -lf ~/.ssh/id_ed25519.pub 拿到指纹,在账号里删掉对应公钥并重新生成。
4. 包仓库:npm whoami、npm token list、npm token revoke <token-id>。发布权限的 token 优先级高于只读 token。
5. CI/CD 的 Secrets:全部重置,尤其是能部署生产环境的那几个。
6. 数据库与其他 SaaS:改密码、吊销 API key,同时检查有没有新增的子账号或 OAuth 授权。
7. 回查有没有被顺手装的后门:~/.ssh/authorized_keys 里多出来的公钥、新增的 CI runner、新增的 OAuth App、云上新建的 IAM 用户。
判断轮换是否彻底:在控制台看旧 key 的「最近使用时间」是否不再更新;云厂商的审计日志(CloudTrail、Activity Log 之类)里是否还有旧凭据的调用记录。只要还在被调用,说明某处还留着副本。
都不管用时的兜底方案
- 用干净配置做对照,这一步能区分「扩展的问题」和「配置文件的问题」:
```bash
code --user-data-dir /tmp/clean --extensions-dir /tmp/clean-ext
```
- 全量卸载重装扩展,前提是先备份清单:
```bash
code --list-extensions > ~/ext-backup.txt
code --list-extensions | xargs -n1 code --uninstall-extension
```
- 把本地机器当作已污染,敏感项目全部转到 devcontainer、虚拟机或远程开发机上,本地只做非敏感工作。
- 检查系统级持久化,扩展可能只是入口,真正的驻留可能在别处:
```bash
crontab -l
ls ~/Library/LaunchAgents /Library/LaunchAgents 2>/dev/null
systemctl --user list-units --type=service
```
- 确认存在系统级后门后,重装系统是止损手段。重装前必须先把凭据全部轮换一遍,否则新系统装好、旧凭据还在被用,等于没做。
- 上报:通过扩展市场的举报入口和厂商安全邮箱反馈,企业环境同步通知安全团队并保留完整时间线(什么时候发现的、装了哪个版本、轮换了哪些凭据)。
如何预防再次发生
- 关掉扩展自动更新,在
settings.json里设置"extensions.autoUpdate": false,需要更新时手动确认。这一步能挡住大部分「上游被投毒、你被动中招」的情况。 - 只从官方市场安装,装之前核对 publisher 名字。收到
.vsix文件先unzip -l看看里面有什么,再决定装不装。 - 维护扩展清单基线,每周
diff一次,多出来的扩展第一时间能看到。 - 开启 Workspace Trust,陌生仓库一律用受限模式打开。
- 开发环境隔离:敏感项目在容器或虚拟机里开,扩展装在工作区级别而不是全局。
- 凭据最小权限 + 短期有效:给扩展和脚本用的 key 只开必需的权限,能设过期时间就设。用密钥管理器注入环境变量,而不是把 key 写进
~/.zshrc。 - 打开用量告警和审计日志:模型服务、云平台、代码托管三处都设置异常用量通知,异常请求通常比人先发现。
- 定期演练十分钟:
code --list-extensions --show-versions加一条lsof -i -P -n | grep -i code,成本很低,但真出事时能省掉大量猜测。
