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)
This commit is contained in:
1 parent
dbec0b3099
commit
e445bcd323
7 files changed
+199
-29
No files matched your search
@@ -186,7 +186,9 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
p["password"] = value
|
||||
}
|
||||
if value, ok := opts["simple-obfs"].(map[string]interface{}); ok && boolValue(value["enable"]) {
|
||||
p["plugin"] = "simple-obfs"
|
||||
// The listener config key is `simple-obfs`, but the mihomo SS
|
||||
// client proxy doc (proxies/ss.md) names the plugin `obfs`.
|
||||
p["plugin"] = "obfs"
|
||||
p["plugin-opts"] = value
|
||||
}
|
||||
applySSPluginWrappers(p, opts)
|
||||
@@ -199,6 +201,10 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
if value, ok := opts["obfs-opts"]; ok {
|
||||
p["obfs-opts"] = value
|
||||
}
|
||||
// Translate TLS-like listener wrappers (shadow-tls/res-tls/jls-config)
|
||||
// into the snell `obfs-opts` format documented in proxies/snell.md.
|
||||
// Skipped when an explicit `obfs-opts` block is already present.
|
||||
applySnellObfsOpts(p, opts)
|
||||
result = append(result, p)
|
||||
case "vmess", "vless":
|
||||
if len(credentials) == 0 {
|
||||
@@ -223,7 +229,10 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
copyOption(p, opts, "authenticated-length")
|
||||
// encryption (client) and decryption (server) are a generated pair from
|
||||
// `mihomo generate vless-x25519` / `vless-mlkem768` — never copy decryption→encryption.
|
||||
copyOption(p, opts, "encryption")
|
||||
// VLESS has an `encryption` field (xtls-rprx-vision etc.); VMess does not.
|
||||
if protocol == "vless" {
|
||||
copyOption(p, opts, "encryption")
|
||||
}
|
||||
if flow := userFieldFromOpts(opts, cred.UUID, "flow"); flow != nil {
|
||||
p["flow"] = flow
|
||||
} else {
|
||||
@@ -266,7 +275,7 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
for _, key := range []string{
|
||||
"up", "down", "obfs", "obfs-password", "bbr-profile",
|
||||
"realm-opts", "alpn", "sni", "servername", "skip-cert-verify", "name-cert-verify",
|
||||
"fingerprint",
|
||||
"fingerprint", "handshake-timeout", "max-idle-time",
|
||||
} {
|
||||
copyOption(p, opts, key)
|
||||
}
|
||||
@@ -287,7 +296,7 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
if token, ok := opts["token"]; ok {
|
||||
p := makeProxy("")
|
||||
p["token"] = token
|
||||
for _, key := range []string{"congestion-controller", "bbr-profile", "max-idle-time", "authentication-timeout", "alpn", "max-udp-relay-packet-size"} {
|
||||
for _, key := range []string{"congestion-controller", "bbr-profile", "max-idle-time", "authentication-timeout", "alpn", "max-udp-relay-packet-size", "sni", "skip-cert-verify"} {
|
||||
copyOption(p, opts, key)
|
||||
}
|
||||
result = append(result, p)
|
||||
@@ -299,7 +308,7 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
p := makeProxy(fmt.Sprintf("%d", i+1))
|
||||
p["uuid"] = cred.UUID
|
||||
p["password"] = cred.Password
|
||||
for _, key := range []string{"congestion-controller", "bbr-profile", "max-idle-time", "authentication-timeout", "alpn", "max-udp-relay-packet-size"} {
|
||||
for _, key := range []string{"congestion-controller", "bbr-profile", "max-idle-time", "authentication-timeout", "alpn", "max-udp-relay-packet-size", "sni", "skip-cert-verify"} {
|
||||
copyOption(p, opts, key)
|
||||
}
|
||||
result = append(result, p)
|
||||
@@ -340,15 +349,13 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
if p["alpn"] == nil {
|
||||
p["alpn"] = []string{"h3"}
|
||||
}
|
||||
if p["quic-versions"] == nil {
|
||||
p["quic-versions"] = []string{"v2"}
|
||||
}
|
||||
if p["congestion-controller"] == nil {
|
||||
p["congestion-controller"] = "bbr"
|
||||
}
|
||||
if p["zero-rtt"] == nil {
|
||||
p["zero-rtt"] = true
|
||||
}
|
||||
// quic-versions / congestion-controller / zero-rtt are NOT defaulted
|
||||
// here: the official docs (proxies/shadowquic.md) specify defaults of
|
||||
// v1 / cubic / false, which mihomo applies automatically when these
|
||||
// fields are absent. Inventing conflicting defaults (v2/bbr/true)
|
||||
// would override the listener's intent and break connectivity with
|
||||
// servers that don't support them. They pass through via copyOption
|
||||
// above only when present in the listener config.
|
||||
result = append(result, p)
|
||||
}
|
||||
case "anytls":
|
||||
@@ -543,6 +550,89 @@ func ssClientFingerprint(src map[string]interface{}) string {
|
||||
return "chrome"
|
||||
}
|
||||
|
||||
// applySnellObfsOpts translates a Snell listener's TLS-like wrapper blocks
|
||||
// (shadow-tls/res-tls/jls-config) into the snell `obfs-opts` format documented
|
||||
// in proxies/snell.md. Snell carries these wrappers as obfs-opts (with a
|
||||
// `mode` field) rather than the `plugin`/`plugin-opts` form used by SS.
|
||||
//
|
||||
// Only the first matching enabled wrapper is emitted (they are mutually
|
||||
// exclusive on a single Snell listener). When the listener config already
|
||||
// provides an explicit `obfs-opts` block it is left untouched.
|
||||
func applySnellObfsOpts(p map[string]interface{}, src map[string]interface{}) {
|
||||
if _, ok := p["obfs-opts"]; ok {
|
||||
return
|
||||
}
|
||||
if stls, ok := src["shadow-tls"].(map[string]interface{}); ok {
|
||||
if enable, _ := stls["enable"].(bool); enable {
|
||||
opts := map[string]interface{}{"mode": "shadow-tls"}
|
||||
if v, ok := stls["version"]; ok {
|
||||
opts["version"] = v
|
||||
}
|
||||
// Password: v2 uses the top-level password; v3 uses users[0].password.
|
||||
if pwd, ok := stls["password"].(string); ok && pwd != "" {
|
||||
opts["password"] = pwd
|
||||
} else if users, ok := stls["users"].([]interface{}); ok && len(users) > 0 {
|
||||
if u, ok := users[0].(map[string]interface{}); ok {
|
||||
if pwd, ok := u["password"].(string); ok {
|
||||
opts["password"] = pwd
|
||||
}
|
||||
}
|
||||
}
|
||||
if alpn, ok := stls["alpn"]; ok {
|
||||
opts["alpn"] = alpn
|
||||
}
|
||||
// host from handshake.dest (strip :port).
|
||||
if hs, ok := stls["handshake"].(map[string]interface{}); ok {
|
||||
if dest, ok := hs["dest"].(string); ok {
|
||||
opts["host"] = stripHostPort(dest)
|
||||
}
|
||||
}
|
||||
p["obfs-opts"] = opts
|
||||
return
|
||||
}
|
||||
}
|
||||
if restls, ok := src["res-tls"].(map[string]interface{}); ok {
|
||||
if enable, _ := restls["enable"].(bool); enable {
|
||||
opts := map[string]interface{}{
|
||||
"mode": "restls",
|
||||
"version-hint": "tls13",
|
||||
}
|
||||
if dest, ok := restls["dest"].(string); ok {
|
||||
opts["host"] = stripHostPort(dest)
|
||||
}
|
||||
if pwd, ok := restls["password"].(string); ok {
|
||||
opts["password"] = pwd
|
||||
}
|
||||
if script, ok := restls["restls-script"].(string); ok && script != "" {
|
||||
opts["restls-script"] = script
|
||||
}
|
||||
p["obfs-opts"] = opts
|
||||
return
|
||||
}
|
||||
}
|
||||
if jls, ok := src["jls-config"].(map[string]interface{}); ok {
|
||||
if enable, _ := jls["enable"].(bool); enable {
|
||||
opts := map[string]interface{}{"mode": "jls"}
|
||||
if dest, ok := jls["dest"].(string); ok {
|
||||
opts["host"] = stripHostPort(dest)
|
||||
}
|
||||
// JLS users: use first user's username+password (single-user case).
|
||||
if users, ok := jls["users"].([]interface{}); ok && len(users) > 0 {
|
||||
if u, ok := users[0].(map[string]interface{}); ok {
|
||||
if un, ok := u["username"].(string); ok {
|
||||
opts["username"] = un
|
||||
}
|
||||
if pwd, ok := u["password"].(string); ok {
|
||||
opts["password"] = pwd
|
||||
}
|
||||
}
|
||||
}
|
||||
p["obfs-opts"] = opts
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stripHostPort removes the :port suffix from a host:port string, returning
|
||||
// the bare hostname. If the input has no port, it is returned unchanged.
|
||||
func stripHostPort(addr string) string {
|
||||
@@ -696,8 +786,6 @@ func realityClientOptions(src map[string]interface{}) map[string]interface{} {
|
||||
}
|
||||
if v, ok := cfg["support-x25519mlkem768"]; ok {
|
||||
result["support-x25519mlkem768"] = v
|
||||
} else {
|
||||
result["support-x25519mlkem768"] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package converter
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -85,7 +86,19 @@ func TestShadowQUICClientExport(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := proxies[0]
|
||||
if p["username"] != "u" || p["password"] != "p" || p["sni"] != "test.com" || p["udp"] != true || p["zero-rtt"] != true {
|
||||
t.Fatalf("%#v", p)
|
||||
if p["username"] != "u" || p["password"] != "p" || p["sni"] != "test.com" || p["udp"] != true {
|
||||
t.Fatalf("base fields mismatch: %#v", p)
|
||||
}
|
||||
// quic-versions / congestion-controller must pass through unchanged.
|
||||
if p["quic-versions"] == nil || fmt.Sprint(p["quic-versions"]) != "[v2]" {
|
||||
t.Fatalf("quic-versions not passed through: %#v", p["quic-versions"])
|
||||
}
|
||||
if p["congestion-controller"] != "bbr" {
|
||||
t.Fatalf("congestion-controller not passed through: %#v", p["congestion-controller"])
|
||||
}
|
||||
// zero-rtt is absent from the listener config; the converter must NOT
|
||||
// invent a default (official docs default is false, applied by mihomo).
|
||||
if _, ok := p["zero-rtt"]; ok {
|
||||
t.Fatalf("zero-rtt should not be defaulted when absent from listener config: %#v", p["zero-rtt"])
|
||||
}
|
||||
}
|
||||
@@ -323,7 +323,7 @@ func tuicURIs(name, host, port string, cfg map[string]interface{}) ([]string, er
|
||||
continue
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, out := range map[string]string{"congestion-controller": "congestion_control", "bbr-profile": "bbr_profile", "udp-relay-mode": "udp_relay_mode"} {
|
||||
for key, out := range map[string]string{"congestion-controller": "congestion-controller", "bbr-profile": "bbr-profile", "udp-relay-mode": "udp-relay-mode"} {
|
||||
if v, ok := cfg[key].(string); ok && v != "" {
|
||||
params[out] = v
|
||||
}
|
||||
@@ -354,7 +354,7 @@ func tuicURIs(name, host, port string, cfg map[string]interface{}) ([]string, er
|
||||
return nil, fmt.Errorf("tuic user %q has empty password", uuid)
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, out := range map[string]string{"congestion-controller": "congestion_control", "bbr-profile": "bbr_profile", "udp-relay-mode": "udp_relay_mode", "max-udp-relay-packet-size": "max_udp_relay_packet_size"} {
|
||||
for key, out := range map[string]string{"congestion-controller": "congestion-controller", "bbr-profile": "bbr-profile", "udp-relay-mode": "udp-relay-mode", "max-udp-relay-packet-size": "max-udp-relay-packet-size"} {
|
||||
if v, ok := cfg[key].(string); ok && v != "" {
|
||||
params[out] = v
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -79,7 +80,8 @@ func subscriptionHandler(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
if target == "v2ray" || target == "base64" || target == "raw" || target == "uri" {
|
||||
raw, err = converter.GenerateUserBase64Subscription(db, pu, c.Request, node.ClientURIsWithCredentials)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
||||
log.Printf("subscription error: %v", err)
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "subscription generation failed"})
|
||||
return
|
||||
}
|
||||
page := subpage.LoadPageSettings(db)
|
||||
@@ -104,7 +106,8 @@ func subscriptionHandler(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
if target == "singbox" || target == "sing-box" || target == "sfa" || target == "sfm" {
|
||||
raw, err = converter.GenerateUserSingboxSubscription(db, pu, c.Request)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
||||
log.Printf("subscription error: %v", err)
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "subscription generation failed"})
|
||||
return
|
||||
}
|
||||
writeSubHeaders(c, db, &pu)
|
||||
@@ -119,7 +122,8 @@ func subscriptionHandler(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
||||
log.Printf("subscription error: %v", err)
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "subscription generation failed"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -365,7 +369,8 @@ func writeSubHTML(c *gin.Context, db *gorm.DB, cfg *config.Config, pu models.Pro
|
||||
}
|
||||
html, err := subpage.RenderHTML(db, pu, base, links)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
log.Printf("subscription error: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "subscription generation failed"})
|
||||
return
|
||||
}
|
||||
c.Header("Cache-Control", "no-store")
|
||||
|
||||
@@ -250,8 +250,21 @@ export function configToFormValues(raw: string | undefined | null): Record<strin
|
||||
if (cfg['padding-min'] != null) values['padding-min'] = cfg['padding-min'];
|
||||
if (cfg['padding-max'] != null) values['padding-max'] = cfg['padding-max'];
|
||||
if (cfg['table-type']) values['table-type'] = cfg['table-type'];
|
||||
if (cfg['custom-table']) values['custom-table'] = cfg['custom-table'];
|
||||
if (cfg['custom-tables']) values['custom-tables'] = asStringList(cfg['custom-tables']);
|
||||
if (cfg['fallback']) values['fallback'] = cfg['fallback'];
|
||||
if (cfg['handshake-timeout'] != null) values['handshake-timeout'] = cfg['handshake-timeout'];
|
||||
if (cfg['enable-pure-downlink'] != null) values['enable-pure-downlink'] = cfg['enable-pure-downlink'];
|
||||
// sudoku httpmask nested block
|
||||
if (cfg['httpmask'] && typeof cfg['httpmask'] === 'object') {
|
||||
const hm = cfg['httpmask'];
|
||||
values.httpmask_enabled = true;
|
||||
values.httpmask_disable = !!hm.disable;
|
||||
values.httpmask_mode = hm.mode;
|
||||
values.httpmask_path_root = hm['path-root'] ?? hm['path_root'];
|
||||
} else {
|
||||
values.httpmask_enabled = false;
|
||||
}
|
||||
|
||||
// trusttunnel
|
||||
if (cfg.network) values.network = cfg.network;
|
||||
@@ -285,6 +298,7 @@ export function configToFormValues(raw: string | undefined | null): Record<strin
|
||||
delete values['realm-opts'];
|
||||
delete values['reality-config'];
|
||||
delete values['ss-option'];
|
||||
delete values['httpmask'];
|
||||
|
||||
if (cfg['ws-path']) values.transport_layer = 'ws';
|
||||
else if (cfg['grpc-service-name']) values.transport_layer = 'grpc';
|
||||
@@ -305,10 +319,11 @@ const FORM_OWNED_KEYS = new Set([
|
||||
'masquerade', 'alpn', 'max-idle-time', 'handshake-timeout', 'token', 'congestion-controller',
|
||||
'authentication-timeout', 'max-udp-relay-packet-size', 'zero-rtt', 'padding-scheme', 'transport',
|
||||
'key', 'aead-method', 'padding-min', 'padding-max', 'table-type', 'enable-pure-downlink',
|
||||
'custom-table', 'custom-tables', 'fallback', 'httpmask',
|
||||
'certificate', 'private-key', 'client-auth-type', 'client-auth-cert', 'ech-key', 'allow-insecure',
|
||||
'reality-config', 'users', 'simple-obfs', 'shadow-tls', 'res-tls', 'jls-config', 'mux-option',
|
||||
'kcp-tun', 'xhttp-config', 'mkcp-config', 'mekya-config', 'obfs-opts', 'jls-upstream', 'realm-opts',
|
||||
'network', 'bbr-profile', 'quic-versions', 'cwnd', 'traffic-pattern', 'user-hint-is-mandatory',
|
||||
'network', 'bbr-profile', 'quic-versions', 'cwnd', 'max-datagram-frame-size', 'recv-window-conn', 'recv-window', 'disable-mtu-discovery', 'traffic-pattern', 'user-hint-is-mandatory',
|
||||
]);
|
||||
|
||||
function cleanObj(obj: Record<string, any>): Record<string, any> | undefined {
|
||||
@@ -435,6 +450,10 @@ export function formValuesToConfig(
|
||||
set('cwnd', values.cwnd);
|
||||
set('bbr-profile', values['bbr-profile']);
|
||||
set('quic-versions', values['quic-versions']);
|
||||
set('max-datagram-frame-size', values['max-datagram-frame-size']);
|
||||
set('recv-window-conn', values['recv-window-conn']);
|
||||
set('recv-window', values['recv-window']);
|
||||
if (values['disable-mtu-discovery'] === true) cfg['disable-mtu-discovery'] = true;
|
||||
break;
|
||||
case 'anytls':
|
||||
set('padding-scheme', values['padding-scheme']);
|
||||
@@ -450,8 +469,18 @@ export function formValuesToConfig(
|
||||
set('padding-min', values['padding-min']);
|
||||
set('padding-max', values['padding-max']);
|
||||
set('table-type', values['table-type']);
|
||||
set('custom-table', values['custom-table']);
|
||||
set('custom-tables', values['custom-tables']);
|
||||
set('fallback', values['fallback']);
|
||||
set('handshake-timeout', values['handshake-timeout']);
|
||||
if (values['enable-pure-downlink'] === true) cfg['enable-pure-downlink'] = true;
|
||||
if (values.httpmask_enabled) {
|
||||
cfg['httpmask'] = cleanObj({
|
||||
disable: values.httpmask_disable === true ? true : undefined,
|
||||
mode: values.httpmask_mode,
|
||||
'path-root': values.httpmask_path_root,
|
||||
}) || {};
|
||||
}
|
||||
break;
|
||||
case 'trusttunnel':
|
||||
set('network', values.network);
|
||||
@@ -766,8 +795,8 @@ const ListenerConfigFields: React.FC<Props> = ({ protocol }) => {
|
||||
<Form.Item name="psk" label={t('listeners.psk')} tooltip={t('listeners.passwordOptionalHint') || 'Leave empty → auto-generate on save (SS / password protocols).'}>
|
||||
<Input.Password placeholder="auto" addonAfter={<Button type="link" size="small" onClick={async () => { const d = await generateMaterial({ kind: 'password' }); form.setFieldsValue({ psk: d.password }); }}>{t('common.generate') || 'Generate'}</Button>} />
|
||||
</Form.Item>
|
||||
<Form.Item name="version" label={t('listeners.snellVersion')} initialValue={3}>
|
||||
<Select options={[1, 2, 3].map((v) => ({ value: v, label: String(v) }))} />
|
||||
<Form.Item name="version" label={t('listeners.snellVersion')} initialValue={4}>
|
||||
<Select options={[1, 2, 3, 4, 5].map((v) => ({ value: v, label: String(v) }))} />
|
||||
</Form.Item>
|
||||
<Form.Item name="obfs_opts_mode" label={t('listeners.obfsOptsMode')}>
|
||||
<Select allowClear options={[{ value: 'http', label: 'http' }, { value: 'tls', label: 'tls' }]} />
|
||||
@@ -934,6 +963,18 @@ const ListenerConfigFields: React.FC<Props> = ({ protocol }) => {
|
||||
<Form.Item name="quic-versions" label={t('listeners.quicVersions')}>
|
||||
<Select mode="tags" tokenSeparators={[',']} />
|
||||
</Form.Item>
|
||||
<Form.Item name="max-datagram-frame-size" label={t('listeners.shadowquicMaxDatagramFrameSize', 'Max datagram frame size')}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} placeholder="1400" />
|
||||
</Form.Item>
|
||||
<Form.Item name="recv-window-conn" label={t('listeners.shadowquicRecvWindowConn', 'Recv window conn')}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} placeholder="0" />
|
||||
</Form.Item>
|
||||
<Form.Item name="recv-window" label={t('listeners.shadowquicRecvWindow', 'Recv window')}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} placeholder="0" />
|
||||
</Form.Item>
|
||||
<Form.Item name="disable-mtu-discovery" label={t('listeners.shadowquicDisableMtuDiscovery', 'Disable MTU discovery')} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -982,12 +1023,35 @@ const ListenerConfigFields: React.FC<Props> = ({ protocol }) => {
|
||||
<Form.Item name="table-type" label={t('listeners.tableType')}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="custom-table" label={t('listeners.customTable')} tooltip={t('listeners.customTable') || 'Custom byte layout (must contain 2x, 2p, 4v); entropy direction only'}>
|
||||
<Input placeholder="xpxvvpvv" />
|
||||
</Form.Item>
|
||||
<Form.Item name="custom-tables" label={t('listeners.customTables')} tooltip={t('listeners.customTables') || 'Custom byte layout list for multi-table rotation; overrides custom-table when non-empty'}>
|
||||
<Input.TextArea rows={2} placeholder="xpxvvpvv\nvxpvxvvp" />
|
||||
</Form.Item>
|
||||
<Form.Item name="handshake-timeout" label={t('listeners.handshakeTimeout')}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="enable-pure-downlink" label={t('listeners.enablePureDownlink')} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="fallback" label={t('listeners.sudokuFallback')} tooltip={t('listeners.sudokuFallback') || 'When HTTPMask is on, forward non-tunnel HTTP-like requests to this address (host:port)'}>
|
||||
<Input placeholder="127.0.0.1:80" />
|
||||
</Form.Item>
|
||||
<EnableSection name="httpmask_enabled" label={t('listeners.sectionHttpmask')} hint={t('listeners.sectionHttpmask') || 'HTTP tunnel masking for Sudoku'}>
|
||||
<Form.Item name="httpmask_disable" label={t('listeners.httpmaskDisable')} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="httpmask_mode" label={t('listeners.httpmaskMode')}>
|
||||
<Select
|
||||
allowClear
|
||||
options={['legacy', 'stream', 'poll', 'auto', 'ws'].map((v) => ({ value: v, label: v }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="httpmask_path_root" label={t('listeners.httpmaskPathRoot')}>
|
||||
<Input placeholder="aabbcc" />
|
||||
</Form.Item>
|
||||
</EnableSection>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
sectionSecurity: 'Security',
|
||||
sectionTransport: 'Transport', sectionTLS: 'TLS certificate', sectionReality: 'Reality', sectionSSOption: 'Trojan SS option', cipher: 'Cipher', password: 'Password', passwordOptional: 'Password (optional)', uuid: 'UUID', privateKey: 'Private key', certificate: 'Certificate', alpn: 'ALPN', sni: 'SNI', clientFingerprint: 'Client fingerprint', allowInsecure: 'Allow insecure', muxOption: 'Mux', realityPrivateKey: 'Reality private key', realityShortID: 'Short ID', realityServerNames: 'Server names', realityDest: 'Dest', transport: 'Transport', path: 'Path', host: 'Host', serviceName: 'Service name', up: 'Up', down: 'Down', ignoreClientBandwidth: 'Ignore client bandwidth', masquerade: 'Masquerade', congestionControl: 'Congestion control', clone: 'Clone', saveTemplate: 'Save as template', versions: 'Versions', cloned: 'Cloned', templateCreated: 'Template created', instantiated: 'Listener created from template', batchDone: 'Batch operation completed', templates: 'Listener Templates', templateName: 'Template name', createdAt: 'Created at', instantiate: 'Instantiate', deleteTemplateConfirm: 'Delete this template?', templateDeleted: 'Template deleted', enableSelected: 'Enable selected', disableSelected: 'Disable selected', newPort: 'New port', version: 'Version', reason: 'Reason', diff: 'Diff', rollback: 'Rollback', rollbackConfirm: 'Rollback to this version?', rollbackDone: 'Rollback completed' , addUser: 'Add user', aeadMethod: 'AEAD method', allowInsecureHint: 'Allow insecure (debug only)', alterId: 'Alter ID', alterIdHint: 'VMess alterId, prefer 0', authenticationTimeout: 'Auth timeout', bandwidthHint: 'Bandwidth limit, e.g. 100 mbps', bbrProfile: 'BBR profile', clientAuthCert: 'Client cert', clientAuthType: 'Client auth type', congestionController: 'Congestion controller', createWizard: 'Create listener wizard', cwnd: 'CWND', decryption: 'Decryption',
|
||||
encryption: 'Encryption',
|
||||
encryptionHint: 'Client-side VLESS encryption pair from generate output; do not reuse the decryption value', decryptionHint: 'Server-side VLESS decryption from mihomo generate vless-x25519 / vless-mlkem768', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Server private-key. Empty=auto on save. Client pbk derived for share URIs.', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS password', shadowTlsPasswordHint: 'v2/v3 password', shadowTlsUsers: 'ShadowTLS users', shadowTlsVersion: 'ShadowTLS version', simpleObfsMode: 'Simple Obfs mode', snellVersion: 'Snell version', ssOptionMethod: 'SS method', ssOptionPassword: 'SS password', sudokuKey: 'Sudoku key', tableType: 'Table type', tlsPairHint: 'Certificate and private key must both be set', token: 'Token', tokenHint: 'Auth token', tokenPlaceholder: 'Enter token', trafficPattern: 'Traffic pattern', userHintMandatory: 'At least one user required', wizardBasic: 'Basics', wizardConfig: 'Protocol', wizardConfigHint: 'Fill protocol-specific fields (TLS / Reality / users, etc.)', wizardReview: 'Review', wsPath: 'WebSocket path', wsPathHint: 'e.g. /ws', xhttpHint: 'XHTTP transport', xhttpHost: 'XHTTP host', xhttpMode: 'XHTTP mode', xhttpPath: 'XHTTP path', zeroRtt: '0-RTT'},
|
||||
encryptionHint: 'Client-side VLESS encryption pair from generate output; do not reuse the decryption value', decryptionHint: 'Server-side VLESS decryption from mihomo generate vless-x25519 / vless-mlkem768', echKey: 'ECH Key', enablePureDownlink: 'Pure downlink', flow: 'Flow', flowHint: 'e.g. xtls-rprx-vision', grpcHint: 'gRPC transport', grpcServiceName: 'gRPC service name', handshakeTimeout: 'Handshake timeout', jlsDest: 'JLS dest', jlsHint: 'JLS settings', jlsProxy: 'JLS proxy', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI hostname', jlsUpstreamAddr: 'JLS upstream', jlsUsers: 'JLS users', kcpTunConn: 'KCP connections', kcpTunCrypt: 'KCP crypt', kcpTunKey: 'KCP key', kcpTunMode: 'KCP mode', kcpTunNocomp: 'KCP no compress', kcpTunRcvwnd: 'KCP recv window', kcpTunSndwnd: 'KCP send window', maxIdleTime: 'Max idle time', maxUdpRelayPacketSize: 'Max UDP relay packet', mekyaHint: 'Mekya settings', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: 'Max simultaneous', mekyaMaxSimultaneousHint: 'Max concurrent connections', mekyaMaxWriteDuration: 'Max write duration', mekyaMaxWriteDurationHint: 'Write timeout', mekyaMaxWriteSize: 'Max write size', mekyaMaxWriteSizeHint: 'Single write limit', mekyaPacketBuffer: 'Packet buffer', mekyaPacketBufferHint: 'Packet buffer size', mkcpCongestion: 'mKCP congestion', mkcpDownlink: 'mKCP downlink', mkcpHeader: 'mKCP header', mkcpHint: 'mKCP transport', mkcpReadBuffer: 'mKCP read buffer', mkcpSeed: 'mKCP seed', mkcpUplink: 'mKCP uplink', mkcpWriteBuffer: 'mKCP write buffer', muxBrutal: 'Mux brutal', muxBrutalDown: 'Brutal down', muxBrutalUp: 'Brutal up', muxMaxConnections: 'Mux max connections', muxMaxStreams: 'Mux max streams', muxMinStreams: 'Mux min streams', muxOnlyTcp: 'Mux TCP only', muxPadding: 'Mux padding', muxProtocol: 'Mux protocol', muxStatistic: 'Mux statistics', network: 'Network', obfs: 'Obfuscation', obfsMaxPacketSize: 'Obfs max packet', obfsMinPacketSize: 'Obfs min packet', obfsOptsHost: 'Obfs host', obfsOptsMode: 'Obfs mode', obfsPassword: 'Obfs password', paddingMax: 'Padding max', paddingMin: 'Padding min', paddingScheme: 'Padding scheme', passwordOptionalHint: 'Optional; empty uses bound user password', passwordOptionalPlaceholder: 'Optional password', psk: 'PSK', quicVersions: 'QUIC versions', rateLimit: 'Rate limit', rateLimitHint: 'e.g. 10m', realityExclusiveHint: 'Reality and TLS cert are mutually exclusive', realityKeyHint: 'Server private-key. Empty=auto on save. Client pbk derived for share URIs.', realityShortId: 'Reality short ID', realmId: 'Realm ID', realmProxy: 'Realm proxy', realmServerUrl: 'Realm server URL', realmSkipCert: 'Skip cert verify', realmStun: 'STUN', realmToken: 'Realm token', resTlsDest: 'ResTLS dest', resTlsHint: 'ResTLS settings', resTlsMinRecordLen: 'Min record length', resTlsPassword: 'ResTLS password', resTlsProxy: 'ResTLS proxy', resTlsScript: 'ResTLS script', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS upstream', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: 'Handshake dest', shadowTlsHandshakeProxy: 'Handshake proxy', shadowTlsHint: 'ShadowTLS settings', shadowTlsPassword: 'ShadowTLS password', shadowTlsPasswordHint: 'v2/v3 password', shadowTlsUsers: 'ShadowTLS users', shadowTlsVersion: 'ShadowTLS version', simpleObfsMode: 'Simple Obfs mode', snellVersion: 'Snell version', ssOptionMethod: 'SS method', ssOptionPassword: 'SS password', sudokuKey: 'Sudoku key', customTable: 'Custom table', customTables: 'Custom tables', sudokuFallback: 'Fallback', sectionHttpmask: 'HTTPMask', httpmaskDisable: 'Disable HTTP mask', httpmaskMode: 'HTTP mask mode', httpmaskPathRoot: 'HTTP path root', tableType: 'Table type', tlsPairHint: 'Certificate and private key must both be set', token: 'Token', tokenHint: 'Auth token', tokenPlaceholder: 'Enter token', trafficPattern: 'Traffic pattern', userHintMandatory: 'At least one user required', wizardBasic: 'Basics', wizardConfig: 'Protocol', wizardConfigHint: 'Fill protocol-specific fields (TLS / Reality / users, etc.)', wizardReview: 'Review', wsPath: 'WebSocket path', wsPathHint: 'e.g. /ws', xhttpHint: 'XHTTP transport', xhttpHost: 'XHTTP host', xhttpMode: 'XHTTP mode', xhttpPath: 'XHTTP path', zeroRtt: '0-RTT'},
|
||||
core: { title: 'Core', status: 'Status', running: 'Running', stoppedStatus: 'Stopped', version: 'Version', pid: 'PID', uptime: 'Uptime', start: 'Start', stop: 'Stop', restart: 'Restart', started: 'Started', stopped: 'Stopped', restarted: 'Restarted' , operationFailed: 'Operation failed', unavailable: 'Core unavailable'},
|
||||
logs: { title: 'Logs', empty: 'No logs', level: 'Level', timestamp: 'Time', payload: 'Payload', autoRefresh: 'Auto refresh', clear: 'Clear' },
|
||||
config: { title: 'Config Engine', subtitle: 'Visual Mihomo configuration', visual: 'Visual Config', yaml: 'YAML Config', proxies: 'Proxy Configuration', yamlPreview: 'YAML Preview', addProxy: 'Add Proxy', editProxy: 'Edit Proxy', deleteProxy: 'Delete Proxy', deleteConfirm: 'Delete this proxy?', generateSuccess: 'Configuration generated successfully', validateSuccess: 'Configuration is valid', validateFailed: 'Configuration validation failed', proxyName: 'Proxy Name', proxyType: 'Proxy Type', proxyServer: 'Proxy Server', proxyPort: 'Proxy Port', proxyPassword: 'Proxy Password', proxyUUID: 'Proxy UUID' , generate: 'Generate config', validate: 'Validate config'},
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
sectionSecurity: '安全层',
|
||||
sectionTransport: '传输层', sectionTLS: 'TLS 证书', sectionReality: 'Reality', sectionSSOption: 'Trojan SS 混淆', cipher: '加密方法', password: '密码', passwordOptional: '密码(可选)', uuid: 'UUID', privateKey: '私钥', certificate: '证书', alpn: 'ALPN', sni: 'SNI', clientFingerprint: '客户端指纹', allowInsecure: '允许不安全', muxOption: '多路复用', realityPrivateKey: 'Reality 私钥', realityShortID: 'Short ID', realityServerNames: 'Server Names', realityDest: 'Dest', transport: '传输', path: '路径', host: 'Host', serviceName: 'Service Name', up: '上行', down: '下行', ignoreClientBandwidth: '忽略客户端带宽', masquerade: 'Masquerade', congestionControl: '拥塞控制', clone: '克隆', saveTemplate: '保存为模板', versions: '配置版本', cloned: '克隆成功', templateCreated: '模板创建成功', instantiated: '已从模板创建节点', batchDone: '批量操作完成', templates: '节点模板', templateName: '模板名称', createdAt: '创建时间', instantiate: '实例化', deleteTemplateConfirm: '确定删除此模板?', templateDeleted: '模板已删除', enableSelected: '启用选中', disableSelected: '禁用选中', newPort: '新端口', version: '版本', reason: '原因', diff: '配置差异', rollback: '回滚', rollbackConfirm: '确定回滚到此版本?', rollbackDone: '回滚完成' , addUser: '添加用户', aeadMethod: 'AEAD 加密方式', allowInsecureHint: '允许不安全连接(仅调试)', alterId: 'Alter ID', alterIdHint: 'VMess 额外 ID,建议 0', authenticationTimeout: '认证超时', bandwidthHint: '带宽限制,如 100 mbps', bbrProfile: 'BBR 配置', clientAuthCert: '客户端证书', clientAuthType: '客户端认证类型', congestionController: '拥塞控制', createWizard: '向导创建节点', cwnd: 'CWND', decryption: '解密',
|
||||
encryption: 'Encryption',
|
||||
encryptionHint: '客户端 VLESS Encryption:与 decryption 成对,取 generate 输出的 encryption 一侧,勿直接复制 decryption', decryptionHint: '服务端 VLESS Encryption:由 mihomo generate vless-x25519 / vless-mlkem768 生成,填 decryption 一侧', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: '服务端私钥。可留空保存时自动生成;客户端pbk由私钥推导写入分享链接。', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3 密码', shadowTlsUsers: 'ShadowTLS 用户', shadowTlsVersion: 'ShadowTLS 版本', simpleObfsMode: 'Simple Obfs 模式', snellVersion: 'Snell 版本', ssOptionMethod: 'SS 加密方式', ssOptionPassword: 'SS 密码', sudokuKey: 'Sudoku Key', tableType: '表类型', tlsPairHint: '证书与私钥需成对填写', token: 'Token', tokenHint: '认证 Token', tokenPlaceholder: '输入 Token', trafficPattern: '流量模式', userHintMandatory: '至少一个用户', wizardBasic: '基础信息', wizardConfig: '协议配置', wizardConfigHint: '填写协议相关字段(TLS / Reality / 用户等)', wizardReview: '确认创建', wsPath: 'WebSocket 路径', wsPathHint: '如 /ws', xhttpHint: 'XHTTP 传输', xhttpHost: 'XHTTP Host', xhttpMode: 'XHTTP 模式', xhttpPath: 'XHTTP 路径', zeroRtt: '0-RTT'},
|
||||
encryptionHint: '客户端 VLESS Encryption:与 decryption 成对,取 generate 输出的 encryption 一侧,勿直接复制 decryption', decryptionHint: '服务端 VLESS Encryption:由 mihomo generate vless-x25519 / vless-mlkem768 生成,填 decryption 一侧', echKey: 'ECH Key', enablePureDownlink: '纯下行', flow: '流控', flowHint: '如 xtls-rprx-vision', grpcHint: 'gRPC 传输', grpcServiceName: 'gRPC Service Name', handshakeTimeout: '握手超时', jlsDest: 'JLS 目标', jlsHint: 'Joliz 相关配置', jlsProxy: 'JLS 代理', jlsSni: 'JLS SNI', jlsSniHint: 'JLS SNI 主机名', jlsUpstreamAddr: 'JLS 上游地址', jlsUsers: 'JLS 用户', kcpTunConn: 'KCP 连接数', kcpTunCrypt: 'KCP 加密', kcpTunKey: 'KCP 密钥', kcpTunMode: 'KCP 模式', kcpTunNocomp: 'KCP 禁用压缩', kcpTunRcvwnd: 'KCP 接收窗口', kcpTunSndwnd: 'KCP 发送窗口', maxIdleTime: '最大空闲时间', maxUdpRelayPacketSize: '最大 UDP 中继包', mekyaHint: 'Mekya 相关配置', mekyaKcpSection: 'Mekya KCP', mekyaMaxSimultaneous: '最大并发', mekyaMaxSimultaneousHint: '最大同时连接数', mekyaMaxWriteDuration: '最大写入时长', mekyaMaxWriteDurationHint: '写入超时', mekyaMaxWriteSize: '最大写入大小', mekyaMaxWriteSizeHint: '单次写入上限', mekyaPacketBuffer: '包缓冲', mekyaPacketBufferHint: '数据包缓冲大小', mkcpCongestion: 'mKCP 拥塞控制', mkcpDownlink: 'mKCP 下行', mkcpHeader: 'mKCP 头类型', mkcpHint: 'mKCP 传输', mkcpReadBuffer: 'mKCP 读缓冲', mkcpSeed: 'mKCP Seed', mkcpUplink: 'mKCP 上行', mkcpWriteBuffer: 'mKCP 写缓冲', muxBrutal: 'Mux Brutal', muxBrutalDown: 'Brutal 下行', muxBrutalUp: 'Brutal 上行', muxMaxConnections: 'Mux 最大连接', muxMaxStreams: 'Mux 最大流', muxMinStreams: 'Mux 最小流', muxOnlyTcp: '仅 TCP Mux', muxPadding: 'Mux 填充', muxProtocol: 'Mux 协议', muxStatistic: 'Mux 统计', network: '传输网络', obfs: '混淆', obfsMaxPacketSize: '混淆最大包', obfsMinPacketSize: '混淆最小包', obfsOptsHost: '混淆 Host', obfsOptsMode: '混淆模式', obfsPassword: '混淆密码', paddingMax: '填充最大', paddingMin: '填充最小', paddingScheme: '填充方案', passwordOptionalHint: '可选,留空使用绑定用户密码', passwordOptionalPlaceholder: '可选密码', psk: 'PSK', quicVersions: 'QUIC 版本', rateLimit: '速率限制', rateLimitHint: '如 10m', realityExclusiveHint: 'Reality 与 TLS 证书互斥', realityKeyHint: '服务端私钥。可留空保存时自动生成;客户端pbk由私钥推导写入分享链接。', realityShortId: 'Reality Short ID', realmId: 'Realm ID', realmProxy: 'Realm 代理', realmServerUrl: 'Realm 服务地址', realmSkipCert: '跳过证书校验', realmStun: 'STUN', realmToken: 'Realm Token', resTlsDest: 'ResTLS 目标', resTlsHint: 'ResTLS 配置', resTlsMinRecordLen: '最小记录长度', resTlsPassword: 'ResTLS 密码', resTlsProxy: 'ResTLS 代理', resTlsScript: 'ResTLS 脚本', sectionJLS: 'JLS', sectionJlsUpstream: 'JLS 上游', sectionKcpTun: 'KCP Tun', sectionMekya: 'Mekya', sectionMkcp: 'mKCP', sectionMux: 'Mux', sectionRealm: 'Realm', sectionResTLS: 'ResTLS', sectionShadowTLS: 'ShadowTLS', sectionSimpleObfs: 'Simple Obfs', sectionXHTTP: 'XHTTP', shadowTlsHandshakeDest: '握手目标', shadowTlsHandshakeProxy: '握手代理', shadowTlsHint: 'ShadowTLS 配置', shadowTlsPassword: 'ShadowTLS 密码', shadowTlsPasswordHint: '版本 2/3 密码', shadowTlsUsers: 'ShadowTLS 用户', shadowTlsVersion: 'ShadowTLS 版本', simpleObfsMode: 'Simple Obfs 模式', snellVersion: 'Snell 版本', ssOptionMethod: 'SS 加密方式', ssOptionPassword: 'SS 密码', sudokuKey: 'Sudoku Key', customTable: '自定义表', customTables: '自定义表列表', sudokuFallback: '回落', sectionHttpmask: 'HTTPMask', httpmaskDisable: '禁用 HTTP 伪装', httpmaskMode: 'HTTP 伪装模式', httpmaskPathRoot: 'HTTP 路径前缀', tableType: '表类型', tlsPairHint: '证书与私钥需成对填写', token: 'Token', tokenHint: '认证 Token', tokenPlaceholder: '输入 Token', trafficPattern: '流量模式', userHintMandatory: '至少一个用户', wizardBasic: '基础信息', wizardConfig: '协议配置', wizardConfigHint: '填写协议相关字段(TLS / Reality / 用户等)', wizardReview: '确认创建', wsPath: 'WebSocket 路径', wsPathHint: '如 /ws', xhttpHint: 'XHTTP 传输', xhttpHost: 'XHTTP Host', xhttpMode: 'XHTTP 模式', xhttpPath: 'XHTTP 路径', zeroRtt: '0-RTT'},
|
||||
core: { title: '核心管理', status: '核心状态', running: '运行中', stoppedStatus: '已停止', version: '版本', pid: '进程 ID', uptime: '运行时间', start: '启动', stop: '停止', restart: '重启', started: '已启动', stopped: '已停止', restarted: '已重启' , operationFailed: '操作失败', unavailable: '核心不可用'},
|
||||
logs: { title: '运行日志', empty: '暂无日志', level: '级别', timestamp: '时间', payload: '内容', autoRefresh: '自动刷新', clear: '清空' },
|
||||
config: { title: '配置引擎', subtitle: '可视化管理 Mihomo 配置', visual: '可视化配置', yaml: 'YAML 配置', proxies: '代理配置', yamlPreview: 'YAML 预览', addProxy: '添加代理', editProxy: '编辑代理', deleteProxy: '删除代理', deleteConfirm: '确定要删除此代理吗?', generateSuccess: '配置生成成功', validateSuccess: '配置验证通过', validateFailed: '配置验证失败', proxyName: '代理名称', proxyType: '代理类型', proxyServer: '代理服务器', proxyPort: '代理端口', proxyPassword: '代理密码', proxyUUID: '代理 UUID' , generate: '生成配置', validate: '校验配置'},
|
||||
|
||||
Reference in new issue
Block a user