94 Commits
Author SHA1 Message Date
kazeyukiro bd914bc308 revert: remove HY2 URI param refactor
Keep sing-box CN geosite/geoip routing; restore previous HY2 URI export.
2026-10-03 07:08:22 +00:00
kazeyukiro 7cc933e073 feat(sub): sing-box CN geo split; harden HY2 URI for Loon
sing-box subscriptions now route geosite-cn + geoip-cn (remote rule-sets)
and private IPs to direct, matching Clash CN defaults. HY2 share query
params are centralized: always sni/alpn=h3, insecure+allowInsecure when
skip-cert, optional mport for hop — aligned with official URI scheme and
Loon parsers.
2026-10-03 07:06:14 +00:00
kazeyukiro 5539873cb9 fix(export,uri): Reality SNI, IP-without-PEM, UUID fallback
- ApplyToConfig no longer overwrites Reality camouflage SNI with
  cert-derived IP; Reality always skip-cert
- ResolveClientSNI: IP dial target without PEM uses IP SNI (not AccessSNI
  domain like www.bing.com which forced broken verify)
- VLESS/VMess UUID from username when uuid field empty
- Credential rows always populate uuid for panel UUID-in-username users
2026-10-03 02:12:31 +00:00
kazeyukiro c912769199 fix(sub,uri): stop wiping users; restore same-TLS share recovery
ClientURIsWithCredentials no longer writes an empty users map over
Config (this dropped HY2/AnyTLS/Mieru passwords and emptied subscriptions).
HY2 URI export accepts password/auth when users is absent. Subscription
may still recover via ExportShare* only when primary fails, and Share
already resolves SNI via export.BuildProfile — not a divergent policy.
2026-10-03 01:51:58 +00:00
kazeyukiro 842fd72ea4 refactor(export): unify TLS profile for URI, share and subscription
Introduce internal/export as the single cert/SNI/skip-cert resolver.
ClientURIs, DecodeNodeModel, resolveShareTLS and Clash proxy TLS helpers
all consume BuildProfileFromConfig so formal IP certs always emit sni and
subscriptions no longer diverge from share links on certificate identity.
2026-10-03 01:25:14 +00:00
kazeyukiro 05816736a0 fix(share,uri): load certstore PEM and always emit HY2 sni
DecodeNodeModel now sets listener ID and materializes certificate paths
or certstore PEM. resolveShareTLS expands paths and never leaves SNI
empty on IP hosts. HY2 share/URI default alpn=h3 and force sni=host
when unresolved so Loon no longer sees bare host:port links.
2026-10-03 01:14:45 +00:00
kazeyukiro b36f930099 fix(sub,share,uri): unify SNI/skip-cert across all protocol exports 2026-10-02 15:15:34 +00:00
kazeyukiro fc14d5bbcd ui(dashboard): mono total-rate chart; drop username from share URI names
Speed chart is a single muted total (up+down) series; metric sparklines
use secondary text color only. HY2/AnyTLS share fragments use node name
without appending the panel username.
2026-10-02 03:40:06 +00:00
kazeyukiro 7d39952425 fix(sub): unify v2ray/share URI TLS for all protocols
Introduce resolveURITLS for HY2/TUIC/AnyTLS/tlsParams so SNI follows
certificate SANs, formal certs never force insecure, Reality links omit
insecure flags, and multi-user HY2/AnyTLS names include the username.
2026-10-02 01:30:49 +00:00
kazeyukiro 741b5734cd fix(sub): SNI must match certificate SANs (incl. LE IP certs)
ResolveClientSNI only accepts AccessSNI/PublicHost when they match the
leaf certificate; PreferredSNIFromCert returns IP SANs for LE IP certs.
URI access profile no longer blindly injects mismatched AccessSNI
(e.g. www.bing.com on a cert for 85.x.x.x).
2026-10-01 09:15:21 +00:00
kazeyukiro 32c6aee335 fix(sub): stop forcing skip-cert-verify for formal certificates
Rewrite DecideClientSkipCertVerify: CA-signed / non-self-signed PEMs never
force insecure; domain endpoints without embedded PEM rely on system CAs;
IP-only without PEM still skips. URI export loads certstore PEM, prefers
SNI as verify identity, and HY2/VLESS share paths use the same decision.
2026-10-01 08:59:43 +00:00
kazeyukiro bbe10da416 fix(release,hy2): sane auto release notes; Loon-friendly HY2 share links
Release workflow: previous stable tag only, merge-base range, first-parent
log, noise filter, and per-section caps so notes stay readable.

