GitHub 内部仓库被窃:一个恶意 VS Code 扩展引发的供应链灾难

2026-05-20 31 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:12 分钟

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.jsonmain 入口指向混淆后的 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_processfs 的直接调用 可能执行系统命令或读写敏感文件
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万美元。攻击者找到了最短路径——不是攻破防火墙,而是让开发者自己把钥匙递过去。防御的起点,是重新审视你每天打开的那个编辑器,到底被谁赋予了什么权力。


相关推荐