dsh-wb400-guard
Identified★ 4Fix WorkBuddy CN gateway 400 / code 11128 in DeepSeek Harness by rewriting @ in system messages at the fetch layer
dsh-wb400-guard
中文 | English
一条命令修掉 DeepSeek Harness 走 WorkBuddy 国内版反代时每轮对话都失败的 400。
本轮运行失败 400: {"message":"workbuddy upstream client (http 400): 请求被拦截,请重新发送","type":"client","code":"client"}
标签 INVALID_REQUEST,底层是网关的 code 11128。走国际版(www.workbuddy.ai)不出现。
30 秒判断你需不需要它
| 现象 | 命中 |
|---|---|
报错文字是「请求被拦截,请重新发送」,标签 INVALID_REQUEST | 是 |
| 换成国际版模型就正常,换回国内版就失败 | 是 |
| 每次都在第一步就挂,连一句完整回复都没有 | 是 |
| 换个账号、换个模型、换台机器,症状一模一样 | 是 |
| 手动在 WorkBuddy 官方客户端里聊天是正常的 | 是 |
报的是 429 或「usage exceeds frequency limit」 | 否,那是额度 |
报的是 11101 或「Non-stream chat request」 | 否,那是非流式请求 |
报的是 fetch failed / 502 | 否,那是网络 |
上面前三条同时命中,基本可以确定就是本文档解决的那个问题。
为什么会被拦
国内网关 copilot.tencent.com 在服务端对 system 消息做通道校验,命中规则就回:
{"code":11128,"msg":"Illegal API invocation from an unapproved channel"}
插件包把这条错误包装成「请求被拦截,请重新发送」。触发这条规则的有两个互不相关的来源,DSH 默认两个都带着,所以插件两个都要处理。
触发源一:文件引用说明里的那一段字
@deepseek-ai/dsh-file-reference 会往 system prompt 里注入这段:
Tokens prefixed with @ are paths the user explicitly referenced. Relative paths
resolve from the workspace root; absolute paths identify files or directories on the
host. ... @\"...\" quotes a path containing spaces.
它不是「只要出现 @ 就拦」。逐词删词消融能直接看出来:这段话里删掉 Tokens prefixed with are paths the user explicitly Relative resolve from workspace 里的任何一个词,请求立刻变 200;删掉后半截的词(referenced. root; absolute identify files or)照样 400。
也就是说网关匹配的是前段连续词组 + 那个 @ 字形这个组合,不是单个字符。同一份文本把 @ 去掉,或换成等义的 (at),都变 200。
触发源二:内核身份句
@deepseek-ai/dsh-system-prompt 的官方发布版默认 includeHarnessIdentity = true,会在 system 最前面放一句:
You are an AI agent powered by DeepSeek Harness.
(DeepSeek 和 Harness 之间是一个零宽空格 U+200B。)
这句只要位于 system 消息开头就会被拦。放到句子中间、放到消息尾部、前面先垫一行别的文字,都不拦。零宽字符、前置普通空格、前置 BOM 都绕不过去。
本机这个坑被 ~/.dsh/prompt-inject.md 顶掉了,所以有些人只撞到触发源一;没写过注入文件的用户两个都会撞。这也是为什么另一个插件只修了身份句、用户升级后照样报错——两个源必须一起修。
国际网关 www.workbuddy.ai 不做这个校验,同一份请求发过去是 200。
定位过程(可复现)
全部结论都是对真实网关做 A/B 打出来的,同账号同请求头,只换 body 里的 system 文本。
触发源一:
| 实验 | 结果 |
|---|---|
| 真实 system 原文(含 @) | 400 / 11128 |
| 同一份文本把 @ 去掉 | 200 |
同一份文本 @ 换成 (at) | 200 |
| 只去掉第一个 @(还剩一个) | 200 |
| 只去掉第二个 @(还剩一个) | 400 |
短句 Tokens prefixed with @ are paths. | 200(单独发不触发) |
单词 @、@ + 纯填充 | 200 |
| @ 放进 user 消息 | 200(只在 system 里触发) |
| 原文前缀截到 110 字符 | 200 |
原文前缀截到 112 字符(末尾正好是 workspace root;) | 400 |
同样 112 字符,把 @ 换成 x | 200 |
同样 112 字符,纯 x 填充 | 200 |
| 300 字符 = 弱内容 + 纯填充 | 200(不是长度阈值) |
| 逐词删词(前半段任一词) | 全部 200 |
| 逐词删词(后半段任一词) | 全部 400 |
换成全角 @ | 400(网关折回半角再判) |
| 同一 body 连发 5 次 | 5 次全 11128,完全确定性 |
触发源二:
| 实验 | 结果 |
|---|---|
| 身份句在 system 开头 | 400 / 11128 |
| 身份句放句子中间 | 200 |
| 身份句放消息尾部 | 200 |
| 身份句前面先垫一行别的文字 | 200 |
| 身份句前加普通空格 / BOM | 400(绕不过) |
DeepSeek 与 Harness 之间加零宽字符 | 400(绕不过) |
改成分号 DeepSeek-Harness. | 200 |
改成分号 DeepSeek.Harness. | 200 |
You are the AI agent ... | 200 |
换词序 ... that DeepSeek Harness powers. | 200 |
换成 WorkBuddy | 200 |
跟长度无关、跟请求头无关、跟工具数量无关:max_tokens 从 1 扫到 1048576 全 200;94 个工具的完整请求头一字不改;纯填充到 60000 字符也不拦。
插件做什么
在宿主进程包一层 globalThis.fetch,只对发往 copilot.tencent.com 的请求,对 system / developer / assistant 消息做两件事:
- 把触发源一那段文本里的 @ 换成等义写法
(at); - 把开头的身份句
You are an AI agent powered by DeepSeek Harness.换成You are an AI agent powered by DeepSeek-Harness.(空格改连字符)。
两处都是语义等价改写,模型读到的指令含义没变,网关匹配不到那两个指纹。user 和 tool 消息一个字不动;别的 API(DeepSeek、OpenAI、Anthropic、WorkBuddy 国际版)一律原样透传。
为什么要连 assistant 一起扫
网关不止看 system。实测(同一账号、同一请求头,只换 body):
| assistant 消息 | 结果 |
|---|---|
| 干净内容 | 200 |
| 身份句在开头 | 400 11128 |
| 身份句在中段 / 尾部 | 200 |
| 文件引用段在开头 / 中段 / 尾部 | 400 11128 |
文件引用段 @ 换成 (at) | 200 |
| 文件引用段前加 1 空格 / tab / 换行 / BOM | 400 全部 |
普通 @、邮箱 a@b.com、@someone | 200 |
| 多轮对话里只有第 2 条 assistant 带指纹 | 400(每条都查) |
结论:assistant 里文件引用段任意位置都触发(比 system 严),身份句仍然只在开头触发。user 和 tool 消息里放同样的内容都 200,所以刻意不动它们。
mode=all 只在 system 上兜底全换,不影响 assistant 的普通 @。
为什么挂在 fetch 层
试过另外两个位置,都不行:
llm/stream事件:GenerateOptions到达时是深冻结的,next()也不接参数,只能读不能改。system-prompt/assemble事件:能改,但它会改掉所有 provider 的提示词,而问题只出在国内网关一家。
fetch 是唯一既精准、又能覆盖插件内部 11128 重试阶梯的位置。那套阶梯每次重试都会重新发请求,在 body 组装层改就会漏掉其中几级。
安装
零运行时依赖,只用 node 内置模块。需要 Node ^22.19.0 || >=24.0.0。
用 DSH 自己的命令装(推荐)
dsh plugin --profile web add github:curo22/dsh-wb400-guard
把 web 换成你自己的 profile 名(桌面版是 desktop)。
或者 clone 下来跑
git clone https://github.com/curo22/dsh-wb400-guard.git
cd dsh-wb400-guard
node install.mjs
它做四件事:拷文件到 %USERPROFILE%\.dsh\profiles\desktop\vendor\dsh-wb400-guard、往 profile 的 package.json 写 dependencies 与 dsh.profile.bundles、建 junction 让模块可解析、跑一遍产物的 29 条自测。
指定别的 profile:
node install.mjs D:\path\to\other\profile
卸载:
node install.mjs --uninstall
装完 重启 DSH Desktop(不是关窗口,是完全退出)。宿主日志里应该出现:
[I] [dsh-wb400-guard] [dsh-wb400-guard] fetch guard installed for copilot.tencent.com
手动装
$prof = "$env:USERPROFILE\.dsh\profiles\desktop"
Copy-Item -Recurse -Force .\* "$prof\vendor\dsh-wb400-guard\"
New-Item -ItemType Junction -Path "$prof\node_modules\dsh-wb400-guard" -Target "$prof\vendor\dsh-wb400-guard"
然后手改 $prof\package.json:dependencies 加 "dsh-wb400-guard": "file:./vendor/dsh-wb400-guard",dsh.profile.bundles 数组末加 "dsh-wb400-guard"。
别用
ConvertTo-Json | Set-Content改 profile 的package.json。 PowerShell 这套组合在中文用户名下会把 UTF-8 字节按 GBK 解一次再编码,link:C:/Users/罗雨/新目录/...会变成link:C:/Users/缃楅洦/鏂扮洰褰?/...,同时给文件加 BOM。 DSH 的配置热重载会直接报SyntaxError: Unexpected token '',profile 加载失败。install.mjs用 Node 的fs读写,写完回读校验 BOM 和中文依赖值没被破坏。
配置
cordis.patch.yml 里可调:
| 键 | 默认 | 说明 |
|---|---|---|
enabled | true | 关掉就完全不碰 fetch |
gatewayHosts | ["copilot.tencent.com"] | 要防护的 host;以 . 开头做后缀匹配。国际网关默认不在列表 |
mode | "anchor" | anchor 只改两处指纹那几句;all 把 system 里所有 @ 全换掉(内核改了措辞时兜底) |
logPath | "" | 空则写 ~/.dsh/logs/dsh-wb400-guard.jsonl |
dryRun | false | 只记录不实际改写 |
安全边界
- 只改 system / developer / assistant。user 与 tool 消息里的邮箱
a@b.com、@某人、代码里的装饰器一律不动。 - 只改受防护的 host。别家 API 请求原样透传同一引用,不重新序列化。
- 非 JSON、非字符串、无 body 的请求原样透传。
- 幂等。第二次改写不再变动,返回入参原引用。
- fail-open。改写链路任何异常都退回原始请求,不阻断对话、不引入重试。
- 剔除陈旧的
Content-Length。body 变短后若还带旧长度,上游会按旧长度截断。 - 不写入、不上报。只读请求 body,改写后立刻发走,不落盘、不外传。
验证
# 产物自测 29 条:契约、改写(三个触发源场景)、host 判定、幂等、fail-open、安装卸载、配置
node --test
# 对真实国内网关验证(需要本机已登录 WorkBuddy 桌面版)
$env:WORKBUDDY_ELECTRON_BIN = '<WorkBuddy 的 Electron 可执行文件>'
node verify-live.mjs
verify-live.mjs 拿真实凭证打真实网关,预期:
经插件前(预期被拦) 400 BLOCKED(11128) OK
经插件后(预期通过) 200 PASS OK
另外几个更贴现场的脚本:
# 两个触发源各自单独发、叠加发,五组真实网关对照
node scripts/verify-identity-live.mjs
node scripts/verify-assistant-live.mjs
# 端到端:原版 dsh-workbuddy-connect 组装 → guard 改写 → 真实网关
node scripts/verify-pipeline.mjs
# 拿一份 154KB 的真实失败 body(94 个工具、15 条消息)重放
node scripts/verify-real-body.mjs
# 装完重启后跑这个:一条命令回答「修复此刻在不在工作」
node scripts/health-check.mjs
# 只读日志,看真实流量有没有被改写
node scripts/read-guard-log.mjs
# 仓库与已安装的 vendor 副本是否一致
node scripts/compare-vendor.mjs . "$env:USERPROFILE\.dsh\profiles\desktop\vendor\dsh-wb400-guard"
排障
装完还是不行,按这个顺序查:
1. 插件有没有被加载。 宿主日志(%APPDATA%\DSH Desktop\logs\host\)里搜 dsh-wb400-guard。没有 fetch guard installed 这一行,说明 profile 没读到它 —— 多半是没重启,或者 dsh.profile.bundles 没写进去。
2. 插件本身有没有效。 跑 node verify-live.mjs。它不依赖 DSH 的加载链路,直接拿真实凭证打真实网关。如果这一步的「经插件后」也不是 200,问题在插件或凭证,不在安装。
3. 有没有别处手改抢先处理了。 早期修法是直接改 dsh-workbuddy-connect 源码,那份改动会先于本插件把 @ 换掉,health-check.mjs 的改写计数就会一直是 0,看着像没生效。它会扫 node_modules/dsh-workbuddy-connect/lib/*.js 里几个特征标识,扫到就明说。注意:磁盘上已经恢复原版、但进程还没重启时,内存里仍是旧代码,同样会抢先处理——这种情况重启一次就好。
4. 改写有没有真的发生。 跑 node scripts/health-check.mjs。它会同时读安装状态、宿主加载行、guard 日志的改写计数、宿主日志的拦截记录,然后给结论。看到「已就绪,但还没有国内路由的流量经过」,用国内版模型发一条消息再跑一次。
5. 网关是不是改了规则。 如果 verify-live.mjs 的「经插件前」那一行也变成 200 了,说明网关不再因为 @ 拦截,插件处于空转,不影响使用。
常见问题
会不会影响我别的模型?
不会。只有 host 匹配 copilot.tencent.com 的请求才会被看。DeepSeek 官方 API、OpenAI、Anthropic、WorkBuddy 国际版都是原样透传同一个对象引用,连序列化都不会重做。
会不会改坏我的对话内容?
只动 system / developer / assistant 消息。user 和 tool 里写的邮箱、@某人、代码里的装饰器、正则,一个都不碰,@" 这种 here-string 开头也原样保留。改的只有两处指纹,语义不变。
为什么不用改 dsh-workbuddy-connect?
改那个包在插件升级时会被覆盖,得重打。本插件在 profile 层,插件包升级不影响它。
升级插件后要重装吗?
要。install.mjs 是拷贝到 vendor,不是链接。拉新代码后重跑一次,再用 compare-vendor.mjs 确认一致。
国际版也装吗?
默认不防护国际版。如果哪天 www.workbuddy.ai 也开始校验,往 gatewayHosts 加一条、mode 设成 all 即可。
会被封号吗? 只改自己发出去的消息里一个字符的写法,请求还是同一个端点、同一套凭证、同样的工具和对话。没有伪造身份、没有绕过额度、没有多开。
已知限制
- 网关改了规则(不再因
@拦截)时插件空转,不影响功能。verify-live.mjs会告诉你原样请求已经能过。 - DSH 内核改了
FILE_REFERENCE_PROMPT措辞时,anchor模式仍有Tokens prefixed with @这个锚点兜底;锚点也变了就把mode设成all。 - 429 限流与本插件无关。那是额度用尽,等额度重置。
stream: false会被国内网关拒为11101,这个码跟11128不是一回事。本插件只解决11128。- 两个插件同时包装
fetch时后装的在外层。本插件用Symbol.for('dsh-wb400-guard.installed')保证只装一层,重复调用返回同一个卸载函数,卸载时也不会覆盖别人后来装的fetch。 - 磁盘文件与运行中进程是两回事。把磁盘文件改回原版,不会让进程里的改动消失,它要等下次启动才失效。装了插件不重启,同理。
开发
# 跑测试(Node 24 上 `node --test test/` 不认目录参数,直接不带参数让它自动发现)
node --test
# 把自己的 profile 指向本仓库,改代码即时生效(不需要每次拷贝)
.\scripts\link-local.ps1
.\scripts\link-local.ps1 -Remove # 拆掉
两个脚本的分工:install.mjs 拷贝到 profile\vendor,适合分发;link-local.ps1 用 junction 指向本仓库,适合开发。
install.mjs 是拷贝而不是链接,所以拉了新代码之后要重跑一次,vendor 才会跟上。想知道有没有落后:
node scripts/compare-vendor.mjs . "$env:USERPROFILE\.dsh\profiles\desktop\vendor\dsh-wb400-guard"
逐文件哈希比对,列出内容不同和缺失的文件。全一致时只有一句「vendor 与仓库完全一致」。
推送到 GitHub:
.\scripts\push-github.ps1
会依次检查工作区干净、SSH 认证、远端仓库存在,再推。哪一步不通就停在哪一步并说清原因。
Windows 上 git 自带的 ssh 在中文用户名下会把 HOME 解析成 GBK 乱码路径,导致
Host key verification failed。 用系统 OpenSSH:git config core.sshCommand 'C:/Windows/System32/OpenSSH/ssh.exe'。
相关
- 推广文案包 PROMO.md —— 一句话简介、各平台发布帖、标签建议,随手转发用
- DeepSeek Harness —— 本插件服务的宿主
许可
MIT
Comments
Loading…
Similar plugins
by balongTTY
Fix WorkBuddy/CodeBuddy 400 'Illegal API invocation from an unapproved channel' (code 11128) with lossless prompt keyword neutralization — DSH settings-page plugin. 修复 WorkBuddy 请求被拦截 400(提示词无损绕过)。
★ 11
MIT
JavaScript
Oct 22, 2026
dsh plugin --profile web add @balong/dsh-workbuddy-fix通过 WorkBuddy API Key 或 CodeBuddy 登录令牌为 DeepSeek Harness 接入 CodeBuddy 模型
★ 22
↓ 113/wk
MIT
JavaScript
Aug 20, 2026
dsh plugin --profile web add dsh-llm-codebuddyby omdsh-plugins
Code mode for the DeepSeek Harness web GUI: a third segment beside Chat and Work whose column is the harness's own terminal, running in the conversation's workspace
★ 0
MIT
TypeScript
Sep 4, 2026
dsh plugin --profile web add @omdsh-plugins/omdsh-codemodeby leoc114119
DSH (DeepSeek Harness) 插件:注册 workbuddy_checkin 工具,读取本机 WorkBuddy 加密登录态并完成每日积分签到
★ 0
MIT
JavaScript
Oct 17, 2026
dsh plugin --profile web add dsh-workbuddy-checkinby Acoder416
WorkBuddy (workbuddy.ai / codebuddy.cn) subscription quota as a DSH model provider: supervises the local OpenAI-compatible gateway and exposes it as a settings page.
★ 0
MIT
JavaScript
Oct 19, 2026
dsh plugin --profile web add dsh-plugin-workbuddy-gatewayby longge1213812138
将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.
★ 3
MIT
TypeScript
Oct 27, 2026
dsh plugin --profile web add dsh-workbuddy-connect