Skip to content

add native Shadowrocket proxy output - #637

Open
lonecoding wants to merge 6 commits into
sub-store-org:masterfrom
lonecoding:feat-shadowrocket-native-output
Open

add native Shadowrocket proxy output#637
lonecoding wants to merge 6 commits into
sub-store-org:masterfrom
lonecoding:feat-shadowrocket-native-output

Conversation

@lonecoding

@lonecoding lonecoding commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Adds opt-in native proxy output for Shadowrocket subscription and collection downloads. YAML-compatible output remains the default. Supported native protocol subsets include Shadowsocks, VMess, VLESS, HTTP/HTTPS, SOCKS5/SOCKS5-TLS, Trojan, Hysteria, Hysteria2, TUIC, Juicity, WireGuard, and Snell v2. Unsupported native configurations fail explicitly rather than losing connection parameters.

Input validation and review fixes

Native validation runs before lossy normalization and generic export filtering. It checks scalar types, VMess AEAD/alterId consistency, mandatory TLS flags, aliases, credentials, transport fields, and WireGuard addresses.

  • F — VMess cipher: unsupported explicit values such as bogus fail before falling back to auto. Supported values and equivalent cipher aliases remain accepted. Coverage includes Clash JSON, VMess share links, and Loon/Surge inputs. Loon/Surge parser fallbacks report the original failure before replacing the cipher, while non-native fallback behavior remains unchanged.
  • G — Hysteria2 obfs: Gecko and other unsupported obfs modes fail before the legacy Salamander rewrite. Salamander without its password is rejected before parse filtering can remove the node.
  • H — WireGuard CIDR: conflicting embedded and separate prefixes fail for both IPv4 and IPv6. Matching numeric/string prefixes remain valid.
  • I — Internal metadata: validation failures are non-enumerable, so JSON serialization and preview responses do not expose them. Built-in operator cloning explicitly carries the private state forward. Non-native production uses a clean export copy without clearing the source failure.
  • J — Reality: empty, incomplete, and populated Reality configurations are rejected for native output before cleanup or generic filtering. Native output bypasses generic silent export filters and lets its validation reject unsupported nodes.

Regression tests cover direct production, Clash parsing/normalization, native subscription and collection downloads, complete valid output strings, non-native JSON/YAML/URI exports, private metadata cloning, and cases previously filtered to empty output. Collection errors preserve the native validation reason.

Additional review fixes

  • Reject raw ports, hop-interval, and hop-interval-max before shared normalization can delete malformed values. Coverage includes objects, zero, false, empty strings, invalid strings and reversed ranges for Hysteria/Hysteria2.
  • Add native subscription/collection download regressions for Loon/Surge cipher fallback and malformed hopping options, with positive tests for supported cipher values and unchanged non-native cleanup.
  • Rerun the independent reproductions from the previous review: all now fail explicitly instead of emitting altered configuration.

Validation

  • Targeted producer/download tests: 131 passing.
  • Full local suite: 869 passing.
  • Isolated merge with upstream master 1b0e4a6f62ced7e3e083f7fa847f9e92aaef8ce9: no conflicts; 879 passing, including all final changes.
  • Local and isolated-merge builds: successful.
  • ESLint: no new diagnostics; 14 existing errors remain in both source trees.
  • git diff --check: passed.

Client verification

Real-device Shadowrocket import and connection testing has not been performed. Automated checks do not establish client-version compatibility or actual TLS/SNI, WebSocket, UDP, authentication, and WireGuard connectivity. Existing dependency build warnings remain. Maintainer review and client verification are still required before treating the feature as release-ready.

@xream

xream commented Sep 4, 2026

Copy link
Copy Markdown
Member

由于我对 shadowrocket 原生写法不熟悉, 以下内容由 Codex review 得出

produceNativeVmess 目前仅输出 passwordalterIdmethod,会丢弃 tlsnetworksni/servernamews-opts 等连接参数。

例如 VMess + WS + TLS 节点最终会变成:

VM=vmess,example.com,443,password=<uuid>,method=auto

输出中没有 TLS 和 WebSocket 配置,Shadowrocket 可能将其按普通 TCP VMess 节点解析,导致无法连接。

建议补齐 native VMess 所需的 TLS/WS 参数;如果某种配置暂时无法无损表达,则明确报错。同时增加 VMess + WS + TLS 的完整输出测试。目前测试只覆盖基础 TCP VMess,无法发现这个问题。


Since I’m not familiar with Shadowrocket’s native configuration syntax, the following finding was identified by a Codex review.

produceNativeVmess currently serializes only password, alterId, and method, while dropping connection-defining fields such as tls, network, sni/servername, and ws-opts.

For example, a VMess + WebSocket + TLS node is reduced to:

VM=vmess,example.com,443,password=<uuid>,method=auto

Without the TLS and WebSocket settings, Shadowrocket may interpret it as a plain TCP VMess node, causing the connection to fail.

Please either serialize the native VMess parameters required to preserve TLS/WS semantics or throw a clear error when a VMess configuration cannot be represented losslessly. A full-output regression test for VMess + WS + TLS should also be added, since the current test covers only basic TCP VMess.

