23 Commits
Author SHA1 Message Date
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
風雪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
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 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 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
風雪 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
風雪 6bbea65368 fix(ci): remove unused imports in node/api.go 2026-08-29 11:17:20 +08:00
風雪 3fc0503960 feat(node): prefer m-ui BuildShares for URI/client YAML export 2026-08-29 00:30:59 +08:00
風雪 708ffd0471 feat(node): ExportNodeURI via typed protocol registry BuildShare 2026-08-29 00:24:26 +08:00
風雪 e0a0afc577 feat(share): ExportNodeURI (uri/qr/client_yaml) + ExportClientYAML 2026-08-29 00:17:49 +08:00
風雪 dc62de00a3 feat(share): node share (uri/qr/client_yaml) + Access Profile in URIs 2026-08-29 00:10:35 +08:00
風雪 b36df8f029 fix: audit — access-token export uses PublicHost; robust IPv6 URI host 2026-08-20 15:05:21 +00:00
風雪 6b3a09a09c update
update
update
update
update
update
update
update
update
update
update
update
2026-08-19 12:49:36 +08:00
風雪 96153d4531 Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
Add files via upload
2026-08-18 17:14:11 +08:00
風雪 79fc469e11 feat(node): Access Profile public_host overrides URI export host 2026-08-18 16:58:16 +08:00
風雪 7e80804323 fix: expose node-only routes without duplicate CRUD 2026-08-18 13:48:19 +08:00
風雪 664885ce2a fix: expose node-only routes without duplicate CRUD 2026-08-18 13:47:43 +08:00
風雪 4d05dcf56f fix(node): resolve share host via public_url / loopback fallback 2026-08-18 11:11:05 +08:00
風雪 6c0a7ac649 fix(node): URI export empty hint; ensure non-nil uris 2026-08-18 11:01:57 +08:00
dzx941 6ac6b31a39 update 2026-08-16 11:16:41 +08:00
dzx941 5b4cb63a73 fix: align listener generation with mihomo official spec
- Port now supports mihomo ports format (single/range/list)
- Add explicit RoutingMark field support
- Auto-propagate UDP flag for vmess/vless/trojan/anytls/trusttunnel
- Update validator to accept port ranges
- Sync visual config and client converter port types
- Rename module from dzx941 to kazeyukiro
- Fix missing ExpiresAt in JWTClaims
- Fix nodeDTO Port type to string
- Fix frontend tsconfig and add missing dependencies
2026-08-16 09:21:42 +08:00