🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Xray 代理守护 TwinsEarth/DSH-2Xray

DeepSeek 客户端
TwinsEarth2 天前有提交已提供中文文档扫描:高危兼容 / 相关生态

托管 xray 子进程并把进程流量接入代理节点

30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/6 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/TwinsEarth/DSH-2Xray.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/06·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 2 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

🔴 安全扫描发现高危能力自动 · 静态分析源码
v2.7.0
🔴 扫描:高危尚无人工评估

静态扫描发现安装期即执行代码,或命令参数由运行时变量拼接(参数来源无法静态判定,从严处理)。

命令参数由运行时变量拼接读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 62 处,展示前 62 条)
  • 发起网络请求
    lib/client.js:91
    const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json' } })
  • 发起网络请求
    lib/client.js:144
    const response = await fetch(new URL('api/xray/switch', document.baseURI), {
  • 发起网络请求
    lib/client.js:374
    const response = await fetch(new URL(`api/xray/update${refresh ? '?refresh=1' : ''}`, document.baseURI), {
  • 发起网络请求
    lib/client.js:390
    const response = await fetch(new URL('api/xray/update', document.baseURI), {
  • 执行外部命令来自配置/变量
    lib/process.js:64
    spawn(spec) {
  • 执行外部命令表达式求值
    lib/process.js:76
    spawn({ argv, cwd, graceMs, env }) {
  • 执行外部命令表达式求值
    lib/process.js:124
    spawn({ argv, cwd, graceMs, env }) {
  • 执行外部命令来自配置/变量
    lib/process.js:125
    const child = spawn(argv[0], argv.slice(1), {
  • 执行外部命令固定命令
    lib/process.js:170
    spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true })
  • 删除本地文件
    lib/supervisor.js:584
    rmSync(file, { force: true })
  • 写入本地文件
    lib/paths.js:28
    mkdirSync(dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/paths.js:30
    chmodSync(dir, 0o700)
  • 写入本地文件
    lib/paths.js:39
    mkdirSync(dir, { recursive: true, mode: 0o700 })
  • 写入本地文件
    lib/paths.js:42
    writeFileSync(temp, content, { mode })
  • 写入本地文件
    lib/paths.js:44
    chmodSync(temp, mode)
  • 写入本地文件
    lib/paths.js:48
    renameSync(temp, file)
  • 删除本地文件
    lib/paths.js:51
    rmSync(temp, { force: true })
  • 发起网络请求
    lib/monitor.js:125
    const request = http.request(
  • 读取本地文件
    lib/update.js:15
    return JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version
  • 执行外部命令来自配置/变量
    lib/update.js:80
    if (spawnSync(command, argv, { stdio: 'ignore' }).status === 0) return true
  • 写入本地文件
    lib/update.js:86
    mkdirSync(target, { recursive: true })
  • 读取本地文件
    lib/update.js:87
    for (const entry of readdirSync(source, { withFileTypes: true })) {
  • 写入本地文件
    lib/update.js:91
    else if (entry.isFile()) copyFileSync(from, to)
  • 写入本地文件
    lib/update.js:113
    writeFileSync(archive, bytes)
  • 写入本地文件
    lib/update.js:116
    mkdirSync(extracted, { recursive: true })
  • 读取本地文件
    lib/update.js:119
    if (!existsSync(join(packageDir, 'package.json'))) throw new Error('tarball has no package/ directory')
  • 写入本地文件
    lib/update.js:123
    renameSync(target, backup)
  • 删除本地文件递归
    lib/update.js:134
    rmSync(target, { recursive: true, force: true })
  • 写入本地文件
    lib/update.js:135
    renameSync(backup, target)
  • 删除本地文件递归
    lib/update.js:139
    if (renamed) rmSync(backup, { recursive: true, force: true })
  • 删除本地文件递归
    lib/update.js:147
    rmSync(workspace, { recursive: true, force: true })
  • 读取本地文件
    lib/update.js:157
    return statSync(path).size
  • 读取本地文件
    lib/geodata.js:33
    const stat = statSync(path)
  • 写入本地文件
    lib/geodata.js:63
    mkdirSync(dataDir, { recursive: true })
  • 写入本地文件
    lib/geodata.js:77
    writeFileSync(temporary, bytes)
  • 写入本地文件
    lib/geodata.js:78
    renameSync(temporary, target)
  • 删除本地文件
    lib/geodata.js:90
    rmSync(`${target}.tmp-${process.pid}`, { force: true })
  • 写入本地文件
    lib/assets.js:15
    mkdirSync(dataDir, { recursive: true })
  • 读取本地文件
    lib/assets.js:21
    const missingBefore = FILES.some((name) => !existsSync(join(dataDir, name)))
  • 读取本地文件
    lib/assets.js:37
    if (existsSync(target)) continue
  • 读取本地文件
    lib/assets.js:38
    const source = candidates.find((dir) => dir && existsSync(join(dir, name)))
  • 写入本地文件
    lib/assets.js:44
    copyFileSync(join(source, name), target)
  • 读取本地文件
    lib/binary.js:52
    accessSync(file, constants.X_OK)
  • 读取本地文件
    lib/binary.js:56
    return existsSync(file) && process.platform === 'win32'
  • 读取本地文件
    lib/binary.js:177
    if (existsSync(candidate)) {
  • 读取本地文件
    lib/binary.js:187
    entries = readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    lib/binary.js:203
    if (existsSync(root)) visit(root, 0)
  • 执行外部命令固定命令
    lib/tun.js:83
    return spawnSync('net', ['session'], { stdio: 'ignore' }).status === 0
  • 执行外部命令来自配置/变量
    lib/core-install.js:32
    if (spawnSync(command, args, { stdio: 'ignore' }).status === 0) return true
  • 读取本地文件
    lib/core-install.js:38
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    lib/core-install.js:70
    if (existsSync(target)) return { ok: true, path: target, cached: true }
  • 写入本地文件
    lib/core-install.js:84
    writeFileSync(archive, zip.body)
  • 写入本地文件
    lib/core-install.js:86
    mkdirSync(out, { recursive: true })
  • 写入本地文件
    lib/core-install.js:90
    mkdirSync(join(dataDir, 'bin'), { recursive: true })
  • 写入本地文件
    lib/core-install.js:91
    copyFileSync(binary, target)
  • 写入本地文件
    lib/core-install.js:92
    if (platform !== 'win32') chmodSync(target, 0o755)
  • 删除本地文件递归
    lib/core-install.js:96
    rmSync(workspace, { recursive: true, force: true })
  • 发起网络请求
    lib/health.js:84
    const request = http.request(
  • 发起网络请求
    lib/control/grpc.js:81
    request = client.request({
  • 发起网络请求
    chrome-extension/background.js:250
    const response = await fetch(url, {
  • 写入剪贴板
    chrome-extension/options.js:346
    await navigator.clipboard.writeText(text)
  • 写入剪贴板
    chrome-extension/popup.js:308
    await navigator.clipboard.writeText(link)
扫描于 2026/11/6(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH-2Xray:为 DSH 提供 xray-core 进程守护与进程内流量接管

README

由 DeepSeek 最新模型翻译生成
DSH-2Xray 一个 DSH bundle 插件,身兼两职:xray-core 进程管理者 + 进程内流量路由者: 1. 生成 xray-core 客户端配置,启动并守护 xray 子进程; 2. 在 xray 本地入站通过真实探测之后,把 DSH 进程的全局 HTTP(S) 出口接到该入站; 3. 可选:通过零依赖 gRPC 控制面做多节点运行时切换与流量统计。 0.8.0 适配 DSH 0.2.2-rc.1(Node >= 24.9.0);DSH 0.2.1 及以前请使用 0.7.0。 apply(ctx, config) │ ├─ config.js 配置归并 + 字段级校验(失败不进 failed 以外的状态) ├─ xray-config.js 纯函数:多节点 outbound + balancer + VLESS Vision REALITY ├─ supervisor.js 状态机:启动 → 探活 → 接管 → 守护 → 回收/退避 │ ├─ binary.js 解析 xray(插件 bin/ → 数据目录 → PATH)、探测版本 │ ├─ process.js ctx.subprocess(Windows Job Object)/ child_process 兜底 │ ├─ health.js TCP 探活 + 经代理的功能探测 │ ├─ route.
展开完整文档(README 剩余 1 节)
DSH-2Xray

一个 DSH bundle 插件,身兼两职:xray-core 进程管理者 + 进程内流量路由者:

1. 生成 xray-core 客户端配置,启动并守护 xray 子进程;
2. 在 xray 本地入站通过真实探测之后,把 DSH 进程的全局 HTTP(S) 出口接到该入站;
3. 可选:通过零依赖 gRPC 控制面做多节点运行时切换与流量统计。

0.8.0 适配 DSH 0.2.2-rc.1(Node >= 24.9.0);DSH 0.2.1 及以前请使用 0.7.0。

apply(ctx, config)
│
├─ config.js 配置归并 + 字段级校验(失败不进 failed 以外的状态) ├─ xray-config.js 纯函数:多节点 outbound + balancer + VLESS Vision REALITY ├─ supervisor.js 状态机:启动 → 探活 → 接管 → 守护 → 回收/退避 │ ├─ binary.js 解析 xray(插件 bin/ → 数据目录 → PATH)、探测版本 │ ├─ process.js ctx.subprocess(Windows Job Object)/ child_process 兜底 │ ├─ health.js TCP 探活 + 经代理的功能探测 │ ├─ route.js 与 DSH 代理策略层协作,或内置 undici dispatcher 兜底 │ └─ control/ 零依赖 gRPC 控制面(HTTP/2 + 手写 protobuf) ├─ tools.js xray_status / xray_switch_node / xray_stats(可选) └─ paths.js 数据目录、原子写、哈希(只记录配置哈希,不记录密钥)

关键设计决策(以及为什么不是骨架里的写法)

| 主题 | 结论 | 原因(已在 DSH 0.2.1-rc.3 上核实) |
| 全局接管方式 | 调用 @deepseek-ai/dsh-http-proxy 的 installProxyFromEnvironment(),不自己 setGlobalDispatcher() | DSH 在第一个插件挂载前就装好了策略 dispatcher;web_fetch 只问 proxyRouteFor()。裸装 dispatcher 会造成「模型 API 进代理、web_fetch 仍直连」的双源真值分裂。 |
| 入站协议 | 默认 HTTP,SOCKS 只作为可选附加 | DSH 代理策略明确拒绝 socks5:// URL(诊断为 “connecting directly”)。SOCKS 只能靠内置 dispatcher,且失去 web_fetch 覆盖。 |
| 清理钩子 | ctx.effect(() => disposer),apply 返回它 | Cordis 没有 ctx.on('dispose');ctx.effect 与 apply 返回值才是生命周期 seam。 |
| 子进程 | 优先 ctx.subprocess | Windows 上是 Job Object(KILL_ON_JOB_CLOSE)。Electron 关闭时对运行时发 SIGTERM ≈ TerminateProcess,cordis dispose 可能根本不执行;没有 Job Object 就会留下孤儿 xray 占着端口。 |
| 就绪顺序 | 先起进程 → TCP 探活 → 功能探测 → 才接管 | 反过来会出现「dispatcher 指向尚未监听的端口」,整个 DSH 的 fetch 全部 ECONNREFUSED。 |
| 停止顺序 | 先摘路由 → 再 terminate → 等退出 | 反过来存在「进程已死、dispatcher 还指着它」的窗口。 |
| 端口 | 固定端口 + 占用即失败,不静默换端口 | 换端口后 DSH 仍指向旧端口,表现为持续 ECONNREFUSED,且用户无从判断。 |
| 多节点切换 | 预置全部 outbound + balancer + RoutingService.OverrideBalancerTarget | HandlerService.AddOutbound 要传 protobuf 编码的 OutboundHandlerConfig(TypedMessage sender/proxy settings);override 只传两个字符串,是最小可靠面。 |
| 控制面实现 | 零依赖:node:http2 + 手写 protobuf 编解码 | 为一个 balancer 切换 + 统计查询给每个 profile 装 @grpc/grpc-js + proto-loader 不划算,也扩大供应链面。 |
| 重启 | 只对「意外崩溃」退避重启 | 退出码 23(配置错)不重启;端口占用不重启;上游不可达不重启 xray(进程是健康的)。 |
| 工具注册 | ctx.inject(['tools']) 可选注册 | 若声明 inject = ['tools','settings'],缺少任一服务的 profile 会整行不激活;代理本身不应该被工具可用性拖累。 |

安装

插件是一个 Host-only bundle,无需构建;除控制面外无运行时依赖。

1. 安装:npm i @twinsearth/dsh-2xray 后由插件管理器启用;或在 DSH 插件页选择「从目录安装」,指向 E:\GPT\dsh-2xray。
2. 把 xray 可执行文件放到下面任一位置(按解析优先级):
- 插件自带的 bin/xray(.exe)(随 bundle 分发时使用;MPL-2.0 说明见 bin/README.md)
- 配置 binary.path
- 环境变量 DSH_XRAY_BIN
- <DSH_HOME>\xray-proxy\bin\xray.exe(DSH_HOME 默认 %USERPROFILE%\.dsh)
- 放进 PATH
3. 编辑 profile 的 cordis.patch.yml(或 bundle 默认配置),填入 server. 或 nodes[],把 enabled 改成 true,重启 profile。

bundle 自带的 cordis.patch.yml 默认 enabled: false,避免安装即报错;它是整块配置,profile 层覆盖时必须重述要保留的字段。注册行的 name 必须等于包名(@twinsearth/dsh-2xray),否则 patch 会按「name mismatch」跳过。

- id: xray-proxy
name: '@twinsearth/dsh-2xray'
config:
enabled: true
server:
address: example.com
port: 443
uuid: 00000000-0000-0000-0000-000000000000
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
fingerprint: chrome
spiderX: /
inbound:
protocol: http
host: 127.0.0.1
port: 20808
takeover: auto

配置参考

| 字段 | 默认 | 说明 |
| enabled | false | 总开关 |
| server.address | — | 服务端域名或 IP(旧版单节点写法;多节点用 nodes) |
| server.port | 443 | 服务端端口 |
| server.uuid | — | VLESS uuid;非标准 UUID 但 ≤30 字节也可 |
| server.flow | xtls-rprx-vision | "" / xtls-rprx-vision / xtls-rprx-vision-udp443;非空时 network 必须是 raw/tcp 且 security 必须是 reality/tls(-test 不校验,插件静态拦截) |
| server.serverName | — | REALITY/TLS 的 SNI |
| server.password | — | REALITY 公钥(当前字段名),43 字符 base64url |
| server.publicKey | — | REALITY 公钥旧字段名;与 password 同时写可跨版本,至少提供一个 |
| server.shortId | "" | 偶数长度 hex,≤16 字符 |
| server.fingerprint | chrome | uTLS 指纹 |
| server.spiderX | / | 以 / 开头 |
| server.network | raw | raw / tcp(后者归一为 raw) |
| server.security | reality | reality / tls / none |
| nodes | [] | 节点数组;与 server. 二选一。每项支持 id/tag 与全部 server. 字段 |
| shareLink | "" | 粘贴一条 vless:// 分享链接(二维码里就是这段文本),解析后追加为节点 |
| shareLinks | [] | 多条分享链接;优先于 shareLink,与显式 nodes 合并(显式节点在前) |
| activeNode | 第一个节点 | 启动时钉住的 node id 或 outbound tag |
| inbound.protocol | http | http 或 socks;socks 只建议给非 Node 客户端 |
| inbound.host | 127.0.0.1 | 只允许回环地址 |
| inbound.port | 20808 | 本地入站端口 |
| inbound.socks.enabled | false | 额外开 SOCKS 入站 |
| inbound.socks.port | 20809 | 不能与 HTTP 入站相同 |
| inbound.socks.udp | false | undici 只实现 SOCKS CONNECT,开启只服务其它程序 |
| takeover | auto | auto:已有代理策略时不覆盖;force:强制覆盖;off:只起 xray 不接管 |
| control.enabled | false | 开启 gRPC 控制面(api 入站 + services) |
| control.host / control.port | 127.0.0.1 / 10085 | 只允许回环;端口不能与任何入站冲突 |
| control.services | Handler/Stats/Routing | 传给 xray 的 api.services |
| control.stats | true | 打开 stats 与 policy.system 流量计数 |
| control.balancerTag | active | 多节点 balancer 的 tag,也是切换目标 |
| control.timeoutMs | 3000 | 每次 gRPC 调用超时 |
| noProxy | "" | 追加的 bypass 列表(回环由 DSH 强制加入);不支持 CIDR |
| routing.bypassDomains | 内网后缀 | 直连域名后缀列表,数组或逗号串 |
| routing.blockAds | false | 加 geosite:category-ads-all → block |
| routing.geositeCn | false | 国内域名直连(需要 geosite 数据) |
| binary.path | "" | 显式 xray 路径 |
| binary.dataDir | "" | 数据目录;默认 <DSH_HOME>/xray-proxy |
| lifecycle.startTimeoutMs | 15000 | 端口就绪预算 |
| lifecycle.stopGraceMs | 3000 | 终止宽限(也是 ctx.subprocess 的 graceMs) |
| lifecycle.maxRestarts | 5 | 崩溃重启上限,超过进 restart-exhausted |
| lifecycle.backoffMs / backoffMaxMs | 500 / 30000 | 指数退避 |
| lifecycle.healthIntervalMs | 30000 | 周期健康探测;0 关闭 |
| lifecycle.unhealthyThreshold | 3 | 连续失败后回收进程 |
| functionalProbe.enabled | true | 经本地入站请求一次 204;这是唯一能证明 uuid/flow/SNI/REALITY 全链路可用的检查 |
| functionalProbe.url | http://www.gstatic.com/generate_204 | 探测目标 |
| functionalProbe.timeoutMs | 5000 | 探测超时 |
| log.level | warning | 传给 xray 的 loglevel;访问日志固定 none |

多节点与 gRPC 控制面

nodes[] 与 server. 二选一:前者为每个节点生成一个 outbound + 一个 balancer;后者保持单节点旧行为,配置字节不变。

- id: xray-proxy
name: '@twinsearth/dsh-2xray'
config:
enabled: true
activeNode: hk-01 # node id 或 outbound tag nodes:
- id: hk-01
address: hk.example.com
port: 443
uuid: 11111111-2222-4333-8444-555555555555
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
- id: jp-01
address: jp.example.com
port: 443
uuid: 11111111-2222-4333-8444-555555555555
flow: xtls-rprx-vision
serverName: www.example.com
password: REPLACE_WITH_43_CHAR_BASE64URL_X25519_PUBLIC_KEY
shortId: ''
control:
enabled: true
port: 10085
stats: true

启动顺序:写配置 → 起进程 → TCP 就绪 → 204 功能探测 → 接管 dispatcher → OverrideBalancerTarget(activeNode) 钉住出口。切换节点只改 balancer override,进程、入站、dispatcher 都不动。

模型可调用的工具(ctx.tools.register + 从安装目录加载 @deepseek-ai/dsh-tools 的 defineTool):

| 工具 | 作用 | 依赖 |
| xray_status | 与 status.json 相同的状态快照 | 无 |
| xray_validate_config | 用 xray run -test 预检当前配置;不启动/不重启进程;返回 { ok, reason, detail, configHash } | 无 |
| xray_switch_node | {node} → 钉住指定 outbound | control + RoutingService |
| xray_stats | 每节点累计上下行字节;{reset:true} 读后清零 | control + StatsService + stats |

版本要求:Xray 2024-09 之后的版本(v1.8.24+ / v24.9+)。 本机 1.4.5 实测 RoutingService 返回 12 unknown service,而 StatsService 在 1.4.5 已可用。版本过老时插件仍正常启动,切换工具会给出可读诊断并把 status.control.reachable 置为 false。

注意:

- override 只存在于 xray 进程内存,重启后由插件按 activeNode 重新钉住;切换结果不写回配置(持久化需要设置页 ctx.settings,尚未实现)。
- API 入站只绑 127.0.0.1,但无认证:本机任意进程可查统计或切节点。需要更强隔离时用随机端口,或关闭 control.enabled 只保留静态多出站。
- stats 计数器是自进程启动以来的累计值,不是速率;速率需要调用方自行差分。

界面入口:设置页 + 左侧 2Xray 面板

v0.2.0 提供两个入口,互不替代:

1. 插件设置页(设置 → 插件 → Xray 代理):Schemastery 表单,可编辑
启用、分享链接 (vless://)、xray 路径、本地端口、takeover、功能探测、控制面。
保存后写入 profile patch,重启 DSH 生效。
2. 左侧菜单「2Xray」(插件标签下方,黑色地球图标):内含二维码图片识别、屏幕共享扫码、链接复制;识别后复制到设置页的「分享链接」保存。

设置 → 插件 → Xray 代理 ← 粘贴 / 保存 / 开关左侧 2Xray(地球图标) ← 扫码 / 复制

0.8.0 起设置页内置 jsQR:BarcodeDetector 不可用(部分 Windows Chrome)时自动回退;图片导入与屏幕扫码都不再需要打开 tools/scanner.html。

配置导入:分享链接 / 二维码

三种入口最终都归约为同一段 vless:// 文本,所以解析器只有一份(lib/share-link.js),UI 只负责把文本拿到手。

| 方式 | 状态 | 实现 |
| 粘贴分享链接 | ✅ Host 侧已实现 | shareLink: "vless://...",或 node scripts/parse-share-link.mjs "" 生成 nodes JSON |
| 扫描屏幕二维码 | ✅ 设置页 | getDisplayMedia() 取帧 + BarcodeDetector → 内置 jsQR 回退 |
| 扫描图片二维码 | ✅ 设置页 | + createImageBitmap() + 同一个双解码器 |

- id: xray-proxy
name: '@twinsearth/dsh-2xray'
config:
enabled: true
支持标准 Xray/v2rayN 导出格式: vless://uuid@host:443?security=reality&sni=...&fp=chrome&pbk=...&sid=...&spx=%2F&flow=xtls-rprx-vision#Hong%20Kong shareLink: "vless://11111111-2222-4333-8444-555555555555@hk.example.com:443?security=reality&sni=www.example.com&fp=chrome&pbk=REPLACE_43_CHARS&sid=ab12&spx=%2F&flow=xtls-rprx-vision&type=tcp#Hong%20Kong" control: enabled: true

解析规则:

- # 片段名 → 节点 id(如 Hong Kong → outbound tag proxy-Hong_Kong),可用作 activeNode。
- sni → serverName;pbk → password 与 publicKey 双写(跨版本兼容);sid → shortId;fp → fingerprint;spx → spiderX。
- security=none 时清空 flow/SNI/shortId;type=tcp 归一为 raw。
- vmess:// / trojan:// / ss:// 会明确报「暂不支持」,而不是生成一个 Xray 会拒绝的配置——当前 builder 只产出 VLESS 出站。

订阅分组(订阅 URL 拉取多节点)未实现。 它是独立的网络动作(SSRF、重定向、离线缓存、订阅格式),建议单独设计,不与本地导入混在一起。

扫码能力依赖浏览器设置页(dsh.client + configForms/slots),当前仓库是 Host-only,所以只有链接字段与命令行可用。

状态机与失败原因码

对外状态:idle → resolving → starting → ready;异常路径为 degraded / backoff / failed / stopped。

状态快照写入 <dataDir>/status.json(0600,不含密钥),字段包括:

state、reason、detail、generation、restarts、port、binary{path,source,version}、process{pid,alive,exitCode,stderrTail}、route{installed,mode,proxy,webFetchCovered,sentinel,dispatcherIsOurs}、nodes[]、activeNode、control{enabled,reachable,activeTarget,lastError}、functionalProbe、configHash、startedAt、updatedAt。

原因码:

| reason | 含义 | 是否自动重启 |
| disabled | enabled: false | — |
| config-invalid | 字段校验失败(detail 带字段名) | 否,改配置后重载 |
| binary-missing / binary-unusable | 找不到 xray,或 xray version 不能运行 | 否 |
| config-write-failed | 数据目录不可写 | 否 |
| port-busy | 入站端口被占用 | 否(不换端口) |
| config-rejected | xray run -test 失败,或运行中以退出码 23 退出 | 否 |
| start-timeout | 进程活着但端口未监听 | 否 |
| functional-probe-failed | 入站通了但经代理的请求失败 | 否 |
| route-install-failed | xray 健康,但策略安装失败 → degraded | 进程保留 |
| external-proxy-active | takeover: auto 检测到启动器已有代理 → degraded | — |
| crashed | 运行中意外退出 → backoff | 是,指数退避 |
| unhealthy | 连续健康探测失败,回收后重启 | 是 |
| restart-exhausted | 超过 maxRestarts | 否 |
| disposed | 插件卸载/DSH 退出 | — |

覆盖矩阵

| 流量 | 是否经 xray |
| 模型 API(globalThis.fetch,含 SSE) | ✅ |
| web_fetch | ✅(harness 模式);❌ builtin/SOCKS 模式(已知缺口) |
| web_search、MCP HTTP、账号登录、第三方插件 fetch | ✅ |
| 回环地址(DSH Web UI、本地服务) | ❌ 强制直连(DSH 策略内置,插件不重复实现) |
| noProxy 命中的域名 | ❌ |
| 内网 IP / geoip:private | ❌ 由 Xray routing 直连 |
| Electron 更新检查、内嵌视图(Chromium 网络栈) | ❌ |
| ws 远程访问 / api-gateway | ❌ 自行建连,不走 undici |
| OTLP 遥测(node:http) | ❌ |
| worker / PTC 脚本 | ❌ 宿主刻意不注入 |
| bash/curl/git 子进程 | ⚠️ 接管后,DSH 从当前策略派生子进程 overlay,新起的子进程会拿到无认证的 http://127.0.0.1:<port>;是否生效取决于该程序是否读 HTTP_PROXY |

安全

- 生成的 xray 配置含 uuid 与 REALITY 公钥,写入 <dataDir>/conf/xray-config.json,POSIX 权限 0600、目录 0700;Windows 上 ACL 继承用户目录,插件不做 icacls 收紧(已知边界)。
- 只记录 configHash,不记录配置内容;错误信息只带 stderr 尾部。
- 入站只允许回环地址;inbound.host: 0.0.0.0 会被校验拒绝。
- 凭据不写进 process.env:installProxyFromEnvironment 收到的代理 URL 是无认证的 http://127.0.0.1:<port>。
- 控制面 API 无认证(仅回环);高隔离场景请关闭或换随机端口。

已知限制

1. builtin/SOCKS 模式不覆盖 web_fetch(它会自建 pinned-IP Agent)。默认 HTTP + harness 模式没有这个缺口。
2. 没有设置页 UI:v1 通过 cordis.patch.yml 配置。要做表单需走 ctx.settings + Schemastery + configForms,且可编辑字段必须是 volatile。
3. 不下载 xray:插件可以携带 bin/xray,但从不联网下载;供应链(版本、哈希、签名)留给发布流程。
4. 重启会打断在途流:Windows 上 taskkill /F 无法 drain;DSH 会看到 TRANSPORT 重试,或干净 FIN 导致的 STREAM_CLOSED(默认不重试)。xray connIdle 已设为 600s,尽量让 DSH 的 300s 空闲看门狗先触发。
5. 端口变化必须重启 DSH:插件不改端口,也不在运行中重装策略。
6. 多实例:每个 DSH profile 独立端口/配置;没有跨实例锁,端口冲突会明确报 port-busy。
7. 控制面依赖较新 Xray:RoutingService 需要 2024-09 之后的版本;更老的版本只有 StatsService。失败时插件不崩溃,但切换工具不可用。
8. API 无认证:只绑回环,但本机任意进程都能调用。高隔离场景请关闭 control.enabled 或换随机端口。
9. 切换不持久化:xray_switch_node 只改进程内 override,重启后回到 activeNode;写回配置需要设置页/ctx.settings(未实现)。

验证结果(诚实边界)

已执行:

| 项 | 结果 |
| 单元测试 node --test --test-isolation=none test/.test.js | 199/199 通过(配置校验、单/多节点配置生成、分享链接解析、二进制解析优先级、路由安装/回滚/幂等、状态机启动/崩溃重启/退出码 23/端口占用/停止顺序/启动竞态、protobuf 编解码、gRPC 客户端对 mock h2 服务、工具注册与调用) |
| DSH 0.2.2-rc.1 compat 探针 DSH_APP_ROOT=... node --test --test-isolation=none test/compat/.test.js | 12/12 通过(真实 cordis 4.1.0 / dsh-http-proxy / subprocess / client-modules / 工具 schema) |
| 真实运行时加载(ELECTRON_RUN_AS_NODE=1 "E:\DS\DeepSeek Harness.exe") | 插件入口可导入;ctx.effect 注册与 dispose 正常;state=idle → stopped |
| 真实运行时路由探针(scripts/probe-routing.mjs) | 通过 createRequire 解析到安装目录里的 @deepseek-ai/dsh-http-proxy;installProxyFromEnvironment 安装成功;fetch 实测经假代理(absolute-form 请求行)、回环地址直连(origin-form)、restore() 后回到直连(ENOTFOUND) |
| 工具层解析 | @deepseek-ai/dsh-tools 同样从安装目录解析成功,defineTool 生成合法参数 schema |
| 真实 xray gRPC 探针 | 启动本机 xray 1.4.5 的 API 入站后,插件的零依赖客户端成功调用 StatsService.QueryStats 并解码出 inbound 计数;RoutingService 按预期返回 12 unknown service |
| 多节点配置结构校验 | 2 节点 + balancer + API 入站 + stats 的配置,被本机 xray 1.4.5(降级 stream 后)判为 Configuration OK. |
| xray 配置结构校验 | 单节点同款做法通过,证明 inbound/outbound/routing/policy 骨架合法 |

未验证(必须在目标机执行):

- 与 Xray v26.10.x 真实二进制的 run -test(REALITY password、network: raw 字段只能靠源码规格保证);
- 多节点 OverrideBalancerTarget 的端到端切换,在支持 RoutingService 的真实版本上验证(本机 1.4.5 没有该服务);
- 真实服务端的端到端连通(uuid/flow/SNI/shortId/公钥);
- Windows Job Object 在强杀 DSH 后清理 xray;
- web_fetch 实抓流量与 proxyRouteFor() 的一致性;
- 验收测试计划中的 83 条用例(见仓库根目录 DSH-XRAY-PROXY-ACCEPTANCE-TEST-PLAN.md)。

排障

| 现象 | 处理 |
| binary-missing | 把 xray.exe 放进插件 bin/、<DSH_HOME>\xray-proxy\bin\,或设 binary.path |
| config-rejected,detail 含 encryption | 检查生成的配置未被手工改坏;users[0].encryption 必须为 "none" |
| config-rejected,detail 含 password | 用 xray x25519 重新生成,确认 43 字符 base64url |
| functional-probe-failed | 先用 curl -x http://127.0.0.1:20808 https://example.com -v 手工验证;确认服务端参数;离线环境可临时设 functionalProbe.enabled: false |
| port-busy | 释放端口(可能有上次残留的 xray),或改 inbound.port 后重启 DSH |
| external-proxy-active | 启动环境里已有 HTTP(S)_PROXY;需要覆盖时设 takeover: force |
| 模型请求突然 ECONNREFUSED | 查看 status.json 的 state/reason;若为 backoff,等待重启或修复配置 |
| 状态一直 degraded + route-install-failed | 查日志里的模块解析诊断;必要时确认 DSH 安装目录结构 |
| xray_switch_node 报 unknown service ... RoutingService | xray 版本早于 2024-09;升级二进制,或关闭 control.enabled 只用 activeNode |

平台内核包

@twinsearth/dsh-2xray 通过 optionalDependencies 引用按平台拆分的核心包(例如 @twinsearth/dsh-2xray-core-win32-x64),npm 只安装当前平台那一个。本地没有可用内核包时,仍按 插件 bin/ → 数据目录 → v2rayN 发现 → PATH 回退,或用 tools/install-xray-core.mjs 安装。

维护者用 tools/build-core-packages.mjs 从官方 zip 生成平台包;.github/workflows/core-packages.yml 会在具备 NPM_TOKEN secret 时自动下载、校验并发布各平台包。

规则数据自动更新

设置页默认勾选「每 72 小时更新规则数据」:Host 会下载 XTLS 的 geoip-lite.dat / geosite.dat,失败时回退 jsDelivr(Loyalsoldier);文件小于 100KB 视为错误页并拒绝替换。首次缺失时在启动前下载,之后每 72 小时检查一次,更新成功后重启 xray 热加载。

Xray 26.9.30 接受顶层 geodata 配置块但实测不执行其 cron(85 秒内无下载),因此更新由插件完成,不依赖该字段。

健康检查

- 进程内(需 DSH 会话鉴权):GET /api/xray/healthz → { "ok": true, "version": "v0.8.0", ... }
- 发布物(公开、静态):Release 资产 healthz.json,三字段 { ok, service, version },version 与 tag 一致(0.8.0 → v0.8.0)。

DSH 插件市场

索引地址(format 1):

https://raw.githubusercontent.com/TwinsEarth/DSH-2Xray/main/market/index.json

在 DSH 的「设置 → 插件市场 → 添加源」粘贴该地址,即可搜索并安装 DSH-2Xray。索引只声明 npm 包 @twinsearth/dsh-2xray 与其版本,安装仍走 npm registry。

更新

设置页 2Xray 里有三个入口:

- 检测更新:查询 npm 上的 latest,显示当前/最新版本。
- 立即更新:下载并校验 npm tarball(sha512),原子替换插件目录;重启 DSH 后生效。
- 自动更新:启动 5 秒后检查并安装新版本(默认关闭;同样需要重启)。

Host API:GET/POST /api/xray/update(走 ctx.connection.fetch 鉴权围栏)。

TUN 模式(参考 v2rayN)

依赖文件

- Windows:插件自带 bin/wintun.dll(Wintun 0.14.1,x64,GPL-2.0,见 bin/WINTUN-NOTICE.txt)。启用 TUN 时会自动复制到 xray 可执行文件旁,不需要安装 v2rayN。
- macOS / Linux:TUN 使用系统 utun/tun 设备,不需要 wintun,只需要 root 权限。
- 缺少 xray 核心时:插件会自动下载当前平台的官方 Xray release(校验 .dgst SHA2-256)到数据目录的 bin/,macOS arm64/x64、Linux x64/arm64 均已覆盖;可用 binary.autoInstall: false 关闭。

DSH 内置浏览器是 Chromium,不读插件的 undici dispatcher;只加启动参数能覆盖浏览器,但 DNS 仍可能被本地污染。v2rayN 的做法是 虚拟网卡接管全机流量 + 由 Xray 远程解析 DNS,v0.4.2 提供了同样的可选模式:

tun:yaml
enabled: true # 默认 false name: dsh-xray-tun mtu: 1500

开启后 xray 配置会多出:

- protocol: tun 入站(10.0.0.1/16 网关、0.0.0.0/1 + 128.0.0.0/1 路由);
- 顶层 dns:DoH https://1.1.1.1/dns-query / https://dns.google/dns-query;
- protocol: dns 路由到 dns-out,应用不再使用本地被污染的 DNS。

要求:

- 管理员/root 权限(创建虚拟网卡);
- Windows 需要 wintun.dll(放在 xray 可执行文件旁,或从 v2rayN 目录复制);
- 开启后重启 DSH;缺少权限或 dll 时插件会以 tun-admin-required / tun-wintun-missing 明确失败,而不是静默。

不想用 TUN 时,仍可用 tools/launch-with-browser-proxy.cmd 仅给 Chromium 加 --proxy-server;TUN 解决的是「全机 + DNS 污染」。

路由模式与黑白名单

- global(默认):全部流量走 2Xray。
- cn-direct:geoip:cn / geosite:cn 直连,其余走代理。插件会从 xray 二进制旁复制 geoip.dat/geosite.dat 到数据目录,并设置 XRAY_LOCATION_ASSET。
- whitelist:仅白名单域名走 2Xray,其余直连。

黑名单优先于白名单与分流,并会同时加入 DSH 的 noProxy(xray 重启时也不受影响)。

- 默认白名单:github.com、google.com、youtube.com、x.com、facebook.com、api.github.com、raw.githubusercontent.com
- 默认黑名单:deepseek.com、chat.deepseek.com、api.deepseek.com、download.deepseek.com、api-docs.deepseek.com、platform.deepseek.com、registry.npmjs.org

默认值只对没有显式配置的 profile 生效;已有配置请在设置页按上面列表更新后重启。

内核安装(macOS / Linux)

npm 包不内嵌五平台内核。未找到内核时:
bash
node tools/install-xray-core.mjs 26.9.30

脚本下载官方 release 资产、校验 .dgst 的 SHA2-256、解压到 ~/.dsh/xray-proxy/bin/。GitHub 不可达时设置 DSH_XRAY_MIRROR 指向镜像。macOS 也会自动搜索 ~/Library/Application Support/v2rayN/bin/xray/xray。

Chrome 扩展(2Xray for Chrome)

自 0.7.0 起与本插件同版本发布(双版本):仓库内的 chrome-extension/ 是 Manifest V3 浏览器伴侣。

- 把 Chrome 流量指向本地 Xray HTTP/SOCKS 入站(默认 127.0.0.1:20808,与插件默认值一致);
- 三卡片面板:连接状态 / 当前 VLESS 节点 + 分享二维码 / 路由与最近日志;
- PAC 分流:全局、白名单、CN 直连(浏览器侧近似 .cn + 常见国内站点;完整 geosite 仍由内核 cn-direct 负责);
- vless:// 链接导入、图片/摄像头扫码、生成独立 config.json(VLESS + 本地 inbound + 路由);
- 黑名单永远直连,默认带 deepseek.com 系列与 registry.npmjs.org。

安装:chrome://extensions → 开发者模式 → 加载已解压的扩展程序 → 选择 chrome-extension/(或解压 Release 中的 2xray-chrome-v0.8.0.zip)。
固定扩展 ID:jcalgojagadmmjeolgahhmhnmhdkecna。

扩展不能启动/停止 xray 进程,也不能接管系统全局流量;内核由本插件、v2rayN 等提供,系统级代理请用 TUN 模式。详见 chrome-extension/README.md。

构建与测试: powershell
node --test --test-isolation=none test/extension-.test.js
node scripts/build-extension.mjs # dist/2xray-chrome-v0.8.0.zip

独立版(无需 DSH,Windows)

如果用户没有安装 DSH,可以直接使用 2Xray 独立版:一个压缩包内含 Xray 内核、配置生成脚本、登录启动项和 Chrome 扩展,安装后浏览器即可走本地 Xray。

- 发布资产:2xray-standalone-win64-v0.8.0.zip
- 安装:解压 → 双击 install.cmd → 粘贴 vless:// 链接 → 按提示在 chrome://extensions 加载安装目录里的 extension/
- 默认:127.0.0.1:20808、cn-direct 分流、仅监听回环、不改系统代理;可关闭登录启动
- 管理:安装目录里的 start.cmd / stop.cmd / uninstall.cmd
- 详细说明:standalone/windows/README.md

构建独立版(需要一份官方 Xray 26.9.30 的 Windows 文件): powershell
node scripts/build-standalone.mjs --core "$env:USERPROFILE\.dsh\xray-proxy\bin"

dist/2xray-standalone-win64-v0.8.0.zip

macOS/Linux 用户可以直接使用同一个 Chrome 扩展 + 任意本地 Xray(监听 127.0.0.1:20808);独立的 Unix 安装器还没打包。

内置浏览器(Chromium)

DSH 内置浏览器走 Chromium 网络栈,不读 undici dispatcher,插件没法在运行时改它的代理。只支持启动参数: powershell & "E:\DS\DeepSeek Harness.exe" --proxy-server=http://127.0.0.1:20808

或运行 tools/launch-with-browser-proxy.cmd(自动查找 DSH 路径并透传 --proxy-server)。

必须先完全退出 DSH:单实例再次启动只会聚焦旧窗口,不会应用参数。系统代理/TUN(v2rayN)也能让 Chromium 生效,但那是全机代理,与本插件无关。

开发 powershell
cd E:\GPT\dsh-2xray
node --test --test-isolation=none test/.test.js

在受限沙箱里不要用 node --test test/:它按文件 spawn 子进程并使用管道,会被拒绝(EPERM);--test-isolation=none 在进程内运行。

可复跑的验证脚本:

| 脚本 | 用途 |
| scripts/check-config.mjs | 用真实 xray 二进制校验生成的配置;--legacy 兼容 1.8 之前的 tcp 命名 |
| scripts/probe-routing.mjs | 在 DSH 运行时验证「fetch 被接管 / 回环直连 / restore 后恢复」 |

完整证据与未验证项见 docs/EVIDENCE.md;验收闸门 0 的接管方式声明见 docs/AC-0.md。
预览分享卡片长什么样 ↓TwinsEarth/DSH-2Xray
分享 · DeepSeek Harness Hub
TwinsEarth/DSH-2Xray

托管 xray 子进程并把进程流量接入代理节点

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部