@xream

xream commented Sep 4, 2026

Copy link
Copy Markdown
Member

VMess WS/TLS 的问题已经修复,但还有两个类似问题:

D — VLESS WebSocket 参数丢失

produceNativeVless 没有保留 ws-opts.path 和 Host/obfsParam。这可能导致 Shadowrocket 使用错误的 WebSocket 路径或 Host,造成连接失败。当前测试只使用 .include(...),无法发现完整输出中的字段缺失。

E — Shadowsocks v2ray-plugin 参数丢失

v2ray-plugin WebSocket 节点能通过当前的支持性过滤,但 produceNativeShadowsocks 会丢失 plugin、Host、path 和 TLS,最终退化为普通 Shadowsocks 节点,通常无法连接。

建议完整序列化所有可表示的连接参数;如果某种节点无法由 Shadowrocket native syntax 无损表示,应明确报错。同时请为这两个场景添加完整输出的回归测试。


The VMess WS/TLS issue has been fixed, but two similar issues remain:

D — VLESS WebSocket parameters are dropped

produceNativeVless does not preserve ws-opts.path or the Host/obfsParam value. This may cause Shadowrocket to use the wrong WebSocket path or Host and fail to connect. The current test uses only .include(...), so it does not catch fields missing from the complete output.

E — Shadowsocks v2ray-plugin parameters are dropped

A WebSocket v2ray-plugin node passes the current support filter, but produceNativeShadowsocks drops the plugin, Host, path, and TLS settings. This reduces it to a plain Shadowsocks node, which will normally make it unusable.

Please serialize every representable connection parameter. If a node cannot be represented losslessly in Shadowrocket native syntax, return a clear error instead. Full-output regression tests should also be added for both cases.

@xream

xream commented Sep 5, 2026

Copy link
Copy Markdown
Member

能否先自己 review 一下……现在这样有点折腾人。

重新 review 最新提交后,D/E 已解决,但以下问题仍然存在,建议在 merge 前处理:

F. VMess cipher 被静默改写

当前只校验 cipher 的类型,没有校验原始值是否受支持。例如 cipher: "bogus" 最终会被 normalize 成 method=auto。应在 normalization 前校验并拒绝不支持的 cipher。

G. Hysteria2 Gecko 被静默改成 Salamander

obfs: "gecko" 会被改写成 obfs: "salamander",同时将 "gecko" 当作 obfs-password,改变了原配置含义。如果 Shadowrocket native 不支持 Gecko,应在 normalization 前明确报错。

H. WireGuard CIDR 冲突未被发现

例如同时提供 ip: "10.0.0.2/32"ip-cidr: 24 时,最终会静默输出 /24。地址内嵌 CIDR 与独立 CIDR 不一致时应明确报错。

I. 内部校验错误泄漏到非 native 输出

_shadowrocket-native-validation-error 被作为 enumerable 字段写入 proxy,因此可能出现在普通 JSON 等非 native 输出中。该错误应存储为 non-enumerable metadata,或在所有非 native 输出前移除。

J. 不完整的 Reality 配置仍会被静默降级

reality-opts: {} 会被删除并输出成普通 VLESS;缺少 public-key 的 Reality 配置则会被过滤并返回空输出,而不是明确报错。这些情况都应在 normalization/filtering 前失败,避免改变或丢失原配置含义。


Could you please review the changes yourself first? The repeated back-and-forth is becoming a bit exhausting.

After reviewing the latest changes again, D/E have been fixed, but the following issues still remain and should be addressed before merging:

F. The VMess cipher is silently rewritten

The current validation only checks the type of cipher, not whether its original value is supported. For example, cipher: "bogus" is eventually normalized to method=auto. Unsupported ciphers should be rejected before normalization.

G. Hysteria2 Gecko is silently converted to Salamander

obfs: "gecko" is rewritten to obfs: "salamander", with "gecko" used as the obfs-password. This changes the meaning of the original configuration. If Shadowrocket native syntax does not support Gecko, it should fail explicitly before normalization.

H. Conflicting WireGuard CIDRs are not detected

For example, providing both ip: "10.0.0.2/32" and ip-cidr: 24 silently produces /24. A conflict between an embedded CIDR and a separate CIDR value should raise an explicit error.

I. Internal validation errors leak into non-native output

_shadowrocket-native-validation-error is stored as an enumerable proxy property, so it can appear in ordinary JSON and other non-native output. It should be stored as non-enumerable metadata or removed before every non-native output path.

J. Incomplete Reality configurations are still silently downgraded

reality-opts: {} is removed and emitted as ordinary VLESS, while a Reality configuration without public-key is filtered out and produces empty output instead of an explicit error. Both cases should fail before normalization/filtering to avoid changing or losing the original configuration semantics.

@xream

xream commented Sep 5, 2026

Copy link
Copy Markdown
Member

麻烦再看一下这些问题(有些好像 AI 判断不准确, 还是需要人工核实下)

以下是针对 0ea81bfb 的补充 review,编号接着之前的 J。

K — 域名解析后,native 导出失败

