首页
/ axios 预发布版本变更深度解析:NO_PROXY CIDR 匹配、HttpStatusCode 新命名与运行时配置加固

axios 预发布版本变更深度解析:NO_PROXY CIDR 匹配、HttpStatusCode 新命名与运行时配置加固

2026-09-05 21:10:54作者:余洋婵Anita

本文基于当前仓库中的 PRE_RELEASE_CHANGELOG.md 展开,逐条解读该预发布版本中的 2 项新特性、7 项 Bug 修复与文档改进,并结合 lib/helpers/shouldBypassProxy.jslib/adapters/fetch.jslib/core/InterceptorManager.js 等源码实现,帮助读者理解这些变更的设计动机、行为边界与升级注意事项。

变更总览

当前 PRE_RELEASE_CHANGELOG.md 的 Unreleased 段落包含三部分:

类别 数量 涉及能力
Features 2 NO_PROXY/no_proxy CIDR 匹配、RFC 9110 状态码新命名
Bug Fixes 7 运行时配置加固、Fetch 适配器一致性、HTTP/2 适配器一致性、Data URI 校验、keep-alive 内存留存、XHR 导航取消、请求错误栈、拦截器存储(共 8 条修复项)
Documentation 1 文档站浏览器内全文搜索

其中多数修复项带有 PR/Issue 编号(如 #11082、#11091、#11094、#11109、#11070),说明这些变更都经过了完整的缺陷报告—修复流程。下文按"新特性 → 适配器一致性修复 → 运行时安全加固 → 文档改进"的顺序逐条解析。

新特性一:NO_PROXY / no_proxy 支持 CIDR 网段匹配

这是本预发布版本最核心的新特性:NO_PROXY/no_proxy 环境变量新增 IPv4 与 IPv6 的 CIDR 网段匹配能力,包括带方括号的 IPv6 地址、IPv4-mapped IPv6 归一化,且格式错误的网段条目采用 fail-closed(拒绝匹配)策略。特别地,/0 前缀条目会对其整个地址族(AF 家族)内的所有地址生效,即 0.0.0.0/0::/0 会绕过该协议族的全部代理。

实现位置:lib/helpers/shouldBypassProxy.js

该能力完整落在 lib/helpers/shouldBypassProxy.js 中,主入口为 shouldBypassProxy(location)。结合源码可以看到几条关键设计:

  1. 环境变量读取与通配符:函数先读取 process.env.no_proxyprocess.env.NO_PROXY 并转小写(见 shouldBypassProxy.js#L447);整体为 * 时对所有请求放行,条目级 * 同样放行。
  2. CIDR 条目解析parseCidrEntry 通过正则 ^(.+)\/(0|[1-9]\d{0,2})$ 识别形如 192.168.0.0/16[fd00::]/64 的条目(见 shouldBypassProxy.js#L377-L414)。前缀为 0 的条目对应 changelog 中"/0 条目绕过整个地址族"的语义;IPv4-mapped IPv6 基址若前缀小于 96 会被拒绝,否则按 prefix -= 96 折算到 IPv4 侧比较。
  3. 地址归一化normalizeIPAddress 会把点分十进制的简写、八进制、十六进制形式(如 127.0x00ff127.65535)规范化为规范点分十进制;unmapIPv4MappedIPv6 则把 ::ffff:192.168.1.5 这类映射地址还原为 192.168.1.5(见 shouldBypassProxy.js#L236-L260)。这正是 changelog 中"bracketed IPv6 and IPv4-mapped IPv6 normalization"的落地:不做归一化的话,NO_PROXY=192.168.1.5 将无法匹配请求 http://[::ffff:192.168.1.5]/,等于给代理绕过策略开了后门。
  4. 子网判定isInSubnet 按整字节 + 剩余位掩码逐位比较(见 shouldBypassProxy.js#L416-L436),支持任意前缀长度。
  5. Fail-closed 策略:源码注释多处强调"fall through to non-bypass",即解析失败(空八位组、非法字符、前缀越界、非规范括号等)一律不匹配、继续走代理。这与 changelog 中"malformed ranges fail closed"的表述一一对应——安全相关的匹配逻辑宁可漏配也不误配。

使用示例

配置示例(Node 环境):

# 整个内网网段绕过代理
export NO_PROXY="192.168.0.0/16,10.0.0.0/8"

# IPv6 网段(方括号写法)
export NO_PROXY="[fd00:1234::]/48"

# 0.0.0.0/0 绕过所有 IPv4 流量(/0 对整个地址族生效)
export NO_PROXY="0.0.0.0/0"

除 CIDR 外,原有的主机名后缀(.example.com)、通配(*example.com)、精确主机与端口(host:8080)以及方括号 IPv6 端口写法 [::1]:8443 等匹配规则依然保留(见 shouldBypassProxy.js#L483-L504),对应测试文件为 tests/unit/helpers/shouldBypassProxy.test.js

新特性二:HttpStatusCode 引入 RFC 9110 新命名

changelog 说明:为 413 与 422 增加 RFC 9110 官方名称 HttpStatusCode.ContentTooLargeHttpStatusCode.UnprocessableContent,覆盖运行时 API 与 ESM/CommonJS 类型声明;旧名 PayloadTooLargeUnprocessableEntity 保留为弃用别名;数字反查(HttpStatusCode[413])仍返回 v1.x 的既有名称以保持兼容(关联 #11082,关闭 #11066)。

对照 lib/helpers/HttpStatusCode.js 可直接验证:

/**
 * @deprecated Use `ContentTooLarge` instead.
 */
PayloadTooLarge: 413,
ContentTooLarge: 413,
// ...
/**
 * @deprecated Use `UnprocessableContent` instead.
 */
UnprocessableEntity: 422,
UnprocessableContent: 422,

(见 HttpStatusCode.js#L38-L53

文件末尾的反查表构建逻辑解释了"数字反查保留旧名"的实现原理:

Object.entries(HttpStatusCode).forEach(([key, value]) => {
  if (HttpStatusCode[value] === undefined) {
    HttpStatusCode[value] = key;
  }
});

(见 HttpStatusCode.js#L82-L86)由于 Object.entries 保持声明顺序,413 先被 PayloadTooLarge 占用,后声明的 ContentTooLarge 不会覆盖它;422 同理仍反查到 UnprocessableEntity。这意味着按名字取值可以用新名称,按数字反查字符串的输出在 v1.x 中保持字节级一致,不会破坏依赖 HttpStatusCode[response.status] 做日志/告警分类的下游代码。相关测试见 tests/unit/helpers/HttpStatusCode.test.js

Bug 修复一:运行时配置加固(防原型链污染)

这是本批次中篇幅最长、影响面最广的一条修复:阻止那些"仅从共享原型继承"的值(包括来自另一个 JavaScript realm 的 Object.prototype、甚至其 constructor 被篡改过的情形)在配置合并或拦截器替换之后变成请求行为。被保护的字段包括 methods、headers、adapters、transports、FormData hooks、序列化器选项以及 Fetch 的 Request 选项。changelog 进一步给出了细粒度策略:

  • 已经"安全可写"的合并配置在 dispatch 过程中保持自身对象身份不变;
  • Object.freeze/Object.seal、基于访问器(accessor)受限、携带其他受限属性、或带"不安全键"的替换配置,会被转换为可写的过滤快照(writable filtered snapshot)
  • 应用自定义原型(非终端)的拦截器替换,以"归一化后的 null 原型快照"继续受支持;终端 null 原型祖先被视为共享边界,按 fail-closed 处理;
  • 物化(materialized)配置中,自身的 __proto__constructorprototype 键始终被排除。

从源码结构看,这一批加固与 lib/utils.js 中大量 hasOwnProp(仅识别自身属性)的访问模式、lib/core/mergeConfig.js 的合并语义、以及 lib/adapters/fetch.js 中对 config 取值时普遍使用 own('auth') 这类自身属性读取的写法一致(见 fetch.js#L226):整条链路都在刻意区分"配置对象自身的属性"与"从原型链继承的属性",后者在跨 realm(如 vm 沙箱、iframe、Worker)场景下可能是攻击者注入行为的路径。该修复的核心价值在于:即使攻击者能替换 config 或其拦截器传入的对象的 Object.prototype,也无法借此改变请求方法、注入请求头、替换适配器或改写序列化行为。

Bug 修复二:Fetch 适配器与自定义 fetch 实现的参数一致性

这条修复保证自定义 fetch 实现(通过 config.fetchOptions / 环境注入的 fetch)拿到的是"完整解析后的 Request + 一个安全的第二参数 fetchOptions"。行为要点:

  • 第二参数保留了调用方的自定义自有字段(custom own fields),但不会覆盖 Axios 管理下的请求字段;
  • 权威的 method、headers、body、signal、duplex、credentials 只存在于 Request 上,并从第二参数中剔除;
  • 不支持 Request 构造函数的降级路径(no-Request fallback)拿到同样的安全解析选项;
  • maxRedirects: 0 表示"手动处理重定向":Node 侧会暴露未被跟随的 3xx 响应;浏览器侧可能暴露 status 为 0、headers 不可访问的 opaque redirect。

lib/adapters/fetch.js 中的实现与 changelog 描述精确对应:

const safeFetchOptions =
  fetchOptions == null ? fetchOptions : Object.assign(Object.create(null), fetchOptions);

if (safeFetchOptions) {
  // These options are owned by Axios and are already reflected in the
  // resolved Request passed to fetch.
  delete safeFetchOptions.body;
  delete safeFetchOptions.headers;
  delete safeFetchOptions.method;
  delete safeFetchOptions.signal;
  delete safeFetchOptions.duplex;
  delete safeFetchOptions.credentials;
}

(见 fetch.js#L439-L451)随后:

if (maxRedirects === 0) {
  resolvedOptions.redirect = 'manual';
  if (safeFetchOptions) {
    safeFetchOptions.redirect = 'manual';
  }
}

request = isRequestSupported && new Request(url, resolvedOptions);

let response = await (isRequestSupported
  ? _fetch(request, safeFetchOptions)
  : _fetch(url, resolvedOptions));

(见 fetch.js#L478-L490

这里用 Object.create(null) 创建第二参数本身就值得注意:null 原型对象切断了原型链,避免用户传入的 options 对象经由原型携带 body/headers 等字段绕过上面的 delete 清理。对依赖自定义 fetch(如 undici 封装、mock、测试替身)的项目而言,本次修复后第二参数只可能携带你自己的字段,不会再出现"Axios 字段与 fetchOptions 字段谁说了算"的歧义。

Bug 修复三:HTTP/2 适配器一致性

针对 lib/adapters 目录下的 HTTP/2 支持(经由 http2 模块与 lib/helpers/Http2Sessions.js 的会话池),本批次修复了四类行为:

  1. 自定义 DNS lookup 生效:用户提供的 lookup 函数现在会作用于 HTTP/2 连接,且可以安全地复用池中会话;
  2. 显式代理对象的明确报错:直接传 proxy 对象配置 HTTP/2 请求会抛出 ERR_NOT_SUPPORTERR_NOT_SUPPORT 常量定义见 lib/core/AxiosError.js#L215,适配器层的使用示例见 lib/adapters/adapters.js#L110);
  3. 进程环境变量代理与 HTTP/1 agent 的 proxyEnv 被显式忽略:原因是 Node 的 http2.connect() 本身无法应用 HTTP/1 风格的代理机制;proxy: false 依然强制直连;
  4. 失败会话清理:会话失败后从池中移除,不再泄漏未处理的 session 错误事件。

第 2、3 点实际上是在"明确边界":与其让代理配置在 HTTP/2 路径下静默失效(产生难以排查的"为什么没走代理"问题),不如显式报错或文档化忽略,这对依赖 docs/pages/advanced/http2.mddocs/pages/advanced/adapters.md 的读者是有用的行为契约。

Bug 修复四:Data URI 媒体类型校验加固

changelog 指出:现在会拒绝携带多余斜杠分隔符的媒体类型,并避免对超长畸形媒体类型发生正则回溯放大(excessive backtracking)。Data URI 的解析入口在 lib/helpers/fromDataURI.js,fetch 适配器在处理 data: URL 时(如 maxContentLength 前置校验,见 fetch.js#L303-L316)会与该辅助模块协作。

这条修复的安全意义在于两点:其一,data:text//html/... 这类畸形 media type 不再被"宽松解析"为可执行的类型判定;其二,拒绝带量词陷阱的正则写法,避免攻击者用构造的超长 data: 字符串触发 ReDoS,导致解析请求的路径挂起。对应测试见 tests/unit/fromDataURI.test.jstests/unit/estimateDataURLDecodedBytes.test.js

Bug 修复五:Node HTTP 适配器 keep-alive 内存留存

changelog 描述:复用了一个模块作用域的 socket 错误监听器,使池化的 keep-alive socket 不再持有"第一个使用该 socket 的请求"的适配器上下文、响应体与缓冲区;socket 错误依旧只会销毁当前活跃请求(#11091)。

从源码结构看,lib/adapters/http.js 中 socket 错误处理采用模块级共享监听器(socket.on('error', handleSocketError),见 http.js#L1342),并通过当前请求的引用标记(kAxiosCurrentReq 相关注释,见 http.js#L66http.js#L1327 附近注释)区分 socket 归还到连接池后的错误归属。此前的实现会为每个 socket 挂一个捕获闭包上下文(含响应 body)的监听器,导致长驻 keep-alive 连接持续钉住旧请求的内存,表现为服务端内存随请求数缓慢爬升且 GC 无法回收。本次修复后,长连接池在高压场景下的内存占用将不再随历史请求累积,这对使用默认 http adapter 的 Node 服务是可感知的运维级改进。

Bug 修复六:XHR 适配器对"导航取消"请求的处理

这条修复针对浏览器 XHR 路径(lib/adapters/xhr.js):status 为 0 且到达 loadend 的响应,现在会以 ECONNABORTED 错误拒绝,而不是以空 body 解析为成功(#11094,关闭 #11093)。

changelog 给出的浏览器行为细节非常具体:Firefox 152 起,文档导航导致的请求取消不再触发 errorabort 事件,loadend 成为唯一执行的处理器,于是这类请求曾被 settle 为成功响应。修复后的行为对齐了 Firefox 151 时代 onabort 的报错语义,应用侧观察到的结果与浏览器更新前后保持一致。两个边界值得注意:

  • file: 协议豁免:某些环境下 file: 读取成功也报告 status 0,因此仍按成功解析——无论 scheme 出现在请求 URL 上、相对 URL 从 file: 页面来源继承,还是仅出现在 responseURL 中;
  • validateStatus 不适用:由于根本没有收到响应,该拒绝不受 validateStatus 抑制,这与 onerror/onabort 的既有行为一致。

对依赖 XHR adapter 的前端项目,这条修复消除了"页面跳转取消的旧请求竟返回成功"的隐患(例如全局响应拦截器里对空成功响应的误处理)。

Bug 修复七:请求错误栈保留 与 拦截器存储墓碑修剪

请求错误栈:当自定义 Error 栈插桩(stack instrumentation,如某些 APM/覆盖率工具会修改 Error.prepareStackTrace)返回非字符串或在可选的栈装饰过程中抛出时,原始请求失败信息得以保留而不会被二次错误吞没(#11109,关闭 #11108)。这使得"自定义错误栈工具 + axios"组合在异常路径下不再丢失根因。

拦截器存储lib/core/InterceptorManager.jseject 采用"墓碑(tombstone)"机制——删除时不直接移位,而是置 null 占位以维持 ID 与索引稳定:

this.handlers[entry.index] = null;

// Do not reuse an index while forEach is walking its length snapshot.
if (!internals.iterationDepth) {
  trimHandlers(this.handlers);
  internals.handlersLength = this.handlers.length;
}

(见 InterceptorManager.js#L101-L124)配合 trimHandlers(见 InterceptorManager.js#L14-L22),本次修复(#11070)确保迭代结束后尾部的连续墓碑会被弹出:反复"注册—注销"同一位置拦截器不再让 handlers 数组无限增长,同时保持对外可见的数组形状、迭代行为(forEach 跳过 null)与 ID 身份语义不变。这是一个典型的可观测性修复——修复前长期运行的进程里 instance.interceptors.request.handlers.length 会随业务增长虚高。

文档改进:文档站浏览器内全文搜索

changelog 的 Documentation 部分:为当前文档语言新增了本地化、私有、纯浏览器内的全文搜索,支持模糊匹配与前缀匹配(fuzzy and prefix matching)。

"private"(私有)是关键词:搜索索引与查询全部在用户浏览器内完成,不经过任何第三方搜索服务;"localized"则意味着 docs/zhdocs/esdocs/fr 各语言站点分别建立索引,不会出现跨语言混检。实现侧可对照 docs/package.json 中文档站依赖与 docs/scripts 下的构建脚本;功能体验入口即文档站站点内的搜索框。

升级与验证建议

  1. NO_PROXY 使用者:升级前先在预发环境验证 NO_PROXY 条目——尤其是 CIDR 网段与 IPv6 方括号写法,确认"应直连"的地址确实直连;畸形条目会 fail-closed(继续走代理),与部分工具的"宽松解析"习惯不同。
  2. 自定义 fetch 实现的项目:检查自定义 fetch 包装是否依赖从第二参数读取 method/headers/body 等 Axios 已管理的字段——升级后这些字段只存在于 Request 上。
  3. 长期运行的 Node 服务:关注 keep-alive 连接池的内存曲线,本次修复预期可消除历史请求对象无法回收的问题。
  4. 依赖 HttpStatusCode 数字反查的日志系统:无需变更,反查结果保持 v1.x 值;仅在新增代码中改用 ContentTooLarge/UnprocessableContent 命名即可。
  5. 验证途径:可运行 tests/unit 下的针对性测试(如 shouldBypassProxy.test.jsHttpStatusCode.test.jsInterceptorManager.test.js)与 tests/smoke 多运行时冒烟测试(cjs/esm/deno/bun)来确认本地构建行为与上述条目一致;测试整体说明见 tests/README.md

小结

本预发布批次的变更呈现出清晰的三条主线:网络语义的严谨化(CIDR 代理绕过、Data URI 校验、HTTP/2 代理边界)、多实现/多浏览器行为的一致性(fetch 双参数契约、XHR 导航取消、状态码命名)、以及运行时健壮性与内存卫生(原型链污染加固、keep-alive 留存、拦截器墓碑修剪、错误栈保留)。对使用者而言,除 NO_PROXY CIDR 与 maxRedirects: 0 两项是显式新增能力外,其余变更均为行为修复且向后兼容——数字反查的状态码名称、拦截器 ID 语义、validateStatus 交互等公开契约均保持不变,可以放心跟进升级。

登录后查看全文
热门项目推荐
相关项目推荐