axios 威胁模型深度解析:运行时攻击面与 npm 供应链安全的完整防御蓝图
axios 官方维护的 THREATMODEL.md 是目前 HTTP 客户端库中少见的公开威胁模型文档,它同时建模两套系统:运行时(保护 import axios 的应用)与项目/SDLC(保护发布到 npm 的制品完整性)。本文完整继承该文档的威胁清单、缓解措施与事件响应手册,并逐一对照 axios 仓库源码验证每项缓解措施的真实实现位置,帮助维护者、安全研究员与做供应链尽调的下游消费者建立一份可查证、可落地的 axios 安全认知。
1. 建模范围与方法论
axios 将自身拆分为两个截然不同的安全系统分别建模:
| 系统 | 保护对象 | 攻击者画像 |
|---|---|---|
| 运行时(Runtime) | 所有 import axios 的应用 |
恶意服务器、网络中间人、恶意应用输入 |
| 项目 / SDLC(供应链) | 发布到 npm 的制品完整性 | 供应链攻击者、钓鱼者、恶意贡献者 |
对每个系统,文档依次列出资产(Assets)、信任边界(Trust Boundaries)、威胁行为者(Threat Actors)与威胁(Threats),并按 可能性 × 影响 评级。关键原则:当缓解措施真实存在于代码库中时,文档直接引用源码文件路径;当不存在时,明确说"不存在"——这一点正是本文可以逐条对仓库源码验证的原因。
文档还刻意区分了两套模型的抽象层级:运行时模型是"通用的",因为 axios 作为传输层库无法知道调用方认为什么是敏感数据;项目模型则是"具体且可执行"的。
2. 运行时威胁模型
2.1 系统概览与信任边界
axios 的请求管道可以抽象为如下结构(引自原文档):
┌─────────────────┐
│ Application │ ← 受信:负责写 config、持有密钥
│ (caller code) │
└────────┬────────┘
│ axios(config)
┌────────▼────────┐
│ Interceptors │ ← 调用方提供的代码,进程内运行
├─────────────────┤
│ Config merge │ ← lib/core/mergeConfig.js
│ URL build │ ← lib/core/buildFullPath.js, lib/helpers/buildURL.js
│ Header build │ ← lib/core/AxiosHeaders.js
├─────────────────┤
│ Adapter │ ← http.js / xhr.js / fetch.js
└────────┬────────┘
│
═════════▼═════════ ← 信任边界(网络)
│
┌────────▼────────┐
│ Proxy (opt.) │ ← 部分受信(HTTP 明文下可见一切)
└────────┬────────┘
┌────────▼────────┐
│ Origin server │ ← 一般情形下不可信
│ + redirects │
└─────────────────┘
文档明确了四条信任边界:
- 调用方 → axios:调用方完全受信,传入
config的一切都视为有意为之。axios 不对抗恶意调用方,这是明确的 non-goal。 - axios → 网络:socket 之外的一切(响应状态码、头、body、重定向
Location、代理响应)均不可信。 - axios → 环境变量:
HTTP_PROXY/HTTPS_PROXY/NO_PROXY由proxy-from-env读取。控制环境变量的攻击者可以重定向全部流量——虽然它被视为"与进程同权"因而归为受信,但在容器逃逸与 CI 场景下是重要的攻击支点。 - 调用方钩子 → axios 内部:interceptors、
transformRequest、transformResponse、paramsSerializer、beforeRedirect与自定义 adapter 以完整进程权限运行,axios 不做沙箱化。
资产清单(文档 §2.2):传输中的凭据(config.auth、Authorization 头、cookie、XSRF token)、请求/响应体(可能含 PII 与商业机密)、调用方进程完整性(原型链污染可被下游 gadget 放大为 RCE)、调用方内网(SSRF 可穿透运行 axios 的主机)、可用性(解压炸弹、重定向循环、慢速响应)。
威胁行为者:恶意服务器(最普遍,控制响应每一个字节)、路径上的网络攻击者(MITM,被 TLS 缓解——除非调用方关闭校验)、恶意重定向目标、通过宿主应用控制请求片段的普通应用用户。
2.2 威胁 T-R1:调用方控制 URL 导致的 SSRF
| 维度 | 评估 |
|---|---|
| 描述 | 应用把用户输入插值进 config.url 或 config.baseURL,攻击者构造 http://169.254.169.254/、http://localhost:6379/、file://、gopher:// 等 |
| 可能性 | 高——这是真实世界中最常见的 axios 误用模式 |
| 影响 | 高——云元数据窃取、内网服务访问 |
| 是否在范围内 | 部分。axios 无法知道调用方允许哪些 URL |
缓解措施(已对源码验证):
allowAbsoluteUrls: false可阻止相对url覆盖baseURL。源码 lib/core/buildFullPath.js 中buildFullPath(baseURL, requestedURL, allowAbsoluteUrls, config)只有在isRelativeUrl || allowAbsoluteUrls === false时才执行combineURLs,默认保持true以向后兼容。- HTTP adapter 只接受
http:/https:/file:/data:(Node)或http:/https:/file:/blob:/url:/data:(浏览器),gopher:等冷门 scheme 会在 lib/platform/node/index.js 与 lib/platform/browser/index.js 被拒绝。 - 没有内置主机白名单——目的地校验是调用方的责任。
残余风险:显著。SSRF 防护被明确记为调用方责任。
2.3 威胁 T-R2:跨源重定向的凭据泄露
场景:调用方设置 Authorization: Bearer … 请求 https://api.trusted.com/x,服务器返回 302 Location: https://evil.com/——Bearer token 会跟着去 evil.com 吗?
缓解措施:
- Node adapter 委托给
follow-redirects@^1.16.0,在跨主机重定向与 HTTPS→HTTP 降级时剥离Authorization、Cookie、Proxy-Authorization; sensitiveHeaders配置项允许调用方声明自定义秘密头(如X-API-Key),axios 会在跨源重定向时同样剥离;maxRedirects默认 5,设为0可完全手动处理重定向;beforeRedirect回调支持自定义检查;- 浏览器 adapter(XHR/fetch)委托浏览器自身的跨源凭据规则。
残余风险:对标准凭据头与已配置的自定义秘密头为"低"。文档特别强调:axios 继承 follow-redirects 的安全姿态——它作为关键传递依赖的 CVE 就是 axios 的 CVE;调用方必须自行列出希望剥离的自定义秘密头。
2.4 威胁 T-R3:CRLF 头注入
攻击者把 foo\r\nX-Injected: bar\r\n\r\n<body> 塞进 headers: { 'X-User': req.query.name }。相关面还有 multipart 的 per-part 头:攻击者控制的 blob.type / blob.name 流入 multipart body。
缓解措施(双层防御):
- lib/core/AxiosHeaders.js 拒绝包含
\r或\n的头值,并按 RFC-7230 风格的字符集校验头名;Node 自带http模块同样拒绝。 - lib/helpers/formDataToStream.js 在把
value.type与value.name插入 per-part 头之前,剥离 CRLF 并对name中的 CRLF/"做百分号编码(escapeName(),对应 GHSA-445q-vr5w-6q77)。文档明确指出:Nodehttp模块在这一层不设防——multipart 注入发生在 body 字节里,不是请求头里,所以这是 axios 必须自己做的防御。
残余风险:HTTP 头非常低(axios + Node 双层防御纵深);multipart body 头为"低"(单层防御,此处一旦回归是静默的)。
2.5 威胁 T-R4 / T-R4b:原型链污染的写入侧与读取侧
T-R4(写入侧):服务器返回 {"__proto__": {"isAdmin": true}}。若 axios 天真地把它合并进普通对象,进程中所有 {} 都会获得 .isAdmin。
缓解:JSON.parse 本身不污染(它创建名为 __proto__ 的自有属性,而非原型链接);内部合并路径过滤危险键——lib/utils.js 中第 20 行明确排除 __proto__ / constructor / prototype,lib/core/mergeConfig.js 与 lib/helpers/formDataToJSON.js 同样过滤。这些过滤是针对历史安全公告的修复,此处一旦回归即 P0。
T-R4b(读取侧 gadget,更隐蔽):调用方依赖树里另一个库污染了 Object.prototype(如 Object.prototype.validateStatus = () => true)。axios 通过原型链读取配置属性时就会拾取攻击者的值并执行关联行为。每个可达属性都是一个独立 gadget:validateStatus(绕过 HTTP 错误处理)、parseReviver(静默篡改 JSON 响应体)、transport / httpAgent / lookup(MITM/截获)、withXSRFToken(跨源泄露 XSRF token)、transformResponse(替换响应)等。
缓解措施全部经源码验证:
| 位置 | 防御手法 | 对应公告 |
|---|---|---|
| lib/core/mergeConfig.js | 对 config1/config2 的逐属性读取用 hasOwnProp 守卫;mergeDirectKeys(validateStatus 使用)改用 hasOwnProp 而非会遍历原型链的 in 操作符 |
GHSA-w9j2-pvgh-6h63 |
| lib/defaults/index.js | transformResponse / transformRequest 读取 transitional、responseType、parseReviver、response 时经 own() 包装 |
GHSA-3w6x-2g7m-8v23 |
| lib/adapters/http.js | transport、httpAgent、httpsAgent、lookup、family、http2Options 等一律经 hasOwnProp 读取 |
GHSA-pf86-5x62-jrwf gadget 集 |
| lib/helpers/resolveConfig.js | withXSRFToken 要求严格 === true 才跨源发送头,非布尔真值(1、"false"、{})不再短路同源检查 |
GHSA-xx6v-rp6x-q39c |
在 lib/helpers/resolveConfig.js 第 89–93 行可以看到注释与实现完全吻合:"Strict boolean check — prevents proto-pollution gadgets (e.g. Object.prototype.withXSRFToken = 1)",随后 withXSRFToken === true || (withXSRFToken == null && isURLSameOrigin(newConfig.url))。
gadget 类的回归测试(tests/unit/prototypePollution.test.js)覆盖单元级与针对 axios.get 的端到端场景。
残余风险:低,但攻击面是"每一个配置属性读取点"。任何新代码路径读取 config.foo / this.foo 或从合并后的 config 解构,都必须走 hasOwnProp 守卫。文档还澄清了一条容易被误解的 non-goal 边界:axios 不防御"调用方故意污染原型"的情形,但污染通常来自传递依赖而非调用方本意——上述缓解使得即使原型已被污染,可达 gadget 仍然被中和。
2.6 威胁 T-R5:解压炸弹
服务器发送 Content-Encoding: gzip,10 KB body 解压后 10 GB。
缓解措施(源码验证):
- lib/adapters/http.js 中
maxContentLength限制解压后的响应大小,对 buffer 与responseType: 'stream'两种路径均按块强制(stream 路径修复于 GHSA-vf2m-468p-8v99)。源码第 706–714 行(buffer 路径)与第 1210–1251 行(stream 路径,含maxContentLength size of … exceeded报错)可佐证。 maxBodyLength限制请求侧,且在maxRedirects === 0时同样生效(此前可被绕过)。- 两者默认都是
-1(不限制)。文档直言:处理不可信服务器时必须显式设置;README 有顶层 "security notice" 提示,docs/pages/misc/security.md 在四个语言版本中给出了确切的缓解代码片段。 - 解压使用 Node
zlib流式处理,内存由上限而非完整展开量约束。
残余风险:未配置上限时为"中"。默认值偏兼容性而非安全性——理由是收紧默认值会静默破坏一切大于该上限的合法下载。
2.7 威胁 T-R6:TLS 校验绕过
调用方传入 httpsAgent: new https.Agent({ rejectUnauthorized: false }) "修复"开发环境证书错误,然后带着它上了生产。
评估:可能性"中"(极常见的复制粘贴反模式),影响"高"(静默 MITM)。明确不在范围内——axios 把 TLS 完全委托给 Node https 模块/浏览器,不检查也不警告 agent 配置。axios 层没有任何缓解,仅文档责任。残余风险"高但明确越界":这是调用方误配置,不是 axios 漏洞。
2.8 威胁 T-R7:XSRF token 跨源发送
浏览器部署中 xsrfCookieName 已设置,攻击者诱导应用请求 https://evil.com,XSRF token 的 cookie 值会被附为请求头发出。
缓解:lib/helpers/resolveConfig.js 只在 isURLSameOrigin() 通过(或 withXSRFToken 被显式强制)时才附加 XSRF 头——这正是 CVE-2023-45857 的修复。同源检查基于 WHATWG URL 解析器(lib/helpers/isURLSameOrigin.js),对 parser-differential 攻击是稳健的。残余风险:低。
2.9 威胁 T-R8:错误对象中的敏感数据
请求失败时 AxiosError 携带 config,其中含 config.auth、config.headers.Authorization、config.httpsAgent(内嵌客户端证书/私钥)。调用方把错误对象整体写入日志,秘密即泄露。
评估:可能性高(最普遍的实际泄露方式),影响"中到高"。AxiosError.toJSON()(lib/core/AxiosError.js)产生缩减视图,但活动错误对象仍按引用持有完整 config。
残余风险:"中"。使用结构化日志(Winston、带 serializer 的 Pino、Sentry)的调用方,若不配置脱敏,就会捕获凭据。文档定性:这是"已记录的风险而非漏洞",但是 axios 用户实践中泄露秘密最常见的方式。值得补充的是,lib/core/buildFullPath.js 中的 redactSensitiveURLParts() 已把 URL 中可携带秘密的部分(userinfo、查询参数值、fragment)用 REDACTED 标记脱敏后再嵌入错误消息——因为 toJSON() 会逐字序列化 message,而 opt-in 的 config.redact 只清理 config 键、够不到 message。
2.10 威胁 T-R9:代理环境变量劫持
攻击者控制进程环境(被入侵的 CI 步骤、容器逃逸、.env 注入),设置 HTTPS_PROXY=http://evil.com:8080,全部 axios 流量被 MITM。
缓解:
config.proxy: false完全禁用基于环境变量的代理探测;NO_PROXY被尊重(lib/helpers/shouldBypassProxy.js),近期针对 CIDR 段、IPv6 字面量与通配符模式做了加固,关闭 parser-differential 边界情形;- HTTPS 经任意代理走
https-proxy-agent的 CONNECT 隧道:源站证书端到端校验,代理只看到 SNI,永远看不到 URL、头与 body;Proxy-Authorization只发在 CONNECT 请求上,从不发在 TLS 保护后的包裹请求上。
残余风险:HTTPS 为"低";明文 HTTP 为"高"——代理可见并可篡改一切。
2.11 威胁 T-R10 与 T-R11
T-R10:恶意 interceptor / adapter——调用方安装第三方 "axios plugin",注册拦截器外泄每个 Authorization 头。可能性"低到中",影响"高"。不在范围内:interceptor 是运行在调用方进程里的调用方代码,axios 只提供钩子,审查钩子里装什么是调用方的事。文档的措辞很直白:axios.interceptors.request.use(evil) 与 require('evil') 之间没有实质区别。
T-R11:form-data 递归 DoS——调用方把不可信对象输入作为 data 传入,在序列化到 multipart/form-data / application/x-www-form-urlencoded 的上下文中,数千层嵌套让 lib/helpers/toFormData.js 递归到栈溢出。
缓解(源码验证):formSerializer.maxDepth 限制递归深度,默认值即 lib/helpers/toFormData.js 第 11 行的 DEFAULT_FORM_DATA_MAX_DEPTH = 100;超限抛出 ERR_FORM_DATA_DEPTH_EXCEEDED(定义于 lib/core/AxiosError.js 第 217 行)而非崩掉进程;可设 Infinity 禁用。文档在 docs/pages/advanced/multipart-form-data-format.md 与 docs/pages/advanced/x-www-form-urlencoded-format.md 中做了逐语言说明。残余风险:保留默认时为"低";设 maxDepth: Infinity 会重新引入风险。
2.12 运行时明确 non-goals
axios 承诺不做以下事情(每条都对应上面某个"残余风险"的边界):
- 不沙箱化、不校验调用方提供的函数(interceptors、transforms、adapters、serializers);
- 不校验
config.url指向"安全"的地方——axios 不知道你的应用里"安全"是什么含义; - 不在 TLS 校验被自定义 agent 关闭时发出警告;
- 不从抛出的错误中剥离
config——调用方可能合理地需要它做重试逻辑; - 不防御"调用方进程已被完全攻陷"的情形。但对更窄的情形——经传递依赖到达的
Object.prototype污染——axios 确实防御了可达的 config 读取 gadget(见 T-R4b); - 不防御被 monkey-patch 的 JavaScript / Node.js 运行时 API(
Object.keys、http.request、ClientRequest.prototype.setHeader、fetch等)。若攻击者代码已在同进程运行,它可以在 axios 之下观察或篡改请求,这超出 axios 的安全边界。
3. 项目 / 供应链威胁模型
这套模型保护的是发布到 npm 的 axios 包。在此处成功一次攻击,会同时攻陷每一个下游消费者——就 axios 的安装基数而言,这是文档中风险更高的一半。
3.1 发布流水线概览
Maintainer's workstation Contributor's fork+PR GitHub.com (source of truth)
! npm token? / SSH keys / untrusted code
GPG keys
│ git push │ PR
└───────────────────┴──────────────────▶
tag push: v1.x.y
│
GitHub Actions (.github/workflows/publish.yml)
• npm ci --ignore-scripts
• npm run build
• npm publish --provenance
• OIDC to npm (no token)
═════════▶ registry.npmjs.org
axios@1.x.y + provenance attestation
3.2 资产清单
| 资产 | 被攻破意味着 |
|---|---|
npm 包名 axios |
攻击者可以 axios@1.x.y+1 发布恶意代码,生态系统完蛋 |
| npm 发布能力 | 无论是 token、OIDC 信任还是账号接管 |
GitHub axios/axios 写权限 |
可推 tag 触发发布,或直接改 publish.yml |
| 维护者 GitHub 账号 | 传递性地获得以上全部 |
| 维护者工作站秘密 | SSH 键(GitHub push)、~/.npmrc token(若有,可直发)、GPG 键(签名提交)、云凭据(横向移动) |
| 构建确定性 | 若 dist/ 与 lib/ 不匹配,后门可藏在 minified bundle 里 |
| 运行时依赖完整性 | follow-redirects、form-data、proxy-from-env、https-proxy-agent 随每个 axios 安装一起分发 |
3.3 信任边界
- 贡献者 PR → main 分支:来自 fork 的 PR 不可信。CI 会运行它们,但
pull_request工作流无任何 secret 访问权、使用只读GITHUB_TOKEN。 - main 分支 → 发布 tag:推
v1.x分支不触发发布,只有推v1.*.*tag 才触发,且 tag push 需要写权限。 - GitHub Actions → npm:经 OIDC(
id-token: write到 npm trusted publisher)跨越。仓库不存在长寿命的NPM_TOKENsecret。 - 维护者工作站 → 一切:最软的一条边界。维护者的笔记本是"高价值、低保障"环境,见下节。
3.4 威胁行为者
| 行为者 | 能力 | 动机 |
|---|---|---|
| 顺手贡献者 | 只能开 PR,无 secret、无写权限 | 让后门蒙混过审 |
| 被攻陷的 dev 依赖 | 试图经 lifecycle script 在 npm install 时执行代码——在维护者工作站(项目级 .npmrc)与 CI(每个 job 的 --ignore-scripts)上均被阻断;残余执行路径:npm run build / test / lint 下的插件代码 |
窃取 token、注入构建 |
| 钓鱼者 | 只发 convincing 邮件/私信 | 维护者 GitHub/npm 凭据 |
| 被攻陷的维护者账号 | 完整写权限:可推 tag、可改 workflow | 直接发布恶意代码 |
| GitHub / npm 内部或平台级攻陷 | 越界——信任平台 | — |
3.5 威胁 T-S1:贡献者 PR 中的恶意代码
隐蔽后门:测试 fixture 里的混淆载荷、比较语句中的 Unicode 同形异码字符、rollup 配置里的恶意插件。可能性高(高知名度仓库上此类尝试是常态),影响Critical(如果落地)。
缓解:合并前强制审查;pull_request 工作流无 secret、只读 token,恶意测试无法从 CI 外泄任何东西;刻意不使用 pull_request_target(那会把 secret 授予 fork 代码);zizmor 对 workflow 文件做已知危险模式 lint(对应仓库中真实存在的 .github/workflows/zizmor.yml);v1.x 分支保护;包/lockfile/GitHub Actions 更新 PR 仅限维护者/bot,外部协作者对此类更新的 PR 会被关闭;路径作用域的 .github/CODEOWNERS 显式圈出敏感路径:运行时源码(/lib/、/index.*)、构建/发布基础设施(rollup.config.js、package.json、package-lock.json、.npmrc)、CI 自动化(.github/workflows/、.github/dependabot.yml、CODEOWNERS 本身)、安全关键文档(THREATMODEL.md、SECURITY.md)。
差距(文档坦承):人工审查会失误,dist/(若入库)或大型测试 fixture 的混淆改动难以发现;目前没有 lib/ 对 dist/ 的自动 diff 来捕捉构建产物篡改;单维护者约束——当唯一 scoped owner 是单人时,CODEOWNERS 无法强制第二审查人,两人评审在 co-maintainer 加入前不可用,路径作用域规则是"为此预置"的。
3.6 威胁 T-S2:被攻陷的 dev 依赖窃取维护者密钥
文档原文标注:"历史上最薄弱的一环。"
攻击链:约 45 个直接 dev 依赖(及其数千传递依赖)中任何一个被攻陷(维护者账号接管、过期域名重注册等),携带 postinstall script,读取 ~/.npmrc、~/.ssh/id_*、~/.config/gh/hosts.yml、~/.aws/credentials、~/.gnupg/ 并 POST 给攻击者。维护者下次在本地 npm install 时,脚本以维护者用户身份运行,拥有完整文件系统访问权——无需任何漏洞利用,这是 npm 按设计工作。
可能性"中且在上升"(event-stream、ua-parser-js、coa、rc、node-ipc、@solana/web3.js、polyfill.io 2024 事件等都是同一模式)。axios 的 dev 树包含 Babel、Rollup、Gulp、ESLint、Vitest、Playwright,攻击面庞大且随每次 npm install 刷新。
已采用的缓解(仓库验证):
- CI 侧:.github/workflows/publish.yml 执行
npm ci --ignore-scripts,恶意 lifecycle script 无法在发布构建中执行;CI 用 OIDC 而非存储 token,没有NPM_TOKEN可供恶意 workflow 步骤窃取; package-lock.json钉住版本与 integrity hash,新恶意版本无法静默抵达;- 项目级 .npmrc 设置
ignore-scripts=true——仓库中该文件内容已验证,正是这一行。贡献者/维护者 checkout 中的npm install/npm ci默认跳过一切直接与传递依赖的 lifecycle script(preinstall、install、postinstall、prepare); - axios 自身声明的唯一
preparehook 是husky(只写.git/hooks/),因ignore-scripts=true必须手动执行(npm rebuild husky && npx husky,记录于 CONTRIBUTING.md 关联的 README "Contributing / Local setup" 章节)。
工作站侧差距:ignore-scripts 中和的是 lifecycle script 路径,不是构建时代码执行——恶意的 Rollup / Babel / Terser / ESLint / Vitest 插件在维护者执行 npm run build / npm test / npm run lint 时照样运行,因为它们不是 lifecycle script,而是维护者显式调用的工具。lockfile 钉住的包若在被钉时就已是恶意的、或维护者 npm update 引入新包,新的 lifecycle script 仍可落地。一旦构建工具运行,开发环境对维护者可读的每个凭据都有完整读权限——隔离(devcontainer / VM)仍是最强控制(仓库中确有 .devcontainer 目录)。
文档给出的六条缓解/建议措施:
- 工作站上不留可发布的 npm token。发布只经 GitHub Actions OIDC,没有任何工作流要求从笔记本
npm publish;若~/.npmrc有 token,应为只读或限定于无关包。没有可偷的东西,攻击路径即失效。 - 本地
npm install/npm ci加--ignore-scripts(已采用:项目.npmrc)。git hooks 手动执行一次可信脚本即可:npm rebuild husky && npx husky。"手动执行少量已知步骤的小不便,是拒绝运行数千个未知步骤的代价。" - 在隔离环境中开发:devcontainer / VM / 沙箱,不挂载
~/.ssh/(推送时用独立 deploy key 或 SSH agent 转发)、不含可发布 token 的~/.npmrc、不含repo作用域 token 的~/.config/gh/、不含~/.aws/、~/.config/gcloud/等。开发环境只需能读写仓库工作树并联网跑测试。 - GitHub 使用硬件密钥(已全项目采用):FIDO2/WebAuthn 认证 +
sk-ssh-ed25519@openssh.compush。偷走~/.ssh/id_ed25519_sk文件没用,物理密钥不在手里无法用——把"偷文件"升级为"偷文件加偷物理对象"。每位维护者应保留分离存放的备用密钥。 - 对依赖更新 PR 的 lockfile diff 做与代码同等仔细的审计。4000 行
package-lock.jsondiff 能藏很多东西;工具如npm diff、lockfile-lint(仓库中确有 .github/workflows/lockfile-lint.yml);特别注意带安装脚本的新包(lockfile 中hasInstallScript: true)。 - 不要随手加 dev 依赖——每一个都是委托给陌生人的周期性信任决策;优先可用
npx按需运行(不落地node_modules)的工具。
3.7 威胁 T-S3:钓鱼至维护者账号接管
典型剧本:仿冒 npm 安全告警链接假登录页(实时重放密码+TOTP)、仿冒 GitHub OAuth 同意屏骗 repo 作用域、"招聘方"诱导 clone 并 npm install 一份"作业"仓库。文档强调:axios 维护者的 npm 与 GitHub 凭据过去已被此类行动专门针对,这不是理论推演。
缓解:npm 发布强制 2FA;OIDC 发布使正常发布完全不涉及维护者 npm 会话,攻击面收窄到 GitHub;全部维护者用硬件 WebAuthn/passkey 认证——origin-bound 凭据无法被 Evilginx/Modlishka 类钓鱼代理重放,TOTP 单独不允许用于维护者账号;git push 用硬件驻留的 sk-ssh-ed25519@openssh.com 键。
差距:这是账号级策略,无法从仓库本身验证,onboarding/offboarding 清单应确认硬件密钥状态;每位维护者应注册至少 2 把硬件密钥(主+备份分离存放),避免锁定后被迫回退到更弱的恢复方式。
3.8 威胁 T-S4:被攻陷的运行时依赖
follow-redirects、form-data、proxy-from-env、https-proxy-agent 之一发布恶意版本——与 T-S2 不同,这些代码会进入已发布 bundle / 运行时,每个 axios 消费者都在跑它。可能性"低"(仅 4 个依赖,成熟、窄域、被紧盯),影响"Critical"。
缓解:运行时依赖总共 3 个(package.json 层面按最小化设计);^ 版本范围意味着消费者可能拿到比 lockfile 更新的补丁版——有意为之(消费者获得安全修复),但同时也意味着 follow-redirects 的恶意补丁发布可不经 axios 发版就传播;follow-redirects 安全敏感且维护良好,历史上多次 axios 发版只是它的版本 bump;Dependabot 已配置(.github/dependabot.yml)覆盖 npm 与 GitHub Actions,每周分组更新,7 天冷却期保留,除非关键漏洞需要维护者手动更新。
差距:未考虑 vendor/内联这些依赖——它们小到可行,但会放弃上游安全修复,当前判断不值得。
3.9 威胁 T-S5:构建产物篡改(dist/ ≠ lib/)
发布的 tarball 含一个与 rollup 从 lib/ 产物不匹配的 dist/axios.min.js——没人读 minified bundle,这里的后门对源码审查完全不可见。向量:恶意 dev 依赖的 Rollup/Babel/Terser 插件在构建时注入代码(T-S2 作用于 CI),或被攻陷工作站的维护者误发布篡改的本地构建。
缓解:构建只在 CI 的 publish.yml 中、从干净的 npm ci --ignore-scripts checkout 运行,不存在"从笔记本发布"路径;npm provenance(--provenance)用密码学证明"哪个 workflow 在哪个 commit 上产出了这个 tarball",消费者可用 npm audit signatures 验证——但文档诚实地区分:这证明构建可在 GitHub Actions 上溯源到已知 SHA,不证明构建是正确的,只证明它可追溯。
差距:构建目前不是严格意义上可复现的——第三方无法独立重建出字节一致的 dist/(时间戳、插件顺序、minifier 非确定性需要锁定)。仓库中的 .github/workflows/verify-build-reproducibility.yml 对触及构建路径(lib/**、rollup.config.js、package.json、package-lock.json 及 workflow 自身)的 PR 做两遍构建并 diff,当前为非阻塞(continue-on-error: true),把分歧暴露在 CI 摘要里让可复现性回归可见,而不阻塞合并;待分歧消除后移除 continue-on-error 升级为硬门禁。
3.10 威胁 T-S6 / T-S7 / T-S8
T-S6:workflow 文件篡改——有写权限的攻击者(或审查不严的合并 PR)改 publish.yml,让构建后的步骤去 curl OIDC token 或 patch dist/。缓解:全部 action 钉在完整 commit SHA 而非 tag(actions/checkout@de0fac... 而非 @v6);permissions 最小化(contents: read、id-token: write);checkout 加 persist-credentials: false 使构建步骤无法回推仓库;zizmor 在每次 PR 与 v1.x push 上 lint workflow,结果经 security-events: write 权限以 code-scanning alert 呈现——该 job 必须留在 v1.x 分支保护的 required-checks 集合中,缓解才有约束力;npm-publish GitHub Environment 可要求指定审查人;CODEOWNERS 对 /.github/workflows/ 与 /.github/CODEOWNERS 本身有路径作用域规则。差距:单人维护者无法构成两人评审闭环。
T-S7:tag 混淆/重放——有写权限者 force-push 既有 tag 指向恶意 commit,或推 v1.99.99 带外发布。缓解:npm 拒绝重发已存在版本,重打 tag 无法覆盖已发布的 1.15.0;provenance 证明记录了 tag 发布时指向的 commit SHA,可事后取证;仓库设置必须禁止 v1.*.* 模式的 tag 删除与 force-push(GitHub UI 设置,经 Rulesets REST API 可审计)。差距:恶意新版本仍可被任何有 tag-push 权限者发布——最终收敛回 T-S3(账号安全)。
T-S8:typosquatting / 依赖混淆——axois、axios-http、@axios/core 之类。可能性高(这些包已真实存在),影响"中",但大体越界:axios 项目无法治理 npm 命名空间。npm 有发布期 typosquat 检测(不完美);@axios/ npm scope 并不归项目所有,任何人可注册 @axios/anything——文档明确标注这是差距而非缓解;provenance 给消费者提供了验证"拿到的是正品"的途径。
3.11 项目风险姿态总结
| 威胁 | 可能性 | 影响 | 当前姿态 | 优先级差距 |
|---|---|---|---|---|
| T-S1 恶意 PR | 高 | Critical | Good | 第二位维护者,激活 scoped 路径的两人评审 |
| T-S2 dev 依赖窃密钥 | 中 | Critical | Partial | 隔离开发环境;工作站不留发布 token。lifecycle script 已被项目 .npmrc 阻断,但构建工具插件仍在执行 |
| T-S3 钓鱼 | 高 | Critical | Good | 记录钓鱼响应手册;强制注册备份硬件密钥 |
| T-S4 运行时依赖攻陷 | 低 | Critical | Good | — |
| T-S5 构建篡改 | 低 | Critical | Adequate | 消除构建非确定性后,把可复现性检查升级为阻塞 |
| T-S6 workflow 篡改 | 低 | Critical | Good | 为 /.github/workflows/ 引入第二位维护者 |
| T-S7 tag 重放 | 低 | High | Good | — |
| T-S8 typosquat | 高 | Medium | 越界 | — |
文档的总结论:剩余的最高投资项是 T-S2(dev 依赖攻陷维护者工作站)。lifecycle script 执行已被项目级 .npmrc 阻断,T-S3 钓鱼风险在全部维护者迁移到硬件 WebAuthn 后实质下降——实时凭据重放不再可行。T-S2 的残余差距是构建工具插件执行(Rollup/Babel/Vitest/ESLint),ignore-scripts 覆盖不到;关闭它需要"在无法访问长寿命凭据的隔离环境中运行构建"。
4. 事件响应手册(Incident Response Runbook)
文档 §3.7 给出一份按时间窗组织的 runbook,前提是"速度比完整性重要——一个已发布的恶意版本影响每一个下游消费者"。触发条件:点击钓鱼链接、丢失硬件密钥、出现意料外的 tag/发布、token 出现在日志里。
第 1 阶段:遏制(第 0–15 分钟)
- GitHub:吊销所有活跃 session,吊销全部 OAuth/PAT token,审查并移除不认识的授权 SSH 键。若曾存在
repo或admin:org作用域的 PAT,假设已泄露; - npm:
npm token list+npm token revoke <token>处理一切可发布 token;无 CLI 访问时走 npm 设置页的 tokens 面板;轮换 npm 密码并强制登出所有会话; - 工作站:若 build/install 执行过恶意代码,假设笔记本被全用户级攻陷,断开受信网络,不依赖杀毒软件,换干净机器做后续轮换。
第 2 阶段:评估(第 15–60 分钟)
- 查看 GitHub 安全日志(仓库与每个维护者个人账号)中不认识的事件:密钥添加、组织变更、force-push、新 tag;
- 核对近期 tag 是否符合意图:
git log --tags --oneline -n 20,与 npm 上 axios 的 versions 列表对照; - 对每次近期发布验证 provenance:
npm audit signatures axios@<version>,把 attestation 中的sourceCommit与 tag 的 SHA 交叉核对——分歧即需调查(这也是 SECURITY.md 记录的验证方法); - 检查
~/.npmrc、~/.ssh/、~/.config/gh/hosts.yml、~/.gnupg/的篡改与陌生文件。
第 3 阶段:轮换(第 1–4 小时)
- 在干净硬件上生成新 SSH 键并从 GitHub 移除旧键;使用
sk-ssh-ed25519@openssh.com时先注册新硬件密钥再注销旧的,绝不把账号置于零注册密钥状态; - 重新注册 WebAuthn 认证器(主+备份),注销丢失/被攻陷的认证器;
- 若使用签名提交则轮换 GPG 键并上传 GitHub;
- 轮换一切云凭据与被攻陷机器上存在的所有 token。
第 4 阶段:通报(第 1 小时起)
- npm 安全团队:
security@npmjs.com,附包名、嫌疑版本、时间线; - GitHub 安全团队:走官方联系入口的 Security 类别,请求对账号展开调查;
- 下游:确认恶意版本已发布的瞬间就开 GitHub security advisory,不要等修复——用户需要立刻 pin 离恶意版本;
- co-maintainers(若存在):经带外渠道(电话/Signal)通知,不走被攻陷的渠道。
第 5 阶段:unpublish / deprecate(第 1–24 小时)
- npm 允许发布后 72 小时内
npm unpublish <pkg>@<version>;过期后npm deprecate <pkg>@<version> "<reason>",消息指向 advisory; - 发布一个 semver 高于恶意版本的修复版,使
^消费者自动向前移动。
第 6 阶段:复盘(1 周内)
- 写清时间线:初始向量、驻留时间、影响范围、已施加的缓解;
- 若事件暴露了本模型未覆盖的差距,更新威胁模型本身;
- 若某条缓解可固化(新 CI 检查、新 lint 规则、新 CODEOWNERS 路径),提 PR。
文档以一句警告收尾:"没有演练过的手册是文档,不是控制——请保持它现行有效。"
5. 如何核查本文引用的事实
本文所有源码级结论均可在仓库内直接验证,建议的核查入口:
- 原型链污染防御:lib/utils.js(危险键过滤)、lib/core/mergeConfig.js(
hasOwnProp/mergeDirectKeys)、lib/defaults/index.js(own()包装)、lib/adapters/http.js(transport/agent 读取)、lib/helpers/resolveConfig.js(withXSRFToken严格布尔检查); - gadget 回归测试:tests/unit/prototypePollution.test.js;
- 限制与序列化:lib/adapters/http.js(
maxContentLength/maxBodyLength分块强制)、lib/helpers/toFormData.js(DEFAULT_FORM_DATA_MAX_DEPTH = 100); - URL 安全:lib/core/buildFullPath.js(
allowAbsoluteUrls与错误消息 URL 脱敏)、lib/helpers/isURLSameOrigin.js、lib/helpers/normalizeURLForProtocolCheck.js; - 供应链控制:.npmrc(
ignore-scripts=true)、.github/workflows/publish.yml、.github/workflows/zizmor.yml、.github/workflows/verify-build-reproducibility.yml、.github/workflows/lockfile-lint.yml、.github/CODEOWNERS、.github/dependabot.yml; - 安全公告渠道:SECURITY.md。
适用前提与限制:本文基于当前仓库快照(axios v1.x 分支,THREATMODEL.md 约 512 行);文中评级(可能性/影响)均针对"典型服务端部署",浏览器部署因继承同源策略,SSRF 与凭据泄露风险整体更低;文档本身声明"描述的是意图与当前理解,不构成安全保证"——发现模型缺口应按 SECURITY.md 的私有公告渠道报告,而非开公开 issue。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00