2026年5月19日,GitHub官方确认一起严重的内部仓库入侵事件——威胁组织 TeamPCP 在暗网声称窃取约4000个私有代码仓库,开价5万美元兜售,GitHub随后核实约3800个内部仓库受影响。攻击源头不是零日漏洞,不是高级持续性渗透,而是一名员工安装了一个恶意 VS Code 扩展。
这件事值得每一位开发者和安全团队反复咀嚼:最危险的入口,往往是你每天打开的工具本身。
攻击链条还原
根据目前披露的信息,攻击路径可以拆解为三步:
第一步——投递恶意扩展。 攻击者将一个伪装成实用工具的 VS Code 扩展发布到市场(或通过其他渠道诱导员工安装)。扩展一旦激活,便获得了 VS Code 扩展宿主进程的权限,可以读取工作区文件、执行任意命令、访问环境变量和 Git 凭据。
第二步——窃取凭据与代码。 恶意扩展利用 VS Code 的 API 和 Node.js 运行时,扫描本地 Git 配置、SSH 密钥、.env 文件、token,并将这些信息外传到攻击者控制的服务器。有了凭据,攻击者可以直接克隆私有仓库。
第三步——批量拖库。 TeamPCP 利用窃取的员工凭据,批量访问 GitHub 内部私有仓库,将约3800个仓库的源代码完整拖走,随后在暗网挂牌出售。
整条链条的核心杠杆只有一个:VS Code 扩展的权限模型过于宽松。
VS Code 扩展为什么是完美的攻击面
VS Code 扩展运行在 Electron 的 Node.js 进程中,本质上就是一个拥有完整系统访问能力的 Node 程序。它能做到的事情远比多数开发者想象的更多:
- 通过
vscode.workspace.findFiles遍历工作区所有文件 - 通过
fs模块读写任意路径(取决于操作系统权限) - 通过
child_process执行任意 shell 命令 - 读取
process.env拿到所有环境变量 - 通过 Git CLI 或直接读取
.git/config、.git/credentials获取认证信息 - 通过
vscode.extensions.all列出已安装的其他扩展及其元数据
更关键的是,VS Code 的扩展安装流程几乎没有安全审查。任何人都可以在 Marketplace 发布扩展,审核主要依赖事后举报。一个扩展只需要在 package.json 里声明几个看起来无害的激活事件,就能在用户打开工作区时自动启动——用户甚至不会注意到新扩展已经运行。
实战防御:从检测到管控
下面给出几个可以直接落地执行的防御措施和对应的命令/配置。
1. 清点组织内已安装的 VS Code扩展
首先,你需要知道团队里每个人装了什么。VS Code 的扩展列表存储在用户目录下,可以用脚本批量采集:
# 列出当前用户安装的所有 VS Code 扩展及其版本
code --list-extensions --show-versions
# 输出格式示例:
# malicious.author@1.2.3
# legit.extension@0.4.5
如果你管理的是多台开发机,可以结合 SSH 或配置管理工具批量执行,将结果汇总到中央位置做比对。重点关注以下信号:
- 安装量极低(< 1000 installs)但名称听起来很"实用"的扩展
- 发布者账号最近刚创建
- 扩展声明了
onStartupFinished或*激活事件(意味着几乎始终运行) package.json中main入口指向混淆后的 JS 文件
2. 用扩展白名单策略锁定允许安装的范围
VS Code 支持通过 extensions.json 工作区推荐文件和策略设置来管控扩展。更硬性的做法是利用 VS Code 的 extensions.allowed 配置(需要 VS Code 1.90+ 或组织级策略分发):
// .vscode/settings.json — 在工作区级别限制可安装的扩展
{
"extensions.allowed": [
"ms-python.python",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
],
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": false
}
对于企业级管控,可以通过 Windows Group Policy 或 macOS MDM 将以下策略推送到所有开发机:
// 企业策略文件(通过 MDM/Group Policy 分发)
// 路径:/Library/Application Support/Code/Policies/settings.json (macOS)
// 或 %PROGRAMDATA%\Code\Policies\settings.json (Windows)
{
"extensions.allowed": [
"ms-python.python",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"extensions.ignoreRecommendations": true,
"extensions.showRecommendationsOnlyOnDemand": true
}
设置 extensions.allowed 后,不在白名单中的扩展将无法安装和激活。这是最硬的防线。
3. 审查扩展的权限声明和行为
对于必须引入的新扩展,在安装前做一次快速审查。以下脚本可以提取扩展的 package.json 关键字段,帮你判断风险等级:
# 下载扩展的 package.json 进行离线审查(不安装)
# 替换 EXTENSION_ID 为目标扩展,如 some.author@1.0.0
EXTENSION_ID="some.author@1.0.0"
# 从 Marketplace API 拉取扩展元数据
curl -s "https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery" \
-H "Content-Type: application/json" \
-d '{
"filters": [{
"criteria": [{
"filterType": 7,
"value": "'${EXTENSION_ID%%@*}'"
}]
}],
"assetTypes": ["Microsoft.VisualStudio.Services.Content.Details"],
"extensions": [{"extensionName": "'${EXTENSION_ID%%@*}'"}]
}' | python3 -c "
import json, sys
data = json.load(sys.stdin)
for ext in data.get('results', [{}])[0].get('extensions', []):
print('Publisher:', ext.get('publisher', {}).get('publisherName'))
print('Display Name:', ext.get('displayName'))
print('Install Count:', ext.get('statistics', [{}])[0].get('value', 'N/A'))
print('Last Updated:', ext.get('versions', [{}])[0].get('lastUpdated'))
for prop in ext.get('versions', [{}])[0].get('properties', []):
if prop.get('key') in ['activationEvents', 'extensionDependencies']:
print(prop['key'], ':', prop.get('value'))
"
审查时重点看:
| 风险信号 | 说明 |
|---|---|
activationEvents: ["*"] |
扩展在任何场景下都会激活,持续运行 |
依赖 child_process 或 fs 的直接调用 |
可能执行系统命令或读写敏感文件 |
main 指向打包/混淆的 JS bundle |
难以人工审计实际行为 |
| 发布者无历史记录、扩展无开源仓库 | 无法验证代码真实性 |
4. 保护 Git 凭据不被本地进程窃取
即使恶意扩展已经运行,你仍然可以通过凭据管理策略降低损害:
# 1. 检查当前 Git 凭据存储方式——避免使用 "store"(明文写入文件)
git config --global credential.helper
# 如果输出是 "store",立即切换到更安全的方式:
# macOS:使用 osxkeychain
git config --global credential.helper osxkeychain
# Windows:使用 manager-core(Windows Credential Manager)
git config --global credential.helper manager-core
# Linux:使用 libsecret(GNOME Keyring)
git config --global credential.helper /usr/share/git-core/git-libsecret
# 2. 检查是否有明文 token 写入 .git/config 或 .netrc
grep -r "ghp_" ~/.gitconfig ~/.netrc ~/.config/git/ 2>/dev/null
# 3. 如果发现明文 token,立即撤销(GitHub Settings > Developer settings > Personal access tokens)
# 并改用 SSH key + passphrase 或 credential helper
# 4. 为 SSH 密钥添加 passphrase,防止被直接读取后滥用
ssh-keygen -p -f ~/.ssh/id_ed25519
# 5. 使用 ssh-agent 管理已解密的密钥,限制生命周期
eval "$(ssh-agent -s)"
ssh-add -t 4h ~/.ssh/id_ed25519 # 4小时后自动失效
凭据 helper 将 token 存储在操作系统级别的加密凭据库中,而非明文文件。恶意扩展即使能调用 git credential fill,也只能在当前会话中获取凭据,而无法直接从磁盘读取明文密钥文件。
这件事对供应链安全的启示
GitHub 内部仓库被窃,表面看是"一个员工装了坏扩展",深层看是三个结构性问题:
开发工具的信任模型是单向的。 开发者信任工具,工具却不验证开发者。VS Code 扩展可以无限制地访问工作区、凭据、环境变量,而用户在安装时看到的只是一个功能描述和图标。这种不对称在 JetBrains、Eclipse 等IDE生态中同样存在。
代码托管平台的内部资产同样需要零信任。 GitHub 的内部仓库包含自有产品的源代码、内部工具、配置模板等。这些仓库的访问控制往往比面向客户的仓库更松散——内部员工默认拥有更广泛的访问权限。一旦单个员工凭据泄露,攻击者可以横向访问大量仓库。
暗网代码市场的成熟度在快速上升。 TeamPCP 不是随机黑客,而是有组织、有定价策略的威胁组织。5万美元的定价说明他们对代码的价值有评估框架——内部代码包含的 API 密钥、架构逻辑、未公开漏洞信息,都可以被二次利用。
落地检查清单
| 项目 | 动作 | 优先级 |
|---|---|---|
| 扩展清点 | code --list-extensions --show-versions 全团队执行并汇总 |
🔴 立即 |
| 扩展白名单 | 在 .vscode/settings.json 或企业策略中设置 extensions.allowed |
🔴 立即 |
| 凭据存储 | 禁止 credential.helper store,切换到 osxkeychain/manager-core/libsecret |
🔴 立即 |
| SSH 密钥加固 | 所有密钥添加 passphrase,通过 ssh-agent 设置 -t 超时 |
🟡 本周 |
| 新扩展审查流程 | 安装前检查发布者信誉、安装量、激活事件、源码仓库 | 🟡 本周 |
| 内部仓库访问审计 | 定期审查 GitHub org 内成员权限,收紧默认访问范围 | 🟡 本月 |
| CI/CD 凭据轮换 | 设置 GitHub token 自动过期时间(90天或更短),禁止永不过期的 token | 🟢 持续 |
一个恶意扩展,3800个仓库,5万美元。攻击者找到了最短路径——不是攻破防火墙,而是让开发者自己把钥匙递过去。防御的起点,是重新审视你每天打开的那个编辑器,到底被谁赋予了什么权力。