CI/CD 跑着跑着就被偷了——我把 GitHub Actions 供应链攻击的三个入口全拆开了

我配了三年前端 CI/CD,今天才发现这个「方便」是团队最大的安全缺口——GitHub Actions 的供应链攻击,三个入口我全踩了一遍。

攻击者搞 CI/CD 不用攻你的服务器,跑着你 runner 的人、偷着你 secrets 的工作流,权限比你自己的开发机还高。


第一个入口:PR 标题直接跑脚本

GitHub Actions 给你暴露了一堆上下文:github.event.issue.titlegithub.event.pull_request.titlegithub.event.comment.body。很多 workflow 习惯直接拿来用:

- name: Build
  run: npm run build -- --version=${{ github.event.pull_request.title }}

表面看是「用 PR 标题当版本号」,实际上攻击者只需要提一个 PR,标题写成:

v1.0.0" && npm owner add attacker && echo "hacked

GitHub 会把整个标题展开成 shell 命令的一部分。PR 一开,攻击者的代码就在有 secrets.GITHUB_TOKEN 和所有 Secrets 的环境里跑完了。

正确做法:用 toJSON() 把上下文序列化成安全字符串,或者用专门的 Action 做输入校验:

- name: Build
  run: |
    VERSION=$(echo '${{ toJSON(github.event.pull_request.title) }}' | jq -r '.')
    npm run build -- --version="$VERSION"

第二个入口:npm install 哪个版本你说了算吗

很多团队的 package.json 写的是 "lodash": "^4.17.21"npm install 的时候如果 registry 配置是这样的:

npm install lodash --registry=https://registry.npmjs.org

这没问题。但如果 workflow 里是这样写的:

- run: npm install
  env:
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

NPM_TOKEN 是公司私有 npm 的 token。问题是 npm install 在找不到私有包的时候会自动回退到公共 npm,并且优先安装版本号更高的那个

攻击者只要把你的私有包名 company-ui 找到,然后在 npm 上传一个 company-ui@999.0.0——npm 默认会安装最高版本,workflow 就把攻击者的恶意包跑起来了。

真正有效的防御是在 package.json 里明确指定私有 registry,或者用 .npmrc 锁定:

@company:registry=https://npm.company.com
registry=https://npm.company.com

第三个入口:打错一个字母,包就没了

npm install react 你打过,npm install reacct 呢?

typosquatting 就是注册和流行包名只差一个字母的假包:reacct、nodejs、lodahs。GitHub Actions 的 ubuntu-latest 镜像每次全新创建,攻击者专门扫描新建的 runner IP 段,一旦发现有人在跑 npm install 立即注册对应名称的包。

2025 年就有安全研究团队实测:一个流行的 npm 包如果被 typosquatting,从发现到有人中招平均只需要 4 小时

防御策略:

  • 团队统一用 package-lock.jsonpnpm-lock.yaml,锁定到具体版本和 hash
  • .npmrc 里加 verify hash 配置
  • npm audit 配合 CI,每次 install 后自动扫已知恶意包名

你的 CI/CD 其实比你想的肥

GitHub Actions runner 的 token 权限有多高?默认情况下有:

  • 对 repo 的读写权限(可写代码、可删分支)
  • 能访问所有 Secrets(数据库密码、API Key、生产环境部署凭证)
  • 能触发其他 workflow(横向移动)

一旦 CI/CD 被攻破,攻击者拿到的不是一个用户账号,是整条部署链路

下一步可以做的:

  1. workflow_write 权限默认关掉,需要才开
  2. 给 Secrets 设置 Environment protection rules(要求人工审批才能访问)
  3. 跑完 CI/CD 立刻 revoke 临时 token,不要让 token 长期有效
  4. actions/checkout 时指定 persist-credentials: false,减少凭证泄露面

GitHub 的 runner 有 2FA、有审计日志,看起来比普通服务器安全——但它跑的是你的供应链,供应链被控,比服务器被黑更难发现。


你的团队配过这些防护了吗?还是也在用 PR 标题直接跑命令?

评论区

0 条评论

登录后可评论。

阿监·前端工程化 336 阅读