Commit Graph
633 Commits
Author SHA1 Message Date
Z User 4dde61e8db fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
fix(frontend): align listener config form with backend schema (hy2 fields + grpc-opts/mekya inputs)
HIGH-1: remove max-idle-time from hysteria2 form + set()
HIGH-2: remove handshake-timeout from hysteria2 form + set() (kept for sudoku)
MEDIUM-1: narrow UDP_PROTOCOLS to ['shadowsocks', 'snell'] — vmess/vless/trojan udp toggle was dead (backend compile_modules.go strips it)
MEDIUM-2: add 5 R-M4 grpc-opts client-metadata Form.Items (grpc-user-agent, ping-interval, max-connections, min-streams, max-streams) on vmess/vless/trojan, with formValuesToConfig set() and configToFormValues reads
MEDIUM-3: add 2 R-M3 mekya-config client-metadata Form.Items (polling-interval-initial, h2-pool-size) on vmess, with mekya-config builder/reader fields
i18n: add 7 new keys + 6 hints to en.ts and zh.ts listeners section
Lint PASS (0 errors, 33 pre-existing fast-refresh warnings)
Build PASS (tsc -b + vite build OK)
2026-09-03 16:05:49 +00:00
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 6b11dc793e fix(ui): require Snell obfs host when mode is set 2026-09-01 16:18:18 +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 19916828e2 fix(ui): remove stray '(' text node before TUIC protocol section 2026-09-01 15:12:02 +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 32a3d56061 fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
fix(sudoku): remove required validation from key field
The sudoku key field had rules={[{ required: true }]} which blocked
form submission when the operator left it empty. But the backend
autofill (autofill.go:74-80) automatically generates a 32-byte
base64 key when empty — the same pattern as snell PSK, SS password,
and TUIC token.
Fix: removed required rule, added tooltip 'Leave empty to auto-generate'.
This was the root cause of 'sudoku cannot create' — the form refused
to submit without a key, but the operator didn't know to fill one in
(and shouldn't need to).
2026-08-31 16:12:49 +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 02f1b6ef24 fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
fix(frontend): actually apply TUIC v4/v5 split to frontend
Previous commit 20d47e8 only modified backend files — frontend
Listeners.tsx and ListenerConfigFields.tsx were not included due to
a file copy error. This commit applies the frontend changes:
- Listeners.tsx: PROTOCOLS array splits 'tuic' into 'tuic-v4'/'tuic-v5'
- ListenerConfigFields.tsx:
- ALWAYS_TLS_PROTOCOLS includes tuic-v4/tuic-v5
- Form condition: protocol === 'tuic' || 'tuic-v4' || 'tuic-v5'
- case 'tuic': includes tuic-v4 and tuic-v5 fallthrough
- token field: only shows for tuic-v4 and tuic (backward compat)
- tuic-v5: no token field (uses panel-managed users)
2026-08-31 15:35:18 +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 3c8969812c fix(ui): filter XHTTP out of capability transport for non-VLESS 2026-08-31 07:29:07 +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 313164131d fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
fix(ui): use full ListenerConfigFields instead of reduced capability form
useCapabilityForm was hardcoded true, so create/edit node only showed the
thin schema-driven CapabilityFormFields and hid protocol sections
(ShadowQUIC jls-upstream, Reality details, etc.).
2026-08-31 06:59:53 +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 9a7370cc96 chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
chore(i18n): remove unused auto-generate field guide strings
Drop autoGenerateHint, field guide titles, per-protocol guide blocks,
and password/reality auto tooltips from zh/en after UI simplification.
2026-08-31 06:37:05 +00:00
Z User 76da8746e1 ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
ui(listeners): drop auto-generate prose; use placeholder auto
Remove field-guide alerts and tooltips about auto-generation.
SS password, Snell PSK, REALITY key/short-id keep placeholder "auto".
2026-08-31 06:34:15 +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 5b8e42dfc1 feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
feat(ui): per-protocol required/optional/auto field guide; XHTTP VLESS-only
Show a unified field guide alert for each protocol. Keep XHTTP out of
VMess/Trojan transport options and document that on the transport control.
2026-08-31 05:31:24 +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 dbec0b3099 fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
2026-08-30 09:47:16 +00:00