Commit Graph
422 Commits
Author SHA1 Message Date
Z User fbca88a534 fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
fix(config): align listener/client fields with MetaCubeX wiki (round 2)
Complete field-level audit against https://wiki.metacubex.one/ for 8 protocols
(shadowsocks/vmess/vless/trojan/hysteria2/tuic-v4/tuic-v5/sudoku).
HIGH (8):
- VLESS listener: strip client-only `encryption` field (server uses `decryption`)
- mux-option: drop undocumented `brutal-opts` from all 9 listener schemas (keep `brutal` map)
- vmess/vless/trojan listener: drop fabricated top-level `ws-headers` (client uses ws-opts.headers)
- 3m-ui share: nest ws-opts.Host under headers map per proxies-transport
- hysteria2 listener: drop `handshake-timeout`/`max-idle-time` (client/TUIC only)
- m-ui hysteria2Listener: drop 6 quic-go fields (cwnd/udp-mtu/initial-stream-receive-window/...)
- tuic-v5 listener: drop `token` (v4-only per wiki)
- sudoku listener: drop httpmask.path_root/tls/host/multiplex (wiki uses dash path-root only)
MEDIUM (11):
- VMess listener: strip top-level `alterId` (per-user form in users[] only)
- VMess/VLESS/Trojan listener: drop undocumented top-level `udp`
- SS kcptun: emit plugin:kcptun + plugin-opts from kcp-tun block (m-ui + converter paths)
- m-ui hysteria2ClientProxy: add ports/hop-interval/obfs-min/obfs-max/name-cert-verify/fingerprint/handshake-timeout
- m-ui hysteria2RealmConfig: drop client-side `proxy` (listener-only)
- m-ui classicClientProxy (VMess/Trojan): add global-padding/authenticated-length/fingerprint/name-cert-verify/tlsmirror-opts/smux
- m-ui vlessClientProxy: add fingerprint/name-cert-verify/smux
- m-ui websocketClient: add headers map (was only path)
- converter TUIC v4+v5: add ip + name-cert-verify copy
- converter mekya-opts: add polling-interval-initial + h2-pool-size
- converter hysteria2: add ports/hop-interval/obfs-min-packet-size/obfs-max-packet-size
- converter applyClientWrappers: add tlsmirror-opts branch
LOW:
- Update TestGenerateListenersUsesModelTLS to assert wiki-correct VLESS (no top-level tls)
- SS client: add udp-over-tcp/udp-over-tcp-version/ip-version/smux (both paths)
- grpc-opts: add grpc-user-agent/ping-interval/max-connections/min-streams/max-streams (both paths)
- ech-opts: pass-through in copyClientTLS (both paths)
- Schema whitelist: add mekya-config.polling-interval-initial/h2-pool-size + grpc-opts 5 fields
(client-only metadata accepted by panel; mihomo ignores unknown listener fields)
Verification:
- go build ./... → PASS
- go vet ./... → clean
- go test ./... → 16/16 packages PASS
Refs: wiki-audit-1 (33 findings) + wiki-audit-2 (4 R-M follow-ups) in worklog.
2026-09-03 15:14:39 +00:00
Z User d393114a3b fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
fix(config): align listener/client fields with MetaCubeX wiki
- Client SS type is "ss" (not listener name shadowsocks)
- Drop TUIC/Hy2 server-only keys from client export
- Strip simple-obfs enable from SS plugin-opts
- Do not emit top-level tls on VLESS/VMess/Trojan listeners
- Strip client-only keys (sni, skip-cert-verify, *-opts) from listener YAML
2026-09-02 15:12:32 +00:00
Z User 18a8572530 fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
fix(tls): durable per-listener certs on disk across panel updates
Self-signed PEMs that only lived in ephemeral YAML were re-minted on every
upgrade, invalidating all client trust. Store certificate/private-key under
/var/lib/3m-ui/listener-certs/{id}.crt|.key, restore into config when DB
lacks a pair, and never rotate a complete pair already on disk or in DB.
2026-09-02 14:44:17 +00:00
Z User a607c6a4f4 fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
fix(tls): respect allow-insecure; only persist listener config when changed
Forcing a self-signed cert and clearing allow-insecure broke nodes that
intentionally run TLS-off behind nginx/caddy. Skip cert injection when
allow-insecure is true. Avoid rewriting every listener row on each apply.
2026-09-01 16:29:38 +00:00
Z User 01a638ba04 fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
fix(config): do not fail entire apply when one listener is invalid
A single bad listener (e.g. Snell obfs-opts without Host) previously
aborted GenerateFinalConfig, so Mihomo could not load any node after
panel update. Skip broken listeners, log them, and still emit the rest.
2026-09-01 16:24:59 +00:00
Z User 9353b94dad fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
fix(snell): strip incomplete obfs-opts (missing Host) before Mihomo -t
Mihomo rejects obfs-opts without Host. Normalize mode+host, drop incomplete
blocks before and after compile, persist cleaned config, and require host
in the form when mode is selected.
2026-09-01 16:17:40 +00:00
Z User 9724185d05 fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
fix(snell): drop incomplete obfs-opts missing host/mode
Mihomo rejects listeners with obfs-opts that omit Host. Only emit the
block when both mode and host are set, and sanitize partial blocks on
save/config generation.
2026-09-01 16:11:26 +00:00
Z User 9c1d714aa0 fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
fix(tuic): cert autofill + client export for tuic-v4/tuic-v5
Panel protocol kinds tuic-v4/tuic-v5 were missing from TLS ensure paths,
so self-signed certs were not injected. Client export only matched "tuic"
and emitted invalid type tuic-v4/v5. Normalize export type to tuic and
restore UDP support for hy2/shadowquic/tuic variants.
2026-09-01 15:29:17 +00:00
Z User c742c36bcf fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
fix(tls): persist autofilled self-signed certs to listener DB
Runtime ensureListenerTLSMaterial minted a new cert pair on every config
generation when the DB row had no certificate. Panel updates/reloads then
rotated server identity and broke clients that verify TLS (or pinned
fingerprints). Write the filled certificate/private-key back to the
listener config once so subsequent reloads stay stable.
2026-09-01 14:55:53 +00:00
Z User 273d1f7019 fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
fix(trojan+reality/mieru): SNI sync + mieru TLS exclusion
Two issues fixed:
== Trojan+Reality: subscription missing SNI ==
copyClientTLS derives 'servername' from reality-config.server-names
but trojan client proxy uses 'sni' (not 'servername'). The trojan case
only copyOption'd 'sni' (which was stripped by sanitizeServerConfig),
and the fallback check (sni==nil && servername==nil) was short-
circuited because servername was already set from reality.
Fix: at the end of copyClientTLS, if sni is empty but servername has
a value, copy servername → sni. This ensures all protocols that use
'sni' (trojan, hysteria2, tuic, etc.) get the correct TLS hostname
from reality server-names, jls dest, or other wrapper sources.
== Mieru: client YAML has invalid TLS fields ==
Mieru is a plain TCP/UDP proxy — no TLS at all. But copyClientTLS
was called for mieru (not in the exclusion list), injecting tls:true,
sni, skip-cert-verify etc. from any certificate that might be present
in the listener config. Mihomo rejects these fields on mieru proxy.
Fix: added 'mieru' to the copyClientTLS exclusion list alongside
'shadowsocks' and 'shadowquic' (which also skip copyClientTLS).
2026-09-01 14:39:31 +00:00
Z User 3c6e7b5f86 fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
fix(shadowquic): remove TLS cert injection, skip copyClientTLS, SNI fallback
ShadowQUIC uses QUIC's built-in TLS — it does NOT need
certificate/private-key (not in official listener schema) and does
NOT need tls:true/skip-cert-verify in the client proxy (not in
official client proxy schema). Three issues fixed:
1. Removed shadowquic from protocolNeedsServerCertificate (autofill.go)
and listenerProtocolNeedsCert (tls_ensure.go). These functions were
injecting self-signed certificates into the listener config, but the
ShadowQUIC schema has no certificate field → Mihomo rejects:
'field certificate is not supported for listener protocol shadowquic'
2. Skip copyClientTLS for shadowquic (converter/client.go). QUIC has
built-in TLS; the client proxy doesn't need tls:true, certificate,
or skip-cert-verify fields. copyClientTLS was setting these based
on the injected (now removed) certificate.
3. Added SNI fallback to server host. When jls-upstream is absent or
has no sni/addr, the client YAML had no SNI → TLS handshake fails.
Now falls back to the server address as SNI (same fix as trojan/hy2).
Official ShadowQUIC client proxy fields:
type, server, port, username, password, sni, alpn, quic-versions,
zero-rtt, congestion-controller, up, down, cwnd, bbr-profile,
max-datagram-frame-size, max-open-streams, recv-window-conn,
recv-window, disable-mtu-discovery, udp-over-stream,
keep-alive-interval
(NO tls, NO certificate, NO skip-cert-verify)
2026-08-31 16:05:50 +00:00
Z User 18655e32c5 fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
fix(mieru): remove certificate/private-key from mieru listener
Mieru is a TCP/UDP proxy that does NOT use TLS — it has no
certificate/private-key fields in the official Mihomo listener
schema. But ensureServerTLSCertificate and ensureListenerTLSMaterial
both listed mieru as needing a cert, so they injected self-signed
PEMs into the listener config. Mihomo rejected with:
listener 'mieru': field 'certificate' is not supported
Fix: removed 'mieru' from the protocol list in both:
- autofill.go: protocolNeedsServerCertificate
- tls_ensure.go: listenerProtocolNeedsCert
Mieru now correctly skips TLS certificate generation.
2026-08-31 16:01:51 +00:00
Z User 83be2eeeba fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
fix(trojan/hysteria2): SNI missing in client export — fallback to server host
ROOT CAUSE: listener config strips 'sni' in sanitizeServerConfig
(autofill.go:116) because 'sni' is a client-only hint, not a Mihomo
listener field. But the client YAML export (converter/client.go) reads
'sni' from the listener config to populate the client proxy's 'sni'
field. With 'sni' stripped, the exported client YAML had no SNI at all.
For Trojan, this means the client doesn't know which hostname to
verify in the TLS certificate → TLS handshake fails.
Fix: in the trojan and hysteria2 cases of listenerToProxies, if sni
and servername are both empty after copyOption, fall back to the
server host address as the SNI. This matches what most clients do
implicitly when no SNI is configured.
Also checked other protocols:
- vmess/vless: copyClientTLS already has reality/jls fallback logic ✅
- anytls: copyClientTLS called in base, has fallback ✅
- tuic: ensureTUICClientDefaults + copyOption sni ✅
- trusttunnel: copyOption sni in client list ✅
2026-08-31 15:48:02 +00:00
Z User 4854f18753 feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
feat: split TUIC into TUIC v4 and TUIC v5 in protocol selector
The protocol dropdown now shows 'TUIC v4' and 'TUIC v5' as separate
options instead of a single 'tuic'. This makes it clear which
authentication mode the listener uses:
- TUIC v4: token-based auth (token: ["..."])
- TUIC v5: UUID+password auth (users: {UUID: PASSWORD})
Implementation:
- Frontend: PROTOCOLS array splits 'tuic' into 'tuic-v4' and 'tuic-v5'
Select shows them as separate options. Token field only shows for v4.
- Backend: tuic-v4 and tuic-v5 are accepted as valid protocol names
in schema registry, compiler registry, autofill, and mihomo listener
protocol list. Generator normalizes tuic-v4/tuic-v5 to 'tuic' for
Mihomo (which only has one 'tuic' type).
- Compiler: TUICCompiler gets a 'kind' field. tuic-v4 compiler ensures
token is present and removes users. tuic-v5 compiler uses users map.
- Existing 'tuic' protocol still works (backward compatible).
- autofill: tuic-v4 and tuic-v5 both call autofillTUICUsers (which
correctly handles token-vs-users logic).
Verification: go build/vet/test PASS, npm run build PASS.
2026-08-31 15:04:36 +00:00
Z User 4190024234 fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
fix(tuic): token field must be slice not string
Same class of bug as trusttunnel network: Mihomo expects TUIC v4 token
as an array (token: ["TOKEN"]), but the frontend stored a single
token as a string. Mihomo rejected with 'token' is not a slice.
Fix:
- Frontend: token always stored as array (even when single token)
- Backend: TUICCompiler converts string token to []string{token}
for legacy configs
Also audited all other array-type fields:
- alpn: Select mode=tags → array ✅
- quic-versions: Select mode=tags → array ✅
- short-id: Select mode=tags → array ✅
- server-names: Select mode=tags → array ✅
- network (trusttunnel): mode=multiple → array ✅ (fixed in prev commit)
- token (tuic v4): now always array ✅ (this fix)
No other type mismatches found.
2026-08-31 14:28:21 +00:00
Z User 156b21fcc1 fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
fix(trusttunnel): network field must be slice not string
Mihomo expects network: ["tcp", "udp"] (array) for TrustTunnel
listeners. The frontend Select stored a single string ("tcp" or
"udp"), which copyConfigPassthrough emitted as network: "tcp"
(string). Mihomo rejected with: 'network' is not a slice.
Fix:
- Frontend: network Select changed to mode="multiple" (multi-select)
Storing as array, reading as array (backward compatible with string)
- Backend: TrustTunnelCompiler.Compile converts string network to
[]string{network} for legacy configs that still have a string
Official docs: network: ["tcp", "udp"] # http2+http3
2026-08-31 14:22:14 +00:00
Z User 0e2d47d778 fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
fix(protocol): XHTTP transport is VLESS-only in capabilities and UI
VMess/Trojan shared transportComponents() which listed XHTTP. Split a
VLESS-only transportComponentsWithXHTTP(); hide XHTTP radio/hint for
other protocols and refuse to emit xhttp-config unless protocol is vless.
2026-08-31 07:28:58 +00:00
Z User 214605d9c8 fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
fix(ui/protocol): P1 always-TLS UX, P2 wrapper sanitize, P3 ShadowQUIC/tlsmirror
- Hy2/TUIC/AnyTLS/TrustTunnel: no Security=None; always show cert with auto hint
- Optional protocols: cert only when security_layer=tls
- Do not emit incomplete shadow-tls/res-tls/jls/jls-upstream/tlsmirror
- Sanitize same blocks on save and config generation
- ShadowQUIC default congestion-controller=cubic
- VMess minimal tlsmirror-config (dest + primary-key)
2026-08-31 07:20:24 +00:00
Z User d46ad2a94e fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
fix(listener): drop incomplete jls-upstream before Mihomo validate
Empty jls-upstream (missing addr) fails mihomo -t on ShadowQUIC.
Require addr on the form and sanitize at save/generation.
2026-08-31 06:52:38 +00:00
Z User dacc5f5b08 fix(tls): autofill regenerates when only cert or key exists 2026-08-31 06:42:48 +00:00
Z User 4e532fa93b fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
fix(tls): regenerate full cert pair when only key or cert is present
Half-filled TLS material blocked autofill and left TUIC/AnyTLS without
a usable certificate. Clear the partial pair and issue a new self-signed
certificate+key together.
2026-08-31 06:42:20 +00:00
Z User e589f9d414 fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
fix(tuic): client uuid fallback, ALPN h3 defaults, real UUID autofill
TUIC v5 clients require uuid+password matching server users map keys.
Fill uuid from username when panel UUID is empty, default alpn=h3 and
congestion-controller=bbr, auto skip-cert-verify for panel certs, and
stop using the non-UUID key "default" in autofill.
2026-08-31 06:39:27 +00:00
Z User d6e64ab313 chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
2026-08-31 06:21:47 +00:00
Z User 3279d1c963 feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
feat(share): auto skip-cert-verify for panel self-signed certs
Detect certificates issued with Organization=3m-ui (panel generator)
and set skip-cert-verify / insecure=1 on client YAML and share URIs
so AnyTLS/Hy2/Trojan self-signed nodes work without manual toggles.
2026-08-31 06:12:47 +00:00
Z User a19a34420a fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
fix(anytls): always inject TLS cert; clear allow-insecure
Incomplete jls-config no longer suppresses self-signed cert generation.
When certificate/private-key are filled, drop allow-insecure so clients
can dial normal AnyTLS+TLS (not nginx offload mode).
2026-08-31 06:05:25 +00:00
Z User 8f9ffb1a08 fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes
mihomo -t to fail. Require complete JLS on the form; sanitize on
save and at config generation.
2026-08-31 05:57:21 +00:00
Z User de6425ae86 fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
fix(listener): auto-fill TLS certificate for Trojan/Hy2/AnyTLS/TUIC
Mihomo rejects listeners with unset certificate/private-key. Expand
autofill beyond Hysteria2, run on update as well as create, and ensure
material at config generation so existing DB rows still pass mihomo -t.
2026-08-31 05:19:34 +00:00
Z User 4b00a1268a fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
fix(functional): batch autofill, soft-delete name reclaim, hasCredState test, sub_token error, batch reset traffic
Deep functional audit of node/user/subscription/traffic features
found 5 bugs. All fixed. All tests pass (including the previously
failing hasCredState test).
== CRITICAL ==
F1: BatchCreate skips AutofillListenerDefaults
Batch-created listeners had no auto-generated UUIDs, passwords,
REALITY keys, SS ciphers, snell PSKs, or sudoku keys → mihomo
rejected the config → entire batch failed.
Fix: call AutofillListenerDefaults before ValidateModel in loop.
F2: BatchCreate doesn't reclaim soft-deleted names
Inline Count check used GORM default scope (excludes soft-deleted)
but UNIQUE index covers them → DB constraint error on name clash.
Fix: use ensureUniqueName which renames soft-deleted rows.
F3: Failing test contradicts hasCredState fallback fix
Test asserted no fallback when hasCredState=true + empty creds.
But we intentionally removed the guard to fix 'unset fields: users'.
Fix: updated test to assert config users ARE included as fallback.
== HIGH ==
F4: sub_token generation ignores error
subTok, _ := randomHex(16) — empty sub_token on rand.Read failure.
Fix: check error, return on failure.
F5: BatchResetTraffic doesn't trigger config regeneration
Over-quota users stayed blocked for ~10s after traffic reset.
Fix: added notifyCredentialsChanged() after reset.
Verification: go build/vet/test all PASS. gofmt clean.
2026-08-31 04:26:51 +00:00
Z User 5b9674987f fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
2026-08-31 03:42:32 +00:00
Z User 913783b309 fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
fix(critical): listener users empty when panel creds unavailable
ROOT CAUSE: asUsersArray/asUsersMap/asUsersMapUUID had a guard:
if hasCredState { return nil }
When a listener had ListenerUser bindings (hasCredState=true) but the
panel credentials were empty (decrypt failure, user inactive, etc.),
the compiler returned nil users → Mihomo config had no users field →
validation failed: 'unset fields: users'.
This caused two symptoms:
1. Subscription returns 'no exportable proxies for user'
2. Binding nodes fails: 'Mihomo configuration could not be updated:
validate Mihomo configuration: listener 1 has unset fields: users'
Fix: removed the hasCredState guard in all three functions. When panel
credentials are empty, fall back to the listener config's users field.
This is safe because:
- Config users were validated by the listener schema
- Config users were autofilled (password/UUID generated) on create
- Panel users take precedence when available (fromCreds checked first)
- Config users are a safety net for decrypt failures, inactive users
The hasCredState parameter is kept in the signature for API compat
but no longer affects the fallback logic.
2026-08-31 03:27:04 +00:00
Z User 090d81605e fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
fix(subscription): return actual error message instead of generic 'failed'
The generic 'subscription generation failed' gave users no clue what
was wrong. Now the actual error is returned in the JSON response:
- 'user is not bound to any listener' → bind the user to a node
- 'no exportable proxies for user' → check listener config / protocol
- 'failed to load credentials' → check credential_key / user passwords
- 'listener X requires at least one active user' → bind active users
The log.Printf is still present for server-side diagnostics.
2026-08-31 03:05:07 +00:00
Z User 6698073cd4 fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
fix(subscription): decrypt failure no longer breaks all users' subscriptions
ROOT CAUSE: ActiveCredentialsByListener returned an error if ANY user's
password decrypt failed (e.g. after credential_key rotation or version
upgrade). This caused ALL users' subscriptions to return 422, even
users whose passwords were perfectly fine.
The error propagated through:
ActiveCredentialsByListener → error
→ userBoundListeners → error
→ GenerateUserRawConfig / GenerateUserBase64Subscription → error
→ subscription.go handler → 422 Unprocessable Entity
Fix: when decryptPassword fails for one user, skip that user and
continue processing others. The affected user's subscription will
still fail (no matching credential → 'no exportable proxies'), but
all other users' subscriptions work normally.
Operators should reset the affected user's password after a
credential_key rotation to restore their subscription.
2026-08-31 01:30:09 +00:00
Z User f14e95b684 fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
fix(all-remaining): 24 audit findings — Medium + Low severity
All remaining unfixed issues from 3-round exhaustive audit are now
resolved. 8 Medium + 16 Low = 24 items.
== Medium (8) ==
M1: GetLogs timestamp always time.Now()
mihomo/service.go GetLogs now parses RFC3339 prefix from stored log
lines, infers level (ERROR/WARN/INFO/DEBUG) from payload prefix.
M2: Cron parser silently ignores */N and range
telegram/scheduler.go cronFieldMatches now supports */N (step),
A-B (range), A-B/N (stepped range) in addition to * and comma.
M3: findExistingProcesses holds pm.mu during /proc scan
mihomo/process.go start() now calls findExistingProcesses() BEFORE
acquiring pm.mu. Logs/Status/Stop no longer blocked during scan.
M4: Cluster HealthCheckAll sequential (N*12s)
cluster/service.go now fans out goroutines with WaitGroup. Worst
case drops from N*12s to ~12s.
M5: Panel settings PUT not transactional
router/panel_settings.go PUT handler now wraps create/update loop
in db.Transaction(). Partial failure rolls back.
M6: Cluster api_token no validation at save time
cluster/service.go Create/Update now validate JWT shape (starts
with 'ey', 3 dot-separated parts). Clear error message guides
operator to POST /api/v1/auth/login.
M7: Disk full — stale config warning not prominent
app/app.go soft-fail warnings changed to [WARNING] prefix + hint
to check listener configs.
M8: Telegram bot retries forever on 401
telegram/bot.go loop now detects 401/Unauthorized, switches to
5-minute retry interval (was 5s). Clears on success.
== Low (16) ==
L1: Scheduler/Bot Start() idempotent via sync.Once
L2: Container.Shutdown() method added
L3: Enforcer lastBlocked prunes deleted users
L4: traffic.Service first-call rate skipped (was bogus spike)
L5: strings.HasPrefix replaces path[:4]=="/api"
L6: ActiveCredentialsByListener batch-load (N+1→1 query)
L7: Telegram bot commands check DB errors
L8: Cluster health save error logged
L9: Collector continues on AddSample failure (was return)
L10: user.Create logs warning instead of returning error after
successful DB write
L11-L14: Documentation comments for known limitations (HTTP-01
goroutine, GlobalConfig thread-safety, credentialsChanged
thread-safety, SubToken TOCTOU)
L15: useAuth.ts calls fetchMe() to refresh mustChangePassword
L16: Verified access-tokens/client-access endpoints are NOT dead
Verification: go build/vet/test PASS, npm run build PASS, gofmt clean.
2026-08-31 00:19:24 +00:00
Z User 76c81b8261 fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
fix(audit-r3): traffic rule/chains/start fields, ProxyUser telegram fields
Round 3: frontend-backend API contract audit found 2 issues. Fixed.
F-1 (Medium): Traffic page rule column always empty
Frontend Traffic.tsx declares a 'rule' column on connections, but
backend ConnectionView (traffic/types.go) didn't include Rule/Chains/
Start fields. Mihomo's /connections API returns these (api/types.go
Connection struct has Rule, Chains, Start) but the collector dropped
them when building ConnectionView.
Fix: added Rule/Chains/Start to ConnectionView struct; collector now
populates them from conn.Rule/conn.Chains/conn.Start.
F-2 (Low): ProxyUser type missing telegram_id/telegram_name
Backend SafeUser returns telegram_id + telegram_name (added in
Telegram enhancement), but frontend ProxyUser interface didn't
declare them. Also had phantom created_at (backend never returns it).
Fix: replaced created_at with telegram_id + telegram_name in
frontend/src/api/users.ts ProxyUser interface.
Verification: go build/vet PASS, go test PASS, npm run build PASS.
2026-08-30 15:35:32 +00:00
Z User da5a0f67d1 fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
2026-08-30 15:14:36 +00:00
Z User 0754e6a9d6 fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
fix(format-mismatch): TUIC v5 URI UUID key, cluster JWT auth doc, visual port type, access token entropy
Hunt for format-mismatch bugs (like the backup zip vs raw db bug) found
4 issues across the codebase. All fixed.
== Critical 1: TUIC v5 share URI uses Username instead of UUID ==
uri_credentials.go ClientURIsWithCredentials built the users map for
TUIC keyed by credential.Username, but the server-side compiler
(asUsersMapUUID) keys by UUID. Official Mihomo TUIC v5 spec requires
users: {UUID: PASSWORD}. The URI tuic://alice:pass@host failed auth
because the server looked up users['alice'] not users['UUID'].
Fix: split 'tuic' from the 'anytls/hysteria2/mieru' case; use UUID
as key (fallback to Username when UUID empty), matching asUsersMapUUID.
== Critical 2: Cluster APIToken sent as Bearer JWT to remote panel ==
cluster/service.go sends 'Authorization: Bearer <api_token>' but the
remote panel's RequireAuth middleware expects a signed JWT (HS256).
Opaque tokens fail jwt.Parse → 401 on all auth-protected endpoints.
Fix: documented that api_token must be a valid JWT obtained from
POST /api/v1/auth/login on the remote panel. Added comments at both
Bearer header sites. (Full opaque-token auth path requires a separate
middleware on the remote — tracked as future feature.)
== Medium 3: VisualConfig ProxyEntry.Port string → quoted YAML ==
visual.go ProxyEntry.Port was string type, causing yaml.Marshal to
emit port: '8080' (quoted). Mihomo expects port: 8080 (integer).
Changed to interface{} so JSON/YAML can carry int or string.
== Low 4: AccessToken entropy inconsistency ==
router/access_token.go used 24 bytes (192-bit) for token generation,
but node/api.go CreateClientAccess used only 16 bytes (128-bit).
Aligned both to 24 bytes (192-bit, NIST SP 800-63A minimum).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- gofmt : clean
2026-08-30 13:04:59 +00:00
Z User 266b59bee1 fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
2026-08-30 11:56:37 +00:00
Z User 7addcdc5c7 fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
2026-08-30 11:13:08 +00:00
Z User e445bcd323 fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
fix(audit): P0+P1 — ShadowQUIC defaults, Snell wrappers, SS plugin, VMess encryption, TUIC/Hy2 fields, subscription error leak, frontend forms
Comprehensive re-audit against official Mihomo docs (Meta-Docs-main)
found 3 High + 9 Medium issues. All fixed in this commit.
== P0 High ==
P0-1: ShadowQUIC client defaults violated official docs
converter/client.go unconditionally defaulted quic-versions=[v2],
congestion-controller=bbr, zero-rtt=true. Official defaults are
v1/cubic/false. Removed defaults; fields now pass through only when
present in listener config.
P0-2: Snell listener wrappers not converted to client obfs-opts
Snell+shadow-tls/res-tls/jls-config produced client YAML with only
psk+version. Added applySnellObfsOpts that maps listener wrappers to
client obfs-opts: {mode: shadow-tls/restls/jls, host, password,
version, username, version-hint, restls-script} per proxies/snell.md.
P0-3: SS simple-obfs plugin name mismatch
Emitted plugin: 'simple-obfs' but official docs say plugin: 'obfs'.
Fixed.
== P1 Medium ==
P1-4: VMess encryption field leaked
copyOption(encryption) called for both vmess and vless. Only VLESS
has encryption. Wrapped in if protocol=='vless'.
P1-5: TUIC client missing sni + skip-cert-verify
Added to both v4 (token) and v5 (users) branches.
P1-6: TUIC URI snake_case → kebab-case
congestion_control→congestion-controller, bbr_profile→bbr-profile,
udp_relay_mode→udp-relay-mode, max_udp_relay_packet_size→
max-udp-relay-packet-size.
P1-7: Hysteria2 client missing handshake-timeout + max-idle-time
Added to copyOption list.
P1-8: Subscription endpoint error leak
Replaced err.Error() with generic 'subscription generation failed'
at 4 public endpoints (lines 82/107/122/368). Added server-side
log.Printf for diagnostics.
P1-9: Snell form missing v4/v5
Version dropdown [1,2,3]→[1,2,3,4,5]; initialValue 3→4 (matches
backend autofill default).
P1-10: Sudoku form missing httpmask + custom-table + fallback
Added EnableSection for httpmask (disable/mode/path-root) +
custom-table/custom-tables/fallback Form.Items. Wired through
configToFormValues + formValuesToConfig + FORM_OWNED_KEYS.
P1-11: ShadowQUIC form missing 4 fields
Added max-datagram-frame-size, recv-window-conn, recv-window,
disable-mtu-discovery.
P1-12: Unconditional support-x25519mlkem768
Removed unconditional true; now only set when listener config
explicitly provides it.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean (on modified files)
2026-08-30 10:34:48 +00:00
Z User fad6e612a5 fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
fix(audit): mux-option UI fields, shadowquic/trusttunnel URI, sudoku aliases
Final re-audit against official Mihomo docs (Meta-Docs-main) found 4
remaining issues. All fixed.
== C1 Critical: mux-option frontend writes smux client fields ==
The frontend mux-option form wrote enable/protocol/max-connections/
min-streams/max-streams/statistic/only-tcp (smux client format) into
the listener mux-option block. The backend listener schema only accepts
padding + brutal.{enabled,up,down} per official docs. Saving a listener
with Mux enabled failed validation.
Fix: removed the 7 smux client fields from the frontend form and the
formValuesToConfig serializer. Only padding + brutal remain (matching
the listener schema). The smux fields belong on the proxy client side,
not the listener.
== C2 Critical: shadowquic URI export broken ==
shadowQUICURIs read userMap(cfg) but shadowquic uses array-form
users: [{username, password}] per official docs. The map read always
returned empty → URI export failed with 'requires at least one user'.
Fix: use userRows(cfg) (array form) matching the compiler/autofill.
== C3 Critical: trusttunnel URI export broken ==
Same bug class as C2. trustTunnelURIs read userMap(cfg) but trusttunnel
uses array-form users per official docs.
Fix: use userRows(cfg).
== M Minor: sudoku missing flat compatibility aliases ==
Official sudoku doc lists http-mask-mode and path-root as flat
compatibility aliases for httpmask.{mode, path-root}. The schema only
had disable-http-mask (top-level). Added http-mask-mode and path-root
to the sudoku Fields allowlist.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS
- gofmt : clean
2026-08-30 08:30:38 +00:00
Z User 19842c8cf3 fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(client): SS plugin wrappers + VMess mkcp/mekya transport export
Final re-audit found 3 Medium-severity client config export bugs.
All fixed. No VLESS+REALITY code paths touched.
== N1+N2: SS+shadow-tls/res-tls/jls-config client YAML wrong format ==
The 3m-ui converter emitted VMess-style shadow-tls-opts/restls-opts/
jls-opts for SS, and set tls:true (SS has no tls field). The m-ui
bridge decodeSS didn't populate spec.Security so the m-ui SS module's
plugin emission never fired.
Fix (converter/client.go):
- Guard copyClientTLS to skip for SS (no tls/sni/servername/alpn)
- New applySSPluginWrappers emits documented plugin format:
- shadow-tls: plugin=shadow-tls, plugin-opts={version,password,host}
- restls: plugin=restls, plugin-opts={host,password,version-hint,restls-script}
- jls: plugin=jls, plugin-opts={host,username,password,alpn}
- client-fingerprint defaults chrome (respects listener config)
Fix (mui/bridge.go):
- decodeSS now populates spec.Security via decodeSSSecurity helper
(shadow-tls→ShadowTLS, res-tls→ResTLS, jls-config→JLS)
- Added decodeShadowTLSConfig/decodeResTLSConfig/decodeJLSConfig helpers
- m-ui SS module's existing shadowsocksPlugin emission now fires
== N3: VMess+mkcp/mekya client YAML missing transport ==
copyTransport only handled ws-path/grpc-service-name/xhttp-config.
VMess+mkcp/mekya client YAML defaulted to TCP.
Fix (converter/client.go copyTransport):
- mkcp-config→mkcp-opts: direct field copy (mtu/tti/uplink-capacity/
downlink-capacity/congestion/write-buffer/read-buffer/seed/header)
- mekya-config→mekya-opts: kcp sub-block 1:1; max-write-size→
max-request-size; max-write-duration-ms→max-write-delay
(URL left empty — no listener counterpart, operator sets manually)
Fix (mui/bridge.go decodeHandler):
- mkcp-config→VMessHandlerMKCP + h.MKCP population
(consumed by compileClassicClient→mkcp-opts)
- mekya-config noted as gap (no Mekya handler domain type; converter
handles it for the 3m-ui native path)
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
2026-08-30 05:37:34 +00:00
Z User c817650597 fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
fix(config): P2+P3+P4 audit fixes — client config, schema, m-ui bridge
Comprehensive audit against official Mihomo docs (Meta-Docs-main).
This commit addresses all remaining P2 (Medium) and P3/P4 (Low)
findings, completing the full audit fix campaign.
== P2 Medium (client config gaps) ==
P2-1: VLESS native share URI missing security=tls for non-Reality TLS
share_modules.go BuildShare only set security=reality; plain TLS
had no security param (clients default to none=plaintext).
Added else-if: security=tls when Node.TLS && no Reality.
P2-2: m-ui VLESS URI emitted non-standard security=shadow-tls/res-tls/jls
mui/protocol/vless.go leaked wrapper types into URI. Fixed: map all
TLS wrappers (shadow-tls/res-tls/jls) to security=tls; reality stays
reality; none omitted. Wrapper detail preserved in client YAML.
P2-3: m-ui decodeHy2 dropped alpn — client YAML ALPN always empty
mui/bridge.go Added shared decodeALPN helper (handles []interface{},
[]string, bare string). Wired into decodeHy2/decodeVMess/decodeTrojan/
decodeVLESS. Added ALPN field to domain VLESSSpec/VMessSpec/TrojanSpec.
P2-4: m-ui classicClientProxy (VMess/Trojan) missing ALPN field
mui/protocol/classic_stream.go Added ALPN yaml field + populated
from node.VMess.ALPN / node.Trojan.ALPN.
P2-5: m-ui vlessClientProxy missing ALPN field
mui/protocol/vless.go Added ALPN yaml field + populated from
node.VLESS.ALPN.
P2-6: sing-box TUIC v4 token not handled
converter/singbox.go TUIC case only read uuid+password. Added token
reading ([]interface{}/[]string/string) → sing-box native token field;
falls back to uuid+password for v5.
P2-7: sing-box VLESS missing encryption mapping
converter/singbox.go Added p[encryption] → ob[encryption] when non-empty.
== P3 Low (schema completeness + client gaps) ==
P3-1: ResTLS restls-script not copied to client restls-opts
converter/client.go resTLSClientOptions now copies restls-script.
P3-2: JLS only exports first user — documented as design limitation
(per-credential JLS user matching requires explicit binding table;
single-JLS-user deployments are correct). Added doc-comment.
P3-3: WS ws-headers not in schema — Host header never exportable
Added ws-headers to top-level Fields for vmess/vless/trojan, aligning
with copyTransport which already reads src[ws-headers].
P3-4: VMess tlsmirror-config steps missing name/headers/wait-time field
Added 3 missing steps.* sub-fields to match tls.md 12-field list.
P3-5: VMess users not in NestedFields — documented as design choice
(map-form vs array-form shape difference would break map-form
validation). Added explanatory comment.
P3-6: Hysteria2 URI up/down inconsistent between rows vs map users
uri_builders.go First branch now reads up/down from listener config,
matching second branch.
P3-7: SS mux-option over-broad — removed 7 proxy-side sing-mux fields
(enable, protocol, max-connections, min-streams, max-streams,
statistic, only-tcp) from ALL 7 protocols with mux-option. Kept
padding + brutal.*/brutal-opts.* (listener-documented fields).
P3-8: m-ui bridge only supports 5/12 protocols — documented as follow-up
(requires implementing 7 protocol decoders; large feature).
P3-9: Three-tier fallback inconsistency — documented as follow-up
(design issue requiring output-contract unification).
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean (on all modified files)
2026-08-30 04:13:26 +00:00
Z User a379c33159 fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
fix(config): comprehensive audit vs official docs — client config + schema
Full audit against official Mihomo docs (Meta-Docs-main) found and
fixed 10 bugs across client config generation and schema completeness.
P0 Critical:
- VLESS client YAML missing network field (transport opts ignored)
- TUIC v4 token share broken (read as string, is array)
P1 High:
- SS base64 violates SIP002 (RawStd -> RawURL)
- VMess base64 inconsistency (RawStd -> Std padded)
- Hysteria2 server-only fields (masquerade/ca/ca-str) in client YAML
- sing-box VMess alter_id hardcoded to 0
- sing-box missing TLS insecure + alpn mapping
- VMess per-user alterId dropped when panel creds bound
P2 Medium:
- reality-config schema missing max-time-difference + proxy (vmess/vless/trojan)
- hysteria2 realm-opts schema missing sni
Verification: go build/vet/test all PASS; gofmt clean.
2026-08-30 00:06:00 +00:00
風雪 3b61cd45e3 fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
fix(listeners): complete snell res-tls fields + unify anytls users as opaque
Continued the shadow-tls/res-tls/jls-config nested field audit
against the official Mihomo wiki.
== Fix 1: snell res-tls was missing 3 fields ==
snell's res-tls only listed 4 fields (enable, dest, password,
rate-limit) while ss/vmess/trojan all list 7. Mihomo uses the same
ResTLS struct for all protocols, so snell supports the full set.
Added: restls-script, min-record-len, proxy.
Now all 6 protocols with res-tls (shadowsocks, snell, vmess, vless,
trojan, anytls) have the identical 7-field res-tls schema:
enable, dest, password, restls-script, min-record-len, proxy, rate-limit
== Fix 2: anytls shadow-tls.users / jls-config.users unified to opaque ==
anytls decomposed users into .users.name/.users.password (shadow-tls)
and .users.username/.users.password (jls-config), while all other
protocols treat users as an opaque child. This inconsistency was
workable after C2 (validator now handles arrays of nested objects),
but the decomposition served no purpose and diverged from the
canonical pattern. Unified to opaque 'users' child matching
ss/snell/vmess/vless/trojan.
== Field audit summary (shadow-tls / res-tls / jls-config) ==
All 6 protocols now have identical nested schemas:
shadow-tls: enable, version, password, users, handshake.dest, handshake.proxy
res-tls:    enable, dest, password, restls-script, min-record-len, proxy, rate-limit
jls-config: enable, users, dest, sni, alpn, proxy, rate-limit
Verification: go build/vet/test all PASS; gofmt clean.
2026-08-29 14:53:09 +00:00
風雪 f434b6293a fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
fix(listeners): transport options per protocol + clean autofill hint + remove dead hy2 fields
Second-round audit against https://wiki.metacubex.one/config/inbound/
found and fixed 4 issues:
== 1. XHTTP transport option shown for trojan/vmess (frontend) ==
Official Mihomo docs explicitly state:
'xhttp: 仅 VLESS 支持 xhttp 传输层,请勿在其他协议上使用'
'mkcp: 仅 VMess 支持 mKCP 传输层'
'mekya: 仅 VMess 支持 Mekya 传输层'
The transport_layer Radio.Group showed TCP/ws/grpc/xhttp for ALL
protocols in TRANSPORT_PROTOCOLS (vmess/vless/trojan). Wrapped the
xhttp Radio.Button in {XHTTP_PROTOCOLS.has(protocol) && ...} so it
only appears for vless. TCP/ws/grpc remain shared. mekya/mkcp for
vmess already render via dedicated EnableSection blocks (unchanged).
== 2. Autofill hint was SS/Snell-centric (frontend i18n) ==
The global autoGenerateHint Alert mentioned only 'REALITY/SS/Snell/Hy2'
but the autofill logic now covers ALL protocols (sudoku key, snell
version, trojan/hy2/anytls/mieru/tuic/shadowquic/trusttunnel users).
Updated zh+en i18n strings to enumerate all autofilled fields so the
hint is consistent and accurate regardless of which protocol form is
open.
== 3. hysteria2 dead fields obfs-min/max-packet-size (backend+frontend) ==
These fields are NOT in the Mihomo hysteria2 listener source (appear
copied from hysteria v1). Removed from:
- schema registry Fields allowlist (backend)
- ListenerConfigFields.tsx FORM_OWNED_KEYS + set() calls + Form.Item
- mui/protocol/hysteria2.go struct fields + struct literals
- mui/domain/node.go Hysteria2Spec fields
Kept max-idle-time + handshake-timeout (present in Mihomo source, just
wiki-undocumented). Kept obfs/obfs-password (salamander, wiki-documented).
== 4. trojan ss-option verified (no change) ==
Confirmed present in Mihomo trojan listener source (wraps trojan in a
shadowsocks layer). Real feature, wiki-undocumented. Kept as-is.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
2026-08-29 14:18:07 +00:00
風雪 126e70dbd1 fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
fix(listeners): deep audit fixes — TUIC users, array validation, VLESS encryption, sudoku/snell autofill
Comprehensive audit of the entire listener config pipeline found and
fixed 7 bugs across autofill, validation, compilation, and schema.
== Critical (broke Mihomo) ==
C1: TUIC listener with no panel users produced empty 'users' field
autofillTUICUsers wrote users as ARRAY [{username, password}], but
the TUIC compiler (asUsersMapUUID) only reads MAP form. Compiled
output had no users → Mihomo rejected. Fixed: autofill now writes
v5 map form {'default': '<pass>'}.
C2/C3/C4: Validator rejected array-typed nested fields
anytls shadow-tls.users, anytls jls-config.users, and vmess
tlsmirror-config.embedded-traffic-generator.steps are ARRAYS of
objects in Mihomo, but the schema decomposed them into .name/
.password/.host children, making the parent require a JSON object.
Fixed: validateListenerObject now type-switches on the value —
arrays of objects are recursively validated element-by-element
against the same nested schema. Handles []interface{} and
[]map[string]interface{}.
== High (wrong config output) ==
H1: VLESS 'encryption' field silently stripped by compiler
copyConfigPassthrough had 'encryption' in its strip list (intended
to drop the client-side hint), but 'encryption' is also a legitimate
VLESS server field paired with 'decryption'. Removed it from the
strip list; panel-only keys (_-prefixed, access_profile,
transport_layer, security_layer) remain stripped.
H2: Sudoku listener created without 'key' was unusable
Sudoku uses top-level 'key' for auth (no users). Autofill had no
case for sudoku. Added: auto-generates 32-byte base64 key + defaults
aead-method to chacha20-poly1305.
H3: Snell listener created without 'version' may be rejected
Autofill set psk but never version. Added: defaults version to 4
(current Mihomo default) when unset, without clobbering pre-existing
numeric versions.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 15 packages all PASS
- gofmt : clean
2026-08-29 13:58:55 +00:00
風雪 9a3674685c fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
fix(listeners): align TUIC v5 users map + TrustTunnel users injection with wiki
Continued the listener schema audit against the official Mihomo wiki
(https://wiki.metacubex.one/config/inbound/listeners/). Found and fixed
three remaining inconsistencies after the previous commit (11e8d4b).
== 1. TUIC v5 users format (was: array, should be: map) ==
Mihomo wiki TUIC v5 example:
users:
UUID1: PASSWORD1
UUID2: PASSWORD2
The TUICCompiler was using asUsersArray (producing [{username,password}]),
which is wrong for v5. Added asUsersMapUUID helper that emits
map{uuid: password} (key=UUID, falling back to Username for legacy panel
users without UUID). TUICCompiler.Compile now:
- Passes through  (TUIC v4) via copyConfigPassthrough.
- When no  is configured, emits v5 map-form users via
asUsersMapUUID.
== 2. TrustTunnel credential injection (was: missing) ==
TrustTunnel was registered as GenericCompiler{kind:"trusttunnel"}, which
does NOT inject in.Users into the compiled config — so credentials stored
in the panel DB never reached Mihomo. Created dedicated TrustTunnelCompiler
that calls asUsersArray (TrustTunnel uses users: [{username, password}]
per wiki). Updated DefaultCompileRegistry to use it.
== 3. Sudoku missing mux-option ==
Mihomo wiki sudoku example shows mux-option (commented). Added mux-option
to sudoku Fields + NestedFields in the schema registry so the validator
accepts it.
== Audit summary (all protocols vs wiki) ==
shadowsocks: cipher + password (top-level)        ✅ correct
snell:        psk + version                       ✅ correct
vmess:        users: [{username, uuid, alterId}]   ✅ correct
vless:        users: [{username, uuid, flow}]      ✅ correct (excluded per user)
trojan:       users: [{username, password}]       ✅ correct (fixed in 11e8d4b)
anytls:       users: {username: password}          ✅ correct (fixed in 11e8d4b)
mieru:        users: {username: password}          ✅ correct (fixed in 11e8d4b)
hysteria2:    users: {username: password}          ✅ correct (fixed in 11e8d4b)
tuic-v4:      token: [str]                         ✅ correct (passthrough)
tuic-v5:      users: {UUID: PASSWORD}              ✅ correct (fixed this commit)
shadowquic:   users: [{username, password}]        ✅ correct
trusttunnel:  users: [{username, password}]        ✅ correct (fixed this commit)
sudoku:       key (no password) + mux-option       ✅ correct (mux-option added this commit)
2026-08-29 13:29:06 +00:00
風雪 90f67a6b1d fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
fix(listeners): align auth field formats with Mihomo official listener schemas
Root cause of 'listener hy2: field password is not supported for
listener protocol hysteria2': autofillPasswordUsers created a top-level
'password' field for protocols that use 'users' (map or array) or
'token'/'key' for authentication instead.
Verified all listener protocols against https://wiki.metacubex.one/
(config/inbound/listeners/*) and fixed three classes of bugs.
== autofill.go: protocol-specific user autofill ==
Split the single autofillPasswordUsers (which created top-level
'password' for 8 protocols) into three helpers matching Mihomo schemas:
- autofillUsersMap  → hysteria2/anytls/mieru (users: {name: pass})
- autofillUsersArray → trojan/shadowquic/trusttunnel (users: [{name, pass}])
- autofillTUICUsers  → tuic (keeps existing token for v4; defaults
to v5 users array if neither token nor users set)
- Removed sudoku from the password autofill switch entirely — it
uses 'key' (top-level), not password/users.
- Deleted the old autofillPasswordUsers function.
== compile.go + compile_modules.go: users format ==
asUsersArray always returned []map (array), but hysteria2/anytls/mieru
require map{username: password}. Added asUsersMap helper. Created
dedicated AnyTLSCompiler + MieruCompiler (replacing GenericCompiler
registrations) that emit map-form users. Hysteria2Compiler.Compile
now calls asUsersMap.
Trojan/ShadowQUIC/TUIC compilers unchanged (their array format
already matches Mihomo docs: users: [{username, password}]).
== listener_schema_registry.go: missing mux-option ==
Added mux-option to hysteria2 and tuic Fields + NestedFields — both
are shown (commented) in the official Mihomo wiki examples.
== Protocol → auth format reference (verified against wiki) ==
shadowsocks: cipher + password (top-level)     ✅ unchanged
snell:        psk (top-level)                   ✅ unchanged
vmess:        users: [{username, uuid, alterId}] ✅ unchanged
vless:        users: [{username, uuid, flow}]    ✅ unchanged (excluded)
trojan:       users: [{username, password}]     ✅ fixed (was: top-level password)
anytls:       users: {username: password}        ✅ fixed (was: top-level password)
mieru:        users: {username: password}        ✅ fixed (was: top-level password)
hysteria2:    users: {username: password}        ✅ fixed (was: top-level password)
tuic-v4:      token: [str]                       ✅ fixed (was: top-level password)
tuic-v5:      users: [{username, password}]      ✅ fixed (was: top-level password)
shadowquic:   users: [{username, password}]      ✅ fixed (was: top-level password)
trusttunnel:  users: [{username, password}]      ✅ fixed (was: top-level password)
sudoku:       key (top-level, no password)       ✅ fixed (was: in password autofill)
2026-08-29 12:54:25 +00:00
風雪 dc13e69951 feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
feat(telegram): full bot enhancement with inline menus, cron reports, events
Reference: https://docs.sanaei.dev/zh/docs/operations/telegram-bot/
Field naming follows existing 3m-ui snake_case convention (not sanaei's
tg* prefix) per project maintainer's request.
== Data layer ==
- ProxyUser: add TelegramID (indexed) + TelegramName fields for linking
proxy users to their Telegram accounts (self-service queries).
- SafeUser + ToSafeUser: expose both new fields.
- user.Service: BindTelegram / UnbindTelegram / GetByTelegramID methods.
- user API: GET /by-telegram/:tgid, PUT /:id/telegram, DELETE /:id/telegram
(registered before /:id to avoid path capture).
- UpdateInput/CreateInput: accept TelegramID + TelegramName.
== Settings expansion (8 new fields, snake_case) ==
- schedule: cron expr or @daily/@every 6h for periodic reports (default @daily)
- attach_backup: include DB backup zip in periodic report
- language: bot message language zh/en (default zh)
- enabled_events: comma-sep event list (default 'login,cpu,crash')
Supports: login,cpu,memory,crash,node_down,node_up,block,unblock,expiry,traffic
- expiry_warn_days: remind N days before expiry (0=off)
- traffic_warn_gb: remind when remaining traffic < N GB (0=off)
- proxy_url: socks5:// or http:// proxy for Telegram API
- api_server: custom Bot API server (empty=official)
- EventEnabled(name) helper for per-event gating.
== Client enhancements ==
- Proxy support: http(s):// via http.Transport.Proxy; socks5:// via
golang.org/x/net/proxy SOCKS5 dialer. Parse failure → direct fallback.
- Custom API server (apiBase() replaces hardcoded URL).
- SendDocument: multipart sendDocument for backup file attachment.
- GetMe + SetMyCommands: for bot-info + set-my-commands endpoints.
- sendWithKeyboard: sendMessage with reply_markup (inline keyboards).
- answerCallbackQuery: clear callback loading spinner.
== Bot commands + inline menus ==
- /id: reply with sender's Telegram numeric ID.
- /usage: admin → search users; bound user → self-query traffic/expiry/sub URL.
- /start + /help: send inline keyboard menu (admin menu for admin chats,
user menu for bound users, plain text for unbound).
- Admin menu: 8 buttons (status/users/traffic/online/listeners/deldepleted/
backup/restart) in 4 rows.
- User menu: my_usage + my_sub buttons.
- callback_query handling: answerCallbackQuery + dispatch on callback_data.
- Access control: admin chats always pass; non-admin chats pass only when
FromID matches a ProxyUser.TelegramID.
- Bot struct: added userSvc field for GetByTelegramID lookups.
- NewBot signature: NewBot(db, mihomoSvc, userSvc).
== Cron scheduler (new telegram/scheduler.go) ==
- Periodic report goroutine driven by Settings.Schedule.
- Supports @hourly/@daily/@weekly/@monthly/@every <dur>/5-field cron.
- Report: host, version, uptime, CPU/mem/disk, user stats, expiring+depleted
counts.
- Optional backup: AttachBackup → system.WriteZip → SendDocument.
- Settings reload on every tick (no restart needed for schedule changes).
- NotifyEvent(event, details): generic event emitter for one-shot alerts.
== Events layer ==
- login.attempt: NotifyLogin (success) + NotifyLoginFailed (failure).
Failure path added to auth/api.go Login handler. Both gated by
EventEnabled('login') — admins can mute success spam yet keep failure alerts.
- memory.high: emitMemoryWarning via gopsutil/v4/mem. Reuses CPUWarnPct as
shared 'resource warn %' threshold. 1-hour cooldown dedup.
- crash: mihomo ProcessManager.SetCrashHandler → telegram.NotifyCrash.
Fires on non-zero exit while desired==true (unexpected crash, not admin stop).
Wired in app/container.go.
- Dual thresholds: expiry (hours + days), traffic (percent + GB) with
separate dedup keys so neither suppresses the other.
== API endpoints ==
- POST /telegram/set-my-commands: registers 12-command menu with Telegram.
- GET /telegram/bot-info: returns bot username/first_name/id.
== Frontend ==
- telegram.ts: 8 new fields in TelegramSettings interface + setTelegramCommands
+ fetchTelegramBotInfo API functions.
- Settings.tsx: 8 new form controls (schedule Input, language Select,
enabled_events multi-Select with 10 options, expiry_warn_days + traffic_warn_gb
InputNumber, attach_backup Switch, proxy_url Input, api_server Input).
Added 'Set bot commands' button calling setTelegramCommands.
- i18n: 11 new keys (zh + en) for the new settings labels.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : all 15 packages PASS
- npm run build (tsc + vite) : PASS, 3241 modules
- gofmt : clean
2026-08-29 12:19:46 +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