dsh接入华为内网

作者:CherryYang 发布时间: 2026-08-24 阅读量:4 评论数:0

将 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-versionapp-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 面板抓取它发出的请求即可。

抓包步骤

  1. 打开 CodeAgent 网页端,按 F12 打开 DevTools,切换到 Network 面板
  2. 发送一条对话消息,在 Network 中找到发往 snapengine.cida.cce.prod-szv-y.dragon.tools.huawei.com 的 POST 请求
  3. 右键该请求 → 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-versionapp-id 不能为空,hash3 可以伪造。

凭证获取:从本地存储解密 DevUC token

凭证文件结构

CodeAgent CLI 将 DevUC OAuth2 凭证存储在 ~/.cac/.credentials.json,结构如下:

{
  "devUCOAuth": {
    "accessToken": "<DPAPI 加密后的 base64>",
    "refreshToken": "<DPAPI 加密后的 base64>",
    "expiresAt": 1724481418806,
    "accountId": "b00959275",
    "env": "prod"
  }
}

accessTokenrefreshToken 都是 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-tokenAuthorization 头中。

请求体清理:dsh 的 DeepSeek adapter 会在请求体中注入自身的扩展字段(如 thinkingreasoning_effortstream_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 发送的 thinkingreasoning_effortstream_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 的 fetchhttps 模块以及 npm 都会读取 NODE_EXTRA_CA_CERTS 环境变量来扩展信任的 CA 列表。但 pnpm 9+ 的供应链验证步骤使用了自己独立的 HTTP 客户端,完全不读取这个变量。这意味着即使在环境中设置了 NODE_EXTRA_CA_CERTSpnpm install 仍然会因为证书验证失败而报错。

以下方法对 pnpm 的供应链验证均无效:

  • NODE_EXTRA_CA_CERTS=<path-to-cert>
  • NODE_TLS_REJECT_UNAUTHORIZED=0
  • ~/.npmrc 中的 cafileca

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,必须用 .npmrcstrict-ssl=false。表现为证书相关错误(UNABLE_TO_VERIFY_LEAF_SIGNATURESELF_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.00000001InferHub.001001005.400)和错误消息(如 RANGE_VALIDATOR)只有在代理日志中才能看到,dsh 侧只能看到一个笼统的 HTTP 错误。

总结

将 DeepSeek Harness 接入华为内网的过程,本质上是在标准 OpenAI 兼容协议与企业 SSO 网关之间建立一层适配。抓包逆向确定了鉴权协议的结构,DPAPI 解密解决了凭证获取问题,本地代理承担了认证头注入和请求体清理两项核心职责,使得 dsh 不需要任何核心代码改动就能通过企业网关调用内部模型。

这个模式的核心约束在两个层面:协议兼容性和 TLS 证书。协议层面,不同提供商的 OpenAI 兼容实现存在扩展字段不互通的问题,代理必须充当"方言翻译器";TLS 层面,企业自签名 CA 导致的证书信任问题同时影响包管理和运行时,但两者的解决方式不同——pnpm 需要 .npmrc 配置,Node.js 运行时需要环境变量。

这一模式并不特定于华为 SnapEngine。任何"标准 API 客户端 + 企业 SSO 网关"的场景,都可以用相同的思路:抓包逆向鉴权协议,本地代理注入认证,客户端只需改一行 baseURL 配置。

评论