Qwen Code 语音私有网关白名单:security.allowedInsecureVoiceBaseUrls 配置与网络策略解析
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
导读
Qwen Code 的语音转写(ASR)功能默认拒绝非回环 HTTP 端点与解析到私网地址的端点,这是安全默认值,但也让托管部署无法把语音流量路由到隔离的私有网关。本文围绕设计文档 trusted-private-voice-base-urls.md,系统讲解新增的security.allowedInsecureVoiceBaseUrls信任配置:如何精确匹配放行私有/明文语音端点、哪些地址永远无法放行、CLI 与桌面端两条解析链路的差异、以及失败回退与验证清单。读完你将掌握在受控内网中安全配置私有语音网关、并理解其安全边界与回滚行为的完整方案。
问题背景:语音流量的安全默认值与私有网关冲突
语音转写模块在出站前会对语音服务端点做两层安全检查:
- 拒绝非回环(non-loopback)HTTP 端点:明文传输语音音频不被允许;
- 拒绝解析到私网地址的端点:RFC 1918 私网、CGNAT、IPv6 unique-local 等地址默认不可达。
这两项检查是防 SSRF 与防音频明文泄露的安全默认值。但对托管(managed)部署而言,ASR 流量往往需要经过一个隔离的私有网关,网关地址因部署而异——按厂商或地区维护主机名列表无法扩展。文档明确指出,问题不是去掉检查,而是为受信端点提供一条精确、可审计的放行通道。
设计文档将其实现为 Issue [#8286] 的落地内容,当前仓库中的实现事实如下:
- CLI 侧检查逻辑位于 voice-transcriber.ts:
isInsecureVoiceBaseUrlAllowed读取合并后的security.allowedInsecureVoiceBaseUrls并做精确匹配; - 设置项 Schema 定义于 settingsSchema.ts,默认值为空数组
[]。
核心设计:精确匹配的白名单,而不是通配放行
新增配置项security.allowedInsecureVoiceBaseUrls是一个默认空的完整 base URL 列表,其匹配规则非常严格:
- 必须显式携带 scheme:每条目必须包含
http://或https://; - 必须携带完整 provider 路径:仅当配置的语音 provider 的规范化 base URL 与列表条目完全相等(包括 scheme、host、port、path)时才放行;
- 仅做 URL 序列化与尾部斜杠的规范化:
new URL(...).toString()后去掉尾部斜杠(见 voice-transcriber.ts 的normalizeBaseUrl与normalizeAllowedVoiceBaseUrl); - 不推断缺失信息:缺失 scheme 或
/v1路径段不会被自动补全——自定义或区域网关必须写出完整地址; - 不支持通配符与主机名后缀匹配:
*.example.com这类写法无效。
示例(内网私有网关):
{ "security": { "allowedInsecureVoiceBaseUrls": [ "http://10.0.0.12:8080/asr/v1" ] } }/v1推断的保留与两个表面的差异
文档特别强调了一条容易踩坑的规则:预置的/v1推断只保留给官方 DashScope 兼容模式端点(compatible-mode),且仅用于 provider 条目;桌面端 OAuth 派生与环境变量派生的 base URL 在匹配前仍走同一套旧推断逻辑。而CLI 语音解析器完全不做/v1推断。
这意味着:如果希望在 CLI 与桌面端解析出相同的 DashScope provider 条目,就必须在baseUrl中显式写出/v1后缀;否则 CLI 解析出的是/v1之前的 URL,而桌面端会补上/v1,两端的白名单条目必须各自匹配各自解析后的 URL。也就是说,同一 provider 在不同表面上可能是两条不同的白名单条目。
精确匹配结果随解析配置一起传递
匹配结果(allow / deny)与解析出的语音配置一起传递,因此所有出站路径应用同一个决策:
- CLI 批量转写
- CLI 与 daemon 流式转写
- 桌面端批量与流式转写
放行后,允许明文传输与 RFC 1918、CGNAT、IPv6 unique-local 地址;但回环别名(loopback aliases)、未指定地址(unspecified)、链路本地(link-local)范围与已知云元数据地址仍然被阻止。显式 localhost 行为保持不变。
流式传输的 WebSocket 派生路径与白名单的关系
流式传输的 WebSocket URL 由解析后的 base URL派生而来,而不是直接使用 base URL。实现位于 voice-stream-session.ts:
export function deriveWebSocketBase(baseUrl: string): string { const url = new URL(baseUrl); const wsScheme = url.protocol === 'https:' ? 'wss:' : 'ws:'; let prefix = url.pathname.replace(/\/+$/, ''); if (prefix.endsWith('/compatible-mode/v1')) { prefix = prefix.slice(0, -'/compatible-mode/v1'.length); } else if (prefix.endsWith('/v1')) { prefix = prefix.slice(0, -'/v1'.length); } return `${wsScheme}//${url.host}${prefix}`; } export function deriveStreamUrl(baseUrl: string): string { return `${deriveWebSocketBase(baseUrl)}/api-ws/v1/inference`; }即:deriveWebSocketBase去掉末尾的/v1或/compatible-mode/v1,再追加/api-ws/v1/inference(实时通道为/api-ws/v1/realtime)。因此:
- 精确匹配保证覆盖的是 provider 端点;
- 批量请求路径原样使用该 base URL;
- 流式传输的线上路径(wire path)是从base URL 派生而来,不参与白名单匹配——派生后的 WebSocket 地址可以合法地与白名单条目不同。
这是设计上的有意选择:白名单放行的是“信任这个 provider 端点”,而不是把放行范围扩展到所有派生出的线上地址。
配置归属:受信任作用域与 Workspace 忽略
security.allowedInsecureVoiceBaseUrls属于受信任配置(trusted configuration):
- 允许的作用域:User、System、SystemDefaults;
- 忽略的作用域:Workspace 值被忽略并产生设置警告。
其目的很明确:防止克隆的仓库自我授权访问不安全或私有端点。源码侧由统一的“工作区受限设置”清单驱动,见 settingsUtils.ts 中的WORKSPACE_RESTRICTED_SETTINGS({ section: 'security', key: 'allowedInsecureVoiceBaseUrls' }),该清单同时驱动 Workspace 值剥离、忽略警告与设置对话框作用域过滤,三个表面不会漂移。
另外两点需要特别留意:
- 设置值在匹配前会经过环境变量插值——任何能控制进程环境的东西都可以提供插值后的白名单条目或 provider
baseUrl。因此进程环境本身应被视为受信任配置面的一部分; - 明文 HTTP 会把 provider API key(位于 Authorization 头中)暴露在网络上,Schema 描述中对此有明确警告(见 settingsSchema.ts)。
配置归属原则
谁部署网关,谁负责白名单条目。托管部署应从同一份声明式端点值同时渲染 providerbaseUrl与白名单条目。新增一个区域不需要任何 Qwen Code 改动,也不会漂移成主机名级别的宽泛放行。
DNS 信任边界:白名单中的主机名,其可信度等同于其 DNS。后续 DNS 记录变更会把放行(连同 provider 凭据)重定向到新解析的地址。当网关地址稳定时,优先使用 IP 字面量条目。
桌面端的额外解析逻辑:凭据绑定与歧义处理
桌面端语音解析与 CLI 走不同的路径:桌面端直接读取受信任设置并跨所有 provider 组扫描条目(协议无关),因为它没有 CLI 的模型注册表。这带来一个窄范围的可见性差异:
- CLI 通过模型注册表解析语音模型,自定义 provider 组的条目仅在组 id 能解析出协议时可见(内置组 id 或
providerProtocol映射); - 桌面端会解析出 CLI 过滤掉的条目——例如非 OpenAI 协议组(如
gemini)下的语音条目、imageOnly条目、qwen-oauth组——同时扩大歧义检查范围:任一被扫描组中存在同 ID 不同 baseUrl 的条目,都会在桌面端判定模型歧义,即使该重复条目位于 CLI 永远看不到的组中、且 CLI 能正常解析;而所有保持旧回退(public HTTPS、未白名单)的重复项在两个表面都不会失败。
核心不变量:每个解析路径在两个表面上都经过网络策略检查;这些分歧只改变“哪些条目被解析”,绝不改变“对解析出的条目施加哪些检查”。
凭据选择规则
桌面端只有在条目需要网络策略决策时才把 ID 与所选语音模型完全匹配的 provider 视为权威——即其 base URL 被白名单、是明文 HTTP、私网地址或回环。这些条目在 OAuth 凭据之前解析,因此托管网关对已 OAuth 登录用户生效;同时它们在以下情况fail closed,防止意外回退到别的 provider 或区域:
- 重复匹配(ambiguous duplicates)
- 不支持的 scheme
- 始终被阻止的地址
- 缺少白名单匹配
- 未解析的
envKey
Public HTTPS 条目保持旧回退链(OAuth → 共享 DashScope provider → 环境凭据),保留既有安装的凭据优先级;无法分类的条目(缺失或不可解析的 base URL)同样走回退。无envKey的条目在无 API key 的情况下解析——这与 CLI 对无 key 本地/私有网关的处理一致,便于http://localhost这类本地 ASR 服务免凭据使用。
同 ID 条目的歧义规则与 CLI 模型注册表一致:除非是精确的(id, baseUrl)重复(首个注册的条目胜出,envKey不参与复合键,因此 envKey 不同也保留首个注册),或所有匹配条目都不需要网络策略决策(整个集合保持旧回退,如单一 public HTTPS 条目),否则视为歧义。这防止了无关模型或区域提供端点与 API key。
失败与回滚行为:fail closed 与 DNS 回环封堵
畸形条目与非匹配一律 fail closed(拒绝出站)。删除条目后,设置重载或进程重启即恢复既有的 HTTPS/public-network 要求——回滚简单直接。
两个相对旧守卫的行为变更
- CLI:带内嵌凭据的语音模型
baseUrl(如https://user:pass@host/...)直接拒绝,而不是剥离凭据后继续——userinfo 可能让 URL 解析器解析到攻击者控制的主机。实现见 voice-transcriber.ts(normalizeBaseUrl对url.username || url.password抛错)。 - 桌面端:IPv4-mapped IPv6 字面量(如
::ffff:127.0.0.1)按其内嵌的 IPv4 地址分类,不再绕过回环封堵;应改用显式回环拼写。
回环 DNS 与本地端点
解析到回环地址的主机名始终被阻止(无论是否有白名单条目)——例如asr.localtest.me或指向本地 ASR 服务器的/etc/hosts别名;CLI 此前会放行这类 DNS 结果。要访问本地端点,必须配置显式回环 baseUrl:
http://localhost http://127.0.0.1 http://[::1]这些拼写仍然允许。
验证清单:文档定义的验收标准
设计文档 trusted-private-voice-base-urls.md 的 Verification 一节给出完整验收标准,可直接作为回归测试依据:
- 保持对非 localhost HTTP 与私网端点的默认拒绝;
- CLI 与桌面端都要求白名单条目包含显式 scheme 与完整 provider 路径;
- 仅当所选 URL 与条目精确匹配时,接受两个互不相关的区域私有网关 URL;
- 拒绝 scheme、port、host 或 path 不匹配;
- 即使精确列出,也拒绝非 HTTP(S) scheme;
- 忽略并警告 Workspace 作用域条目;
- 精确匹配后仍拒绝 link-local 与云元数据地址(含 AWS IMDS IPv6
fd00:ec2::254); - 一致地解码 IPv4-mapped、IPv4-compatible 与 well-known-prefix NAT64(
64:ff9b::/96)IPv6 字面量,使受信私网地址被接受、内嵌回环与元数据地址仍被阻止; - 在受信路径与默认拒绝路径上均拒绝 local-use NAT64、IETF 协议分配/Teredo 与 6to4 过渡前缀;
- 将桌面端凭据与所选语音模型 ID 匹配到唯一无歧义的 provider;
- 覆盖 CLI 与桌面端两条解析路径及 DNS 守卫路径。
仓库中的对应实现同样可验证这些约束:voice-transcriber.ts 的精确匹配、voice-stream-session.ts 的 WebSocket 派生、settingsSchema.ts 的 Schema 定义,以及 settingsUtils.ts 的受信作用域清单。
实践要点速查
| 场景 | 结论 |
|---|---|
| 内网私有网关放行 | 在security.allowedInsecureVoiceBaseUrls写入完整、带 scheme、带路径的精确 URL |
| 通配符/后缀匹配 | 不支持,必须逐条精确列出 |
| DashScope provider 跨表面一致 | 显式携带/v1后缀;否则 CLI 与桌面端各匹配各的 URL |
| Workspace 作用域 | 被忽略并告警,克隆仓库无法自我授权 |
| 明文 HTTP | 允许但会暴露 Authorization 头中的 API key,仅限受信托管端点 |
| 回环访问本地 ASR | 用http://localhost、http://127.0.0.1、http://[::1] |
| 回滚 | 删除条目,重载/重启即恢复 HTTPS/public-network 要求 |
| 始终阻止 | 元数据(含 AWS IMDS IPv6)、link-local、local-use NAT64、Teredo、6to4、解析到回环的主机名 |
配置该选项只影响出站网络策略检查,不会改变语音转写本身的行为;它在 设置 Schema 中归类于security类别,requiresRestart: false,修改后无需重启即可生效。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考