Hysteria2 share/URI: resolve SNI from access profile/public host, emit
allowInsecure alongside insecure, encode userinfo via url.User so Loon
and similar clients can complete TLS.
2026-10-01 08:41:28 +00:00
風雪 8fe2f9ebb3 test: provide required listener fields 2026-09-30 21:48:17 +08:00
風雪 f75619f0d1 style: gofmt URI extended file 2026-09-30 21:45:35 +08:00
風雪 b061b1436e fix: import strings for Sudoku URI serialization 2026-09-30 21:42:05 +08:00
風雪 faf53ae934 fix: remove duplicate Mieru validation case 2026-09-30 21:42:02 +08:00
風雪 266a3d20b0 fix: merge protocol-specific listener validation cases 2026-09-30 21:38:25 +08:00
風雪 e36d44bb4b test: format Sudoku URI regression test 2026-09-30 21:38:19 +08:00
風雪 af609b746b fix(tests): restore validator tests and append listener coverage 2026-09-30 21:32:01 +08:00
風雪 4f864d8859 test(listener): validate documented protocol values 2026-09-30 21:31:35 +08:00
風雪 5c98af6ca4 fix(listener): validate Sudoku Mieru and TrustTunnel values 2026-09-30 21:31:24 +08:00
風雪 5792bd1978 test(uri): cover Sudoku nested URI serialization 2026-09-30 21:30:03 +08:00
風雪 726781b7be fix(uri): serialize Sudoku listener options without object coercion 2026-09-30 21:30:00 +08:00
Z User d49940d510 fix(ci): restore VMess host fallback from SNI/server addr; fix test
Reverted the previous 'fix' (233d806) that removed the host fallback.
VMess 'host' field IS part of v2rayN's VmessQRCode schema — it
specifies the WebSocket Host header, separate from 'add' (server addr).

The CDN fix (b317042) correctly added the fallback:
  1. ws-headers.Host (explicit)
  2. SNI / servername (TLS config)
  3. server address (for CDN where WS Host = CDN domain)

The test TestVMessURIOmitsWebSocketHostWhenUnset was WRONG — it
asserted host should be absent when no ws-headers.Host is set, but
the fallback to server address is the intended behavior for CDN.

Renamed test to TestVMessURIFillsHostFromServerAddress and inverted
the assertion: host should equal the server address when no explicit
Host header or SNI is configured.

Tests: 24/24 packages PASS.
2026-09-30 12:56:09 +00:00
Z User 233d8067f1 fix(ci): VMess URI host field only set when explicitly configured
The CDN access fix (b317042) added a fallback that set the VMess
'host' field from the server address when no ws Host header or SNI
was configured. This broke TestVMessURIOmitsWebSocketHostWhenUnset
which correctly asserts that v2rayN's VmessQRCode schema has no
'host' field — the server address goes in 'add', not 'host'.

Fix: only set obj["host"] from:
  1. Explicit ws-headers Host (user configured)
  2. SNI/servername (TLS config)
Never from the server address (that's what 'add' is for).

Tests: 24/24 packages PASS (including all 4 VMess URI tests).
2026-09-30 12:37:34 +00:00
kazeyukiro c6f7c02cdc fix(sub): smart TLS/SNI/fingerprint for all protocol exports
Post-process every client proxy (YAML) with access-profile SNI, CDN TLS
for WS/gRPC, and fingerprint. URI path already uses applyURIAccessProfile
globally; VMess Host and AnyTLS fp defaults tightened.
2026-09-30 08:32:39 +00:00
kazeyukiro 8507b8636c fix(sub): audit URI and YAML export for CDN access fields
Fill security=tls, sni, fp, and ws host from AccessSNI/PublicHost for
domain+WS share links and client YAML; default fingerprint chrome.
2026-09-30 08:28:16 +00:00
kazeyukiro fec9733baa docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
docs: remove third-party panel name references from copy
Drop  and external m-ui product names from docs, UI strings,
and code comments. Keep functional descriptions of server egress and WARP.
2026-09-29 15:51:57 +00:00
kazeyukiro b664e57117 fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
fix(share): CDN VLESS/WS export Host + path defaults
When ws-headers Host is empty, client URI/YAML/subscription now fall
back to SNI or public host. Always emit WS path (default /). Panel
gains an explicit WebSocket Host field for CDN setups.
2026-09-28 14:27:44 +00:00
風雪and風雪 35f4c521c3 fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
fix(vless): stop exporting flow on transports that cannot carry it (#87)
* fix(vless): stop exporting flow on transports that cannot carry it
The form promised that flow is cleared for ws/grpc/xhttp and the tooltip still
says so, but exported links kept carrying flow=xtls-rprx-vision. Two separate
reasons, both fixed.
The clear came too late to matter. formValuesToConfig wrote `flow` into the
config inside the protocol switch, and only afterwards did the "Vision is
TCP-only" rule run — where it mutated the form state, not the config it had
already been copied into. The transport is now resolved before anything writes,
and the drop also removes a flow inherited from the previous config, so changing
a listener's transport actually takes effect.
The URI generators never asked. Every one of them emitted whatever flow it found:
- node: vlessURIs decided flow before resolving the transport, so the type it
had just computed was irrelevant to the decision. It now resolves the
transport first. ClientURIsWithCredentials also merged the listener-level
flow onto every user on protocol alone, which reintroduced the value the
form had dropped.
- protocol: the share URI and the client YAML both wrote spec.Flow unguarded,
even though TransportSpec knows the network. Same for the server-side
compile, which pushed the listener default onto users — Mihomo rejects that
combination outright, so this one takes the listener down.
- mui: BuildVLESSShare set flow unconditionally, and bridge.go injected the
listener default into users before it was known to be applicable.
- converter: copyTransport resolved `network` but left a stale flow behind.
The rule is now one predicate per representation: TransportSpec.CarriesFlow,
VLESSHandlerSpec.CarriesFlow, and protocol.TransportCarriesFlow for raw configs.
TCP/raw keeps Vision; ws, grpc, xhttp, mkcp and mekya never emit it.
Tests pin each path in both directions — a stale flow disappears on ws/grpc/
xhttp, and a genuine TCP listener keeps its flow. The frontend one runs the real
form builder and fails, in three of five cases, against the previous ordering.
* fix(uri): carry vmess ws Host and align hysteria2 TLS flags across user shapes
Auditing the remaining exporters after the flow fix turned up two more places
where the link disagreed with the listener.
vmessURIs built its JSON by hand instead of going through transportParams, so a
ws listener that pins a Host header exported only the path: the client had
nothing to send as Host and the listener was unreachable through the link, even
though the client YAML built from the same config worked. It also dropped
allowInsecure, which vless and trojan both carry — a listener whose certificate
the client cannot verify exported a link that failed the handshake. The repeated
tlsParams() calls were hoisted while touching this.
hysteria2URIs reads users either as rows or as a map, and the two branches
disagreed: one asked certutil.ShouldSkipCertVerify and emitted only `insecure`,
the other asked clientSkipCert and emitted `insecure` plus `allowInsecure`. The
same listener therefore produced different links depending on how its users were
stored. Both now use clientSkipCert, the helper intended for share URIs, and
emit both keys.
Not fixed, for the record: an SS listener with simple-obfs exports a bare ss://
link with no `plugin=` entry, so the obfs configuration is lost. The client YAML
does translate it (plugin: obfs). Fixing the URI means committing to a SIP002
plugin encoding that varies between clients, which is worth deciding separately
rather than guessing at here.
* i18n: complete zh-CN and guard against the locale regressions already seen
zh-CN was missing seven listener hints that en.ts declares, so those tooltips
rendered in English for the primary UI language. Translated: client_auth_cert,
client_auth_type, ech_key, enable_pure_downlink, mux, padding and table_type.
The rest of the catalogue is deliberately left alone. Seventeen locales each lack
65 keys that en.ts has; the provider already resolves those from en.ts, so they
degrade to English rather than breaking, and filling 1100 strings with
unreviewed machine translation would be worse than the fallback. The test prints
the per-locale gap so it stays visible.
The new locale test guards the regressions this directory has actually hit: a
duplicated key inside one object (which breaks the type check), and a key renamed
in en.ts leaving an orphan behind. It also asserts zh-CN stays complete, since a
gap there reads as a defect rather than as translation lag.
* i18n: declare the 24 keys the UI asks for but no locale ever defined
Twenty-four keys were referenced from components without being declared in any
locale, so t() fell through to its hardcoded fallback. Where a fallback existed
that merely meant the string could never be translated; where none existed —
protocolHint, access_sniHint, jlsHint, shadowTlsHint and the rest — the tooltip
either rendered the raw key, "listeners.protocolHint", or disappeared entirely,
because fieldTip() suppresses a value that equals the key it looked up.
en.ts takes the text the code was already falling back to, so nothing changes on
screen; the strings just become translatable. The few without a fallback were
written from the surrounding context — the field they annotate, its placeholder,
and the wording of the neighbouring hints. trafficMultiplierHint is the exception:
its fallback was Chinese, which is wrong for the English catalogue, so it now has
a real English string.
zh-CN gets all twenty-four, which keeps it complete against en.ts.
The locale test now reads through the TypeScript AST. It previously parsed line by
line and these files contain minified regions — several "key":"value" pairs on one
line — so a whole block of keys was invisible to it, which is how this gap stayed
hidden. It also gained the assertion that would have caught the bug directly:
every dotted key handed to t() or fieldTip() must be declared in en.ts.
The remaining languages still lag, now by 89 keys rather than 65, and still fall
back to English; the test prints the per-locale figure.
* fix(uri): drop the vmess allowInsecure field the reference client ignores
The vmess skip-certificate field was added on internal consistency alone —
vless and trojan carry allowInsecure in their query strings, so vmess looked
like an oversight. Checked against the client that actually parses these links,
it is not one.
v2rayN's VmessQRCode model declares exactly: v, ps, add, port, id, aid, scy,
net, type, host, path, tls, sni, alpn, fp. There is no skip-certificate field
at all — neither allowInsecure nor insecure — so the field was dead weight in a
payload that v2rayN reads field by field. v2rayNG reads `insecure` instead, and
Xray-core has removed allowInsecure in favour of pinnedPeerCertSha256, so there
is no spelling that the reference clients agree on. Emitting one would have
produced a link that looks configured but is not.
The ws `host` fix stands: v2rayN's own documentation lists "ws host" as the
meaning of that field, so dropping it broke ws listeners that pin a Host.
The conformance test now pins the emitted payload to v2rayN's field set, so a
future field copied over from another protocol fails instead of being silently
ignored by every client.
Deliberately left alone, both confirmed against the official scheme and worth
deciding separately:
- hysteria2 links carry up/down. The Hysteria 2 URI specification is explicit
that the parameters "should never include client modes ... or bandwidth
values". Removing them changes behaviour for anyone relying on the link, so
it is flagged rather than removed. `insecure` is the official parameter;
allowInsecure is kept alongside it purely for clients that read that name.
- an SS listener with simple-obfs still exports a bare ss:// link with no
plugin entry, losing the obfuscation the client YAML does translate.
Also noted while checking: tuicURIs defaults congestion_control to bbr where
sing-box documents cubic as its default. Ours is set explicitly, which is
valid, but the comment claiming it is what "most TUIC clients" use is not
sourced.
* fix(uri): keep vmess allowInsecure after all
Reverts the revert. Dropping it was correct on the evidence and wrong on the
consequences: v2rayN ignores the field, v2rayNG spells it `insecure`, and
Xray-core has moved to pinnedPeerCertSha256 — but the clients that do read
allowInsecure are precisely the ones whose handshake fails without it, and an
ignored extra key costs the others nothing. Removing it would have traded a
working link for a tidy payload.
The conformance test keeps its guard, with allowInsecure recorded as an
explicit, justified exception rather than a silent inclusion: the field set is
still pinned to v2rayN's model, so any further field copied over from another
protocol has to argue its way in.
* fix(uri): restore the vmess ws Host in registry YAML, and stop dropping share links
Two leftovers from auditing the exporters end to end.
The registry's VMess compiler built ws-opts with the path only, so a listener
that pins a Host exported a client config with nothing to send as Host. The
vless YAML path in the same file already nests it under `headers` per the mihomo
wiki, so this just mirrors that — and the converter has always carried it, which
is how the difference went unnoticed.
The node URI share endpoint also had a tier that could not be reached. It walks
three tiers — m-ui, the registry, then the legacy builders — and the second one
gates the third on having neither a URI nor client YAML. But the registry
implements URI export for a different protocol set than the legacy builders: it
compiles vmess, snell, shadowquic, mieru, sudoku and trusttunnel to client YAML
without ever emitting a URI, so those listeners arrived at the third tier with a
YAML in hand and the condition already false, and reported no link at all even
though ClientURIsWithCredentials exports one. The third tier now runs whenever
the URI is still missing, and only fills in YAML that earlier tiers did not
already provide.
---------
Co-authored-by: 風雪 <kazeyukiro@users.noreply.github.com>
2026-09-28 00:36:07 +08:00
kazeyukiro 0ad79c46af fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
fix(share): derive SNI from formal cert when Access SNI is blank
TUIC already fell back to public host / connect host. Now also:
- PreferredSNIFromCert / ResolveClientSNI in certutil
- AnyTLS + TUIC share use cert SAN/CN when SNI unset
- anytls URI export uses same resolution so LE certs verify without skip
2026-09-22 00:40:00 +08:00
kazeyukiro 14889ffc61 revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
revert(share): AnyTLS URI back to previous panel format
Keep PathEscape password, sni/fp/insecure/allowInsecure and idle-session
query params instead of the strict anytls-go official scheme.
2026-09-22 00:15:00 +08:00
kazeyukiro 446caa37d4 fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
fix(share): AnyTLS URI per official anytls-go scheme
anytls://[password@]host[:port]/?sni=...&insecure=1#name
- password as URI userinfo (percent-encoded)
- only official query keys: sni, insecure
- fragment for display name
- subscription ExportShare now emits URI (was YAML-only)
2026-09-21 23:55:00 +08:00
kazeyukiro 37a5fa0284 feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
feat(traffic): per-node usage + traffic multiplier billing
- Listener.traffic_multiplier (default 1): billable = raw × multiplier
- user_node_traffics: raw per user per node; user quota uses billable totals
- Collector attributes deltas by inbound name; resets clear node rows
- API GET /users/:id/node-traffic
- UI: node multiplier on Listeners; Users node-traffic modal (desktop+mobile)
2026-09-20 23:50:00 +08:00
kazeyukiro f26fde0431 fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
fix(uri): TUIC/HY2 share links for NekoBox — host-aware skip-cert, defaults
- DecideClientSkipCertVerify using connect host (IP vs domain vs SAN)
- Default congestion_control=bbr, udp_relay_mode=native, alpn=h3
- Default SNI to domain host when unset; emit allow_insecure + allowInsecure
2026-09-19 23:15:00 +08:00
kazeyukiro 0c5338db2e fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
fix(vless): do not require username on uuid-mode users
Fixes #79 — creating a VLESS listener failed with
"vless listener users[0] requires username" because autofill only minted
uuid (matching Mihomo) while ValidateNode required a panel username.
- uuid-mode validation (VLESS/VMess): only uuid is required
- autofillUUIDUsers: default username "user" when empty
- EnsureListenerCredentials: backfill username on existing uuid rows
2026-09-18 22:20:00 +08:00
Z User b221a2d731 fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
fix(tuic): unblock v5 listener creation + complete v4 URI params
Audit against official MetaCubeX wiki found 6 TUIC issues. Fixes:
CRITICAL-1 (node/validator.go:150): mapUserProtocols missing 'tuic-v5'
tuic-v5 listeners with wiki-standard 'users: {UUID: pwd}' map were
rejected with 'field users does not accept an object'. This ran
BEFORE validateProtocolSpecific (which would pass), so creating a
tuic-v5 listener via the panel failed at validation time.
Fix: add 'tuic-v5' to mapUserProtocols. tuic-v4 stays out (v4 uses
token, any users on v4 is rejected by schema).
HIGH-2 (node/uri_builders.go:299-313): v4 URI emitted zero query params
tuicURIs v4 path returned 'tuic://token@host:port#name' with NO
alpn / sni / congestion_control / udp_relay_mode / allow_insecure.
Fix: build shared params map ONCE (covers v4 token + v5 uuid paths).
HIGH-3 (protocol/compile_modules.go:141-148): v4 compiler dropped
extra tokens
'token: [T1, T2, T3]' was reduced to 'token: [T1]' at compile time,
silently dropping T2/T3 even though mihomo accepts multi-token v4.
Fix: new allStringTokens() helper preserves every non-empty token.
MEDIUM-4 (node/uri_builders.go): URI query param naming
tuicURIs used hyphen-case (congestion-controller, udp-relay-mode)
but mihomo URI parser expects snake_case. BuildShare already used
snake_case — only the user_sub URI path was wrong.
Fix: map hyphen-case listener keys to snake_case URI keys
(congestion_control, udp_relay_mode, max_udp_relay_packet_size,
bbr_profile, allow_insecure).
MEDIUM-6 (protocol/share_modules.go:633-634): BuildShare skipCert
too aggressive
any cert + empty SNI forced skipCert=true, breaking formal
(Let's Encrypt) cert listeners when operator forgot SNI.
Fix: default SNI to server host; only skipCert for panel self-signed
PEMs (O=3m-ui marker) or explicit skip-cert-verify: true.
LOW-7 (mihomo/config/listener_schema_registry.go:178-182): tuic-v4
schema whitelisted 'users'
v4 is token-only per wiki; symmetric to tuic-v5 omitting 'token'.
Fix: remove 'users' from tuic-v4 Fields.
Tests: 4 new regression tests in node/validator_test.go covering
tuic-v5 map users (accept), tuic-v4 token array (accept),
tuic-v4-with-users (reject), tuic-v5-with-token (reject).
Full suite 23/23 packages PASS.
2026-09-16 14:49:45 +00:00
kazeyukiro 42cd620905 fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
fix(tuic-v4): guarantee non-empty token on create and apply
- credentials.EnsureListenerCredentials: tuic-v4 mints token; tuic-v5 users
- ValidateNode covers tuic-v4/v5; empty token[] is not "configured"
- Autofill calls EnsureListenerCredentials after defaults
- Config generate mints token if tuic-v4 still empty before Mihomo apply
2026-09-16 15:45:00 +08:00
kazeyukiro b1a34e1384 fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
fix(sub): export tuic-v4/v5 in subscription links
ClientURIs only handled protocol "tuic", so tuic-v4/v5 were skipped in
base64/v2ray subscription (silent continue) while single-node URI path
often still worked via other exporters.
- ClientURIs + ClientURIsWithCredentials accept tuic-v4/v5
- Prefer Config token/users for TUIC/SS/snell/… in listenerToProxies
- user.credentialsFromListenerConfig reads TUIC token and tuic-v4/v5 users
2026-09-16 13:25:00 +08:00
Z User a1cbef1d74 fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
fix: UNIQUE/soft-delete hygiene across tokens, HWID, config, templates, node delete
- Hard-delete access tokens and HWID devices so unique indexes free immediately
- Revive soft-deleted HWID rows and visual-config fragments on re-register/upsert
- Align node.Service.Delete with pre-delete name free; Unscoped clone rollbacks
- Clearer template name collision errors
2026-09-10 15:34:34 +00:00
Z User 7e3e0ab7d2 fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
fix(node): fall through share exporters when ClientYAML is empty
mui.BuildShares / protocol.ExportShares could succeed with URIs but empty
ClientYAML; connection-check then aborted with client YAML unavailable and
never reached converter.ExportClientYAML. Prefer any non-empty YAML across
tiers, then legacy converter.
2026-09-09 17:06:59 +00:00
phil d0b53dcc9e feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
feat(listeners): verify client connectivity and expose diagnostics
Inspect listener readiness and test exported client endpoints with isolated Mihomo clients. Preserve public addresses, ports and protocol settings, report diagnostic stages, and repair generated access SNI fields.
2026-09-09 21:50:55 +08:00
Z User 3279d1c963 feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
2026-08-31 06:12:47 +00:00
Z User da5a0f67d1 fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
2026-08-30 15:14:36 +00:00
Z User 0754e6a9d6 fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
2026-08-30 13:04:59 +00:00
Z User e445bcd323 fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
2026-08-30 10:34:48 +00:00
Z User fad6e612a5 fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
2026-08-30 08:30:38 +00:00
Z User c817650597 fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
2026-08-30 04:13:26 +00:00
Z User a379c33159 fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
2026-08-30 00:06:00 +00:00
風雪 58f69e3f9d fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
fix(backend): XFF-aware rate limiting, host trust, error sanitization
- M-3: clientIdentifier now trusts X-Forwarded-For (right-most valid
IP) only when the direct RemoteAddr is loopback/private/link-local
(typical same-host reverse proxy). Public exposure still rejects
spoofed XFF, restoring per-client granularity behind nginx/caddy.
- LOW-10: node ExportNodeURI fell back to c.Request.Host (client
controlled) even when publicURL was configured. Now publicURL wins
over client headers when set.
- LOW-11: Replace direct err.Error() in 500/502 responses with generic
messages; real errors logged server-side via log.Printf. Prevents
leakage of DB paths, SQL fragments, remote server responses.
4xx business messages (invalid id, not found) preserved.
2026-08-29 09:39:00 +00:00