订阅启用内置 Resolve Domain Operator 后,产生的 _IP_IPv4_domain_resolved_resolved_ips 等内部字段会被 native 的未知参数检查拒绝,导致整份订阅报错。同一订阅的默认 YAML 导出正常。建议区分内部 metadata 与连接参数,避免正常 processor 的输出阻断 native 导出。

代码位置

L — Hysteria 的两种认证编码被混淆

两个节点分别使用 auth: "c2VjcmV0"auth-str: "c2VjcmV0",当前会生成完全相同的 auth=c2VjcmV0。但在 Clash/Mihomo 语义下,前者的认证字节是 Base64 解码后的 secret,后者则是明文 c2VjcmV0。这两个不同凭据被映射为相同输出,至少一种无法保留原意。请核实 Shadowrocket native 的认证编码要求,正确转换,或明确拒绝暂时无法无损表示的输入。

代码位置 · Mihomo 认证实现

M — WebSocket Host 的大小写变体被静默丢弃

检查逻辑接受任意大小写的 Host,但序列化只读取 Host / host。例如 VMess + WS + TLS 节点显式配置 SNI,并使用 ws-opts.headers: { HOST: "cdn.example.com" } 时,parse 和默认 Shadowrocket YAML 都保留该字段,native 输出却缺少 obfsParam,与未配置 Host 的输出相同。这可能影响 CDN/反向代理路由。建议统一大小写后读取,并检查重复 Host 的冲突。

代码位置 · Mihomo 使用 http.Header 处理 WS headers

N — key 大小写变体可以绕过原始输入校验

现有 parser 会把 -opts 及其子 key 归一化为小写,但新增的原始值校验发生在这之前,且只检查标准拼写。已复现:reality-opts: {} 会报错,改成 Reality-Opts: {} 却会输出普通 VLESS;ws-opts: { path: 42 } 会报错,改成 WS-OPTS: { PATH: 42 } 则被转换成 path=/42。建议先做不丢失原始值的 key 归一化和冲突检查,再在值转换前进行 native 校验。

代码位置

当前完整测试为 869 passing,以上情况已做代码级独立复现;尚未进行 Shadowrocket 真机导入和连接测试,实际客户端影响仍需人工核实。


Please take another look at these issues (some of the AI's assessments may be inaccurate, so they still need human verification).

This follow-up review targets 0ea81bfb, continuing the labels after J.

K — Domain resolution causes native export to fail

Enabling the built-in Resolve Domain Operator adds internal fields such as _IP, _IPv4, _domain, _resolved, and _resolved_ips. Native export treats these as unsupported options and fails the entire subscription, while the same subscription exports successfully to the default YAML format. Please distinguish internal metadata from connection parameters so normal processor output does not block native export.

Code

L — Hysteria's two authentication encodings are conflated

Otherwise identical nodes using auth: "c2VjcmV0" and auth-str: "c2VjcmV0" currently produce the same auth=c2VjcmV0. In Clash/Mihomo, however, the first represents the Base64-decoded bytes secret, while the second represents the literal bytes c2VjcmV0. Mapping these different credentials to identical output cannot preserve both meanings. Please verify Shadowrocket native authentication encoding and convert appropriately, or explicitly reject inputs that cannot yet be represented losslessly.

Code · Mihomo authentication implementation

M — WebSocket Host casing variants are silently dropped

The validation accepts any casing of Host, but serialization only reads Host / host. For example, a VMess + WS + TLS node with explicit SNI and ws-opts.headers: { HOST: "cdn.example.com" } retains that field through parsing and default Shadowrocket YAML export, yet native output omits obfsParam and matches the output of a node with no Host configured. This may break CDN/reverse-proxy routing. Please normalize header names before reading them and detect conflicting duplicate Host values.

Code · Mihomo handles WS headers through http.Header

N — Key casing variants bypass raw-input validation

The existing parser lowercases -opts keys and their child keys, but the new raw-input validation runs before that step and only checks canonical spellings. Reproductions: reality-opts: {} throws, whereas Reality-Opts: {} produces ordinary VLESS output; ws-opts: { path: 42 } throws, whereas WS-OPTS: { PATH: 42 } is converted to path=/42. Please canonicalize keys without losing their original values, check conflicts, and then validate native input before value conversion.

Code

The full suite currently reports 869 passing, and the cases above were independently reproduced at the code level. Shadowrocket import and connection testing on a real device has not been performed; actual client impact still needs human verification.

Handle native input cases that could be changed or dropped during
normalization, including conflicting aliases, duplicate options and
URI-specific aliases.

Add regression tests for parsing, native output and download routes.

Keep the latest upstream QX password parsing changes while preserving
the native validation added by this PR.
@lonecoding

Copy link
Copy Markdown
Contributor Author

You're right. I went through it again and found a few things I had missed before. I've fixed the issues I was able to reproduce and added regression tests around the native parsing and output paths.

I also synced with the latest master and kept the upstream QX password parsing changes while preserving the native validation added by this PR. The targeted tests (791), full test suite (1531), and build are all passing.

Thanks for pointing this out and for taking the time to review the changes. I'll keep working on any remaining issues, though there may be some cases that I'm not able to resolve on my own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants