GitHub 包防火墙:在恶意包运行之前就拦住它

·阅读约11分钟·Evergreen Tools Team

2026 年 10 月 20 日,GitHub 上线了包防火墙(package firewall)的公开预览。它要解决的问题很具体:越来越多的恶意包攻击在「安装那一刻」就完成窃取——在 CI/CD 主机或开发者工作站上收集凭据,甚至根本不需要执行包里的一行代码。于是传统那套「先看看源码有没有问题」的防御就失效了,因为你压根跑不到代码。GitHub 的做法是把一道检查插在端点和上游仓库之间:Actions runner、编码 Agent 这些端点的元数据请求和包下载都要流经它,由它按企业策略判定放不放行。这篇文章讲清三件事:它的策略控制到底有哪些、怎么找出还在绕过它的端点、以及为什么「冷却期」是这里面性价比最高的一个设置。

服务器机柜与网线

防火墙卡在 runner 与仓库之间,而不是在代码里面

一、它防的不是「跑起来的坏代码」

先把威胁模型说清楚,否则你会用错工具。GitHub 的描述是:越来越多的恶意包攻击利用「包安装阶段」来攻破系统,在 CI/CD 主机或开发者工作站上收集凭据,而这个过程中甚至不需要运行包本身的代码。安装阶段能干嘛?能执行安装脚本(比如 npm 的 postinstall)、能读取环境变量里的 token、能访问挂载的凭据文件、能向外发网络请求。也就是说,只要你的流水线执行了 npm install,攻击面就已经打开了,跟这个包的功能是不是恶意无关。所以这类攻击的正确拦截点是「下载之前」,而不是「运行之后」。GitHub 的定位是把防火墙放在端点(Actions runner、编码 Agent)与上游仓库之间,同时检查元数据请求和实际的包下载。

# 1) Find every endpoint still talking straight to the public registry.
#    A firewall only covers the paths you actually moved behind it.

grep -rn --include=".npmrc" --include="*.yml" --include="*.yaml" \
     --include="Dockerfile*" --include="*.sh" \
     -e "registry.npmjs.org" -e "registry.yarnpkg.com" \
     . 2>/dev/null | grep -v node_modules

# Common survivors: Dockerfiles, one-off bootstrap scripts, legacy repos,
# and the developer laptop that never got the new .npmrc.

二、策略控制:三个你现在就能设的旋钮

管理员先在企业层面设策略,目前可用的控制有三类:可配置的冷却期(cooldown period)、对 GitHub Advisory Database 里恶意或漏洞告警的检查、以及供应链检查(比如 attestation 被降级这类情况)。然后端点需要被配置成走防火墙,而不是直接指向上游包仓库——这一步是很多团队漏掉的,因为「企业开了策略」不等于「所有端点都走策略」。之后元数据请求和包下载都会经过防火墙,由它按策略评估。如果某个包被误拦,用户可以按类似 GitHub Advanced Security 其他功能的流程申请例外。这里有三个边界必须记住:第一,目前只支持 npm 包;第二,面向所有 GitHub Enterprise Cloud 客户(包括带数据驻留的版本);第三,预览期间免费,正式可用后会按用量计费,模式类似 Actions 计费。

# 2) Point installs at the firewall registry GitHub provisions for you.
#    Use the exact URL from the setup docs -- do not hand-write one.

# ~/.npmrc for CI, and the same file committed for repo-level config
registry=${GITHUB_PKG_FIREWALL_REGISTRY}
always-auth=true

# Prove the metadata request is routed through the firewall, not around it
npm config get registry
npm view lodash version --loglevel=http 2>&1 | grep -i "http fetch" | head -3

三、冷却期的真正价值

三个旋钮里,冷却期是最被低估的一个,因为它听起来最「笨」——不就是延迟几天再装新版本吗。但它防的正是当下最常见的攻击形态:维护者账号被劫持,然后在几个小时内发一个带后门的补丁版本。这类攻击的存活窗口往往很短,因为社区、安全研究者、以及自动化监控会在几天内发现并下架。冷却期就是把你放进「等别人先踩雷」的那条队伍里。第三段那段 Python 脚本干的事很简单:查一下你正要装的这个版本是多久之前发布的。如果答案是 6 小时前,一个 7 天冷却期就会让它先等一等。真正的价值不在脚本本身,而在于它把一个模糊的策略变成一个可核对的事实:你的冷却期到底拦住了哪些版本,拦下来之后有没有人绕过。

# 3) Cooldown, in numbers: how old is the version you are about to install?
#    A cooldown is not paranoia. It is the window in which the ecosystem
#    notices a hijacked maintainer account before your CI installs it.

python3 - <<'PY'
import json, urllib.request, datetime as dt

name = "example-package"
meta = json.load(urllib.request.urlopen(f"https://registry.npmjs.org/{name}"))
latest = meta["dist-tags"]["latest"]
published = meta["time"][latest]
when = dt.datetime.fromisoformat(published.replace("Z", "+00:00"))
age_h = (dt.datetime.now(dt.timezone.utc) - when).total_seconds() / 3600
print(f"{name}@{latest} published {published}  age={age_h:.1f}h")
print("within 7-day cooldown" if age_h < 168 else "passes cooldown")
PY

四、找出还在绕过防火墙的端点

防火墙只保护你真正搬过去的路径。第一段那条 grep 是最实用的一条命令,它在仓库里搜索所有还在直接指向 registry.npmjs.org 或 registry.yarnpkg.com 的地方。常见的漏网之鱼有四类:Dockerfile 里的 npm install、一次性 bootstrap 脚本、没人维护的遗留仓库,以及开发者本地那份从来没同步过的新 .npmrc。最后这类最危险,因为它不在任何流水线里,但它的凭据往往最全。找到之后,第二段把 registry 指向 GitHub 为你的企业提供的那个地址——注意用文档里给的准确 URL,不要自己拼。然后用 npm config get registry 和一次带 loglevel=http 的查询来确认请求确实走了防火墙,而不是绕过去了。这一步不能靠「我改了配置」来相信。

# 4) Do not let a lockfile change slip through review unread.
#    Malware at install time often arrives as a patch-level bump.

git fetch origin main --depth=1

diff <(git show origin/main:package-lock.json) package-lock.json \
  | grep -E '^[+-].*"(resolved|integrity|version)"' \
  | head -40

# Flag anything that changes host, drops integrity, or resolves to a
# tarball URL outside your approved registry.

五、把「不许绕过」变成构建门禁

策略、冷却期、Advisory 检查都是企业层面的设置,但真正决定成败的是最后一公里:你的流水线有没有断言自己在走防火墙。第五段给了两条 CI 断言:一条检查 npm 当前 registry 是否是防火墙地址,不是就直接让构建失败;另一条检查锁文件里是否还带着 integrity 哈希——锁文件没有完整性哈希,意味着你连「装的确实是这个 tarball」都保证不了。这两条断言的价值在于把「安全配置」从文档变成编译期的硬约束。第四段顺便补了一个容易被忽略的日常动作:看 PR 里的锁文件 diff。安装阶段的恶意包经常伪装成一个 patch 级别的升级,如果 diff 里出现了主机变了、integrity 丢了、或者 tarball 指向了你没批准的仓库,那就是要停下来看的信号。

# 5) Make bypass a build failure, not a code review comment.
- name: Assert installs go through the package firewall
  run: |
    reg=$(npm config get registry)
    echo "registry=$reg"
    case "$reg" in
      *"$GITHUB_PKG_FIREWALL_HOST"*) echo "ok: firewall in path" ;;
      *) echo "FAIL: installs bypass the package firewall"; exit 1 ;;
    esac

- name: Assert lockfile integrity is present
  run: |
    if grep -q '"integrity"' package-lock.json; then
      echo "ok: integrity hashes present"
    else
      echo "FAIL: lockfile without integrity hashes"; exit 1
    fi

六、现在该做什么

按顺序四步。第一,如果你在 GitHub Enterprise Cloud 上,去开预览并用文档走一遍配置,预览期间不花钱。第二,跑那条 grep,把还在直连公共仓库的端点和配置文件列出来——这份清单本身就是你部门的安全现状报告。第三,设冷却期,先选一个你的团队能忍的值(比如 7 天),并在流水线里记录被冷却期拦下的版本,形成可见性。第四,把 registry 和 integrity 两条断言加进 CI,让绕过变成构建失败而不是代码评审里的口头提醒。还有一条边界要提醒:现在只支持 npm。你的 Python、Go、Ruby 依赖不在这道防线的覆盖范围里,别因为 npm 有了防火墙就以为整条供应链都被保护了。顺带说,GitHub 同一天还更新了密钥扫描、并让 Dependabot 支持了 npm 12、pnpm 11/12 和 Bun 1.4——这三件事放一起看,才是完整的故事:发现泄露、更新依赖、以及拦住安装。

构建流水线概念图

冷却期买的是时间——让生态先发现那个坏版本

📌 常见问题 FAQ

包防火墙防的是什么类型的攻击?

主要防「安装阶段」的供应链攻击。按 GitHub 的描述,越来越多的恶意包攻击会在包安装时就攻破系统、在 CI/CD 主机或开发者工作站上收集凭据,而这个过程中甚至不需要运行包本身的代码,所以必须在下装之前拦截。

它现在支持哪些包管理器?

目前只支持 npm 包。Python、Go、Ruby 等其他生态的依赖不在覆盖范围内,这一点在评估整体供应链防护时容易产生误解,别把 npm 的保护当成整条链路都安全了。

有哪些策略可以配?

GitHub 目前列出的企业级控制有三类:可配置的冷却期、对 GitHub Advisory Database 中恶意或漏洞告警的检查、以及供应链检查(例如 attestation 降级)。端点还需要被配置成走防火墙,元数据和下载才能进入策略评估。

多少钱?

公开预览期间免费,面向所有 GitHub Enterprise Cloud 客户,包括带数据驻留的版本。GitHub 表示正式可用后会按用量计费,模式与 Actions 计费类似。

包被误拦了怎么办?

可以申请例外。GitHub 说流程与 GitHub Advanced Security 其他功能类似。建议在流程里同时记录误拦原因,否则「申请例外」会变成习惯性动作,冷却期就形同虚设。