将 DeepSeek Harness 接入华为内网:SnapEngine 网关 W3 鉴权逆向与本地代理桥接
背景:开源 Agent 框架在企业内网的认证断层
DeepSeek Harness(dsh)是一个基于 Cordis 插件框架的开源 Agent Harness,通过 adapter 层对接不同 LLM 提供商,对外仅支持标准 API Key + OpenAI 兼容协议——客户端向 /chat/completions 发送请求,携带 Authorization: Bearer <key> 头,即可获得流式响应。
华为内部的 LLM 推理服务由 SnapEngine 网关托管,该网关要求 W3/DevUC OAuth2 令牌认证。一个在公网环境下几行配置即可运行的 agent 框架,在企业内网中面对的却是:请求不携带企业认证头,网关直接返回 401;即使拿到令牌,令牌的加密方式、刷新机制、网关的必选请求头,全都不在标准 OpenAI 协议的范畴内。
核心矛盾在于:开源工具链只理解"API Key + Bearer Token",企业网关要求的是"SSO 令牌 + 企业标识头"。本文记录的是如何通过抓包逆向 SnapEngine 的鉴权协议,并用一个本地 HTTP 代理将两者桥接起来的过程。
相关工具与概念
SnapEngine 是华为内部的 LLM 推理网关,提供 OpenAI 兼容的 /chat/completions 端点,但要求请求携带 W3/DevUC OAuth2 令牌和一组企业标识头(如 kernel-version、app-id)。它托管了多个内部模型,如 Qwen3.6-27B-CodeAgent-VL 和 GLM-5.1-CodeAgent。
W3/DevUC OAuth2 是华为的企业单点登录体系。用户通过浏览器完成 W3 认证后,DevUC 颁发 access_token 和 refresh_token。token 有效期约 2 小时,过期后可通过 refresh_token 续期。
DPAPI(Data Protection API)是 Windows 的数据保护机制,按当前用户 profile 加解密数据。CodeAgent CLI 将 DevUC 的 access_token 和 refresh_token 以 DPAPI 加密后的 base64 形式存储在本地凭证文件中,确保只有同一 Windows 用户才能解密。
Chrome DevTools Network 面板是浏览器内置的抓包工具,可以观察页面发出的所有 HTTPS 请求的完整头、请求体和响应。在本场景中,它是逆向 SnapEngine 鉴权协议的主要工具。
DeepSeek Harness(dsh) 的 adapter 层(llm-deepseek)负责将 agent 请求序列化为 OpenAI 兼容的 wire body,通过 baseURL 配置项决定请求目标。这正是注入本地代理的切入点——只要将 baseURL 指向本地代理,agent 的请求就能被代理拦截、改写、转发。
抓包确定鉴权协议
逆向鉴权协议的第一步是观察一个合法请求长什么样。华为的 CodeAgent 桌面端已经能正常访问 SnapEngine,用 DevTools Network 面板抓取它发出的请求即可。
抓包步骤
- 打开 CodeAgent 网页端,按 F12 打开 DevTools,切换到 Network 面板
- 发送一条对话消息,在 Network 中找到发往
snapengine.cida.cce.prod-szv-y.dragon.tools.huawei.com的 POST 请求 - 右键该请求 → Copy → Copy as cURL,将完整请求导出
从抓包中提取的信息
通过复制为 cURL 并分析请求头,可以识别出以下关键信息:
上游端点:https://snapengine.cida.cce.prod-szv-y.dragon.tools.huawei.com/api/v2/chat/completions
认证头(必须):
x-auth-token: <DevUC access_token>
Authorization: Bearer <DevUC access_token>
两者都带上了同一个 token。SnapEngine 主要依据 x-auth-token 鉴权,Authorization 头是为了兼容某些内部中间件。
企业标识头:
kernel-version: 1.2605.04.01-win32-x64
plugin-version: 1.2605.04.01
app-id: com.huawei.devmind.codebot.apibot
x-snap-traceid: <随机 UUID>
timestamp: <毫秒时间戳>
这些头标识了调用方的客户端类型和请求追踪信息。
hash3 签名:原始请求中包含一个 hash3 头,初看像是某种 HMAC 签名。但实测用全零值(64 个 0)替代后请求仍然正常通过,说明该签名并非强制校验项,可以先跳过。
用户标识头:
x-agent-user-account: <工号>
x-codeagent-request-kind: main_conversation
x-error-propagation: true
is-need-queue: true
其中 x-agent-user-account 标识调用者身份,可通过环境变量 HUAWEI_USER_ACCOUNT 注入。
确认必选头的方法
对每个头逐个移除后重发请求,观察 SnapEngine 的响应。如果返回 401,说明该头是必须的;如果仍然 200,则可以省略。实测结果:x-auth-token 是严格必须的,kernel-version 和 app-id 不能为空,hash3 可以伪造。
凭证获取:从本地存储解密 DevUC token
凭证文件结构
CodeAgent CLI 将 DevUC OAuth2 凭证存储在 ~/.cac/.credentials.json,结构如下:
{
"devUCOAuth": {
"accessToken": "<DPAPI 加密后的 base64>",
"refreshToken": "<DPAPI 加密后的 base64>",
"expiresAt": 1724481418806,
"accountId": "b00959275",
"env": "prod"
}
}
accessToken 和 refreshToken 都是 DPAPI 加密后的 base64 编码,不能直接使用,必须在当前 Windows 用户会话下解密。
DPAPI 解密实现
Node.js 没有原生 DPAPI 绑定,但可以通过 child_process 调用 PowerShell 的 System.Security.Cryptography.ProtectedData::Unprotect 完成解密。关键是通过 stdin 传入 base64 blob,避免 shell 对 + 和 $ 等字符的转义干扰:
async function decryptDpapi(base64Blob: string): Promise<string> {
const psScript = [
'Add-Type -AssemblyName System.Security',
'$b64 = [Console]::In.ReadLine()',
'$bytes = [Convert]::FromBase64String($b64)',
'$dec = [System.Security.Cryptography.ProtectedData]::Unprotect('
+ '$bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)',
'[Console]::Out.WriteLine([Text.Encoding]::UTF8.GetString($dec))',
].join('\n')
const { spawn } = await import('node:child_process')
return new Promise((resolve, reject) => {
const child = spawn('powershell', ['-NoProfile', '-NoLogo', '-Command', psScript], {
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
})
let stdout = ''
child.stdout.on('data', (d) => { stdout += d.toString() })
child.stdin?.write(base64Blob + '\n')
child.stdin?.end()
child.on('close', (code) => {
if (code !== 0) reject(new Error(`PowerShell exit ${code}`))
else resolve(stdout.trim())
})
})
}
Token 自动刷新
access_token 有效期约 2 小时,过期前需要用 refresh_token 续期。刷新端点为:
POST https://codeagentcli.rnd.huawei.com/codeAgentPro/oauth/refreshToken
Body: { "refreshToken": "<明文 refresh_token>", "clientId": "com.huawei.devmind.codebot.apibot" }
代理在每次请求时检查 token 是否即将过期(提前 3 分钟),若过期则主动刷新。刷新失败时,回退到重新解密磁盘上的凭证文件——因为 CodeAgent 桌面端可能已经在外部刷新了凭证。
本地代理架构
核心思路
dsh 的 llm-deepseek adapter 提供了 baseURL 配置项,支持将请求目标改为任意 OpenAI 兼容端点。只要在本地启动一个 HTTP 代理,让它对外暴露 /chat/completions 接口、对内注入企业鉴权头后转发到 SnapEngine,dsh 就能像调用 DeepSeek 官方 API 一样调用内部模型。
dsh (DeepSeekAdapter)
→ POST http://127.0.0.1:19871/chat/completions
→ proxy 注入 x-auth-token + 企业标识头
→ POST https://snapengine.../api/v2/chat/completions
→ 流式响应原路返回
代理的三项职责
凭证解密与注入:每次请求时从 ~/.cac/.credentials.json 读取 DPAPI 加密的凭证,解密 access_token,注入到 x-auth-token 和 Authorization 头中。
请求体清理:dsh 的 DeepSeek adapter 会在请求体中注入自身的扩展字段(如 thinking、reasoning_effort、stream_options),这些字段在 DeepSeek 官方 API 上有效,但 SnapEngine 不认识,直接返回 400。代理在转发前解析 JSON 请求体,剥离这些字段。此外,SnapEngine 对 max_tokens 有上限(65536),而 dsh 默认发送 256000,代理需要将其 clamp 到允许范围内。
响应流式转发:SnapEngine 返回 text/event-stream 格式的流式响应,代理直接将上游的 ReadableStream 管道到客户端的 HTTP response,保持流式语义不变。
客户端配置
dsh 通过 profile 的 cordis.patch.yml 覆盖 llm-deepseek 的默认配置,将 baseURL 指向本地代理,apiKeyEnv 改为一个占位环境变量:
- id: llm-deepseek
config:
apiKeyEnv: HUAWEI_API_KEY
baseURL: !!js process.env.HUAWEI_BASE_URL ?? 'http://127.0.0.1:19871'
models:
- id: Qwen3.6-27B-CodeAgent-VL
name: Qwen3.6-27B (CodeAgent Light)
contextWindow: 131072
maxTokens: 65536
- id: GLM-5.1-CodeAgent
name: GLM-5.1-CodeAgent (Flagship)
contextWindow: 131072
maxTokens: 65536
!!js 是 Cordis loader 的扩展标签,运行时通过 eval 求值,允许从环境变量动态读取配置。HUAWEI_API_KEY 在凭证文件中存储占位值即可——代理不检查 API Key,它依据的是 x-auth-token。
代理的可观测性端点
代理额外提供两个端点,方便调试和前端展示:
GET /status:返回代理健康状态、auth token 有效性及过期时间、上游 URL、可用模型列表GET /v1/models:返回 OpenAI 兼容格式的模型列表,供前端 ModelSelect 组件使用
关键约束与兼容性适配
上游不认识的 wire 字段
OpenAI 兼容协议允许各提供商在请求体中添加扩展字段,但这些字段不互通。dsh 的 DeepSeek adapter 发送的 thinking、reasoning_effort、stream_options 是 DeepSeek 官方 API 的扩展,SnapEngine 会因无法识别而返回 400。
处理方式是双重防护:配置层在 cordis.patch.yml 中省略(不是设为 disabled——设为 disabled 仍会在 wire body 中发送 thinking: { type: 'disabled' })对应的配置项,从源头减少字段;代理层在运行时剥离残余字段,作为兜底。
参数范围限制
SnapEngine 对 max_tokens 的上限为 65536,超过此值返回 InferHub.001001005.400 RANGE_VALIDATOR 错误。dsh 默认请求 max_tokens: 256000,必须在代理层 clamp 到 65536,同时在模型声明中配置 maxTokens: 65536 让 dsh 不要发送超出范围的值。
TLS 证书注入
SnapEngine 的 HTTPS 证书由华为 BPIT Root CA 签发,该 CA 不是操作系统默认信任的。Node.js 进程发起 HTTPS 请求时需要指定 CA 证书路径,否则 TLS 握手失败,报 TypeError: fetch failed(无详细原因)。解决方案是在启动代理和 dsh 时设置环境变量:
NODE_EXTRA_CA_CERTS="D:\Software\CodeAgentCLI\custom-cac\cert.pem"
企业内网环境下的包管理与 TLS 证书
上面的 TLS 问题不仅影响运行时 HTTPS 请求,也影响包管理器的网络通信。在企业内网中使用自签名 CA 证书的 npm registry 是常见场景——不只是华为,任何在公司内部部署 Artifactory、Nexus 等私有 registry 的环境都会遇到。
pnpm 的特殊行为
Node.js 的 fetch、https 模块以及 npm 都会读取 NODE_EXTRA_CA_CERTS 环境变量来扩展信任的 CA 列表。但 pnpm 9+ 的供应链验证步骤使用了自己独立的 HTTP 客户端,完全不读取这个变量。这意味着即使在环境中设置了 NODE_EXTRA_CA_CERTS,pnpm install 仍然会因为证书验证失败而报错。
以下方法对 pnpm 的供应链验证均无效:
NODE_EXTRA_CA_CERTS=<path-to-cert>NODE_TLS_REJECT_UNAUTHORIZED=0~/.npmrc中的cafile或ca
pnpm 唯一尊重的配置是 .npmrc 中的 strict-ssl:
# .npmrc — 企业内网自签名 CA 环境
strict-ssl=false
这个设置应放在项目级 .npmrc 中,避免影响其他项目。它让 pnpm 在供应链验证阶段跳过 TLS 证书校验,属于开发环境的临时方案——部署到拥有正规 CA 链的环境时应移除。
运行时 vs 包管理:两种证书问题的区别
同一个自签名 CA 引发了两种不同的症状,容易混淆:
- 运行时(
fetch访问 SnapEngine 等 HTTPS 端点):Node.js 信任NODE_EXTRA_CA_CERTS,设置该变量即可解决。未设置时表现为TypeError: fetch failed,没有明确的证书相关错误信息,排查时容易被误判为网络不通。 - 包管理(
pnpm install):pnpm 的供应链验证不信任NODE_EXTRA_CA_CERTS,必须用.npmrc的strict-ssl=false。表现为证书相关错误(UNABLE_TO_VERIFY_LEAF_SIGNATURE、SELF_SIGNED_CERT_IN_CHAIN)。
在同一个企业内网项目中,通常需要同时处理这两个层面:.npmrc 解决包安装,NODE_EXTRA_CA_CERTS 解决运行时 HTTPS 连接。
验证与可观测性
代理启动后,通过 /status 端点可以快速验证鉴权链路是否完整:
curl http://127.0.0.1:19871/status
正常时返回:
{
"status": "ok",
"proxy": "dsh-huawei",
"upstream": "https://snapengine.cida.cce.prod-szv-y.dragon.tools.huawei.com/api/v2",
"auth": { "type": "W3/DevUC", "token": "valid (expires 2026-08-24T07:36:58.806Z)" },
"uptime": 350,
"models": ["Qwen3.6-27B-CodeAgent-VL", "GLM-5.1-CodeAgent"]
}
端到端验证用一条最小请求即可完成:不带 system prompt、不带 tools,只发一条 "Say hi" 给 GLM-5.1,观察代理日志是否出现 ← 200 和流式 SSE 数据。
代理在请求处理路径上记录了关键日志:raw body 的前 2000 字节、解析后的 body keys、剥离的字段名、转发前的 body 大小、上游响应状态码和错误详情。这些信息在排查上游 400 时至关重要——SnapEngine 的错误码(如 TM.00000001、InferHub.001001005.400)和错误消息(如 RANGE_VALIDATOR)只有在代理日志中才能看到,dsh 侧只能看到一个笼统的 HTTP 错误。
总结
将 DeepSeek Harness 接入华为内网的过程,本质上是在标准 OpenAI 兼容协议与企业 SSO 网关之间建立一层适配。抓包逆向确定了鉴权协议的结构,DPAPI 解密解决了凭证获取问题,本地代理承担了认证头注入和请求体清理两项核心职责,使得 dsh 不需要任何核心代码改动就能通过企业网关调用内部模型。
这个模式的核心约束在两个层面:协议兼容性和 TLS 证书。协议层面,不同提供商的 OpenAI 兼容实现存在扩展字段不互通的问题,代理必须充当"方言翻译器";TLS 层面,企业自签名 CA 导致的证书信任问题同时影响包管理和运行时,但两者的解决方式不同——pnpm 需要 .npmrc 配置,Node.js 运行时需要环境变量。
这一模式并不特定于华为 SnapEngine。任何"标准 API 客户端 + 企业 SSO 网关"的场景,都可以用相同的思路:抓包逆向鉴权协议,本地代理注入认证,客户端只需改一行 baseURL 配置。