From 57a4617f1ef886b8d5f5733f79e8c77801156d8b Mon Sep 17 00:00:00 2001 From: Katana <115213458+OfficialKatana@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:14:09 +0800 Subject: [PATCH 1/5] Validate VMess outbound security settings Forbid insecure outbound settings. --- infra/conf/xray.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 39a1f76365b6..2f73cc42b72d 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -319,6 +319,27 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { if err != nil { return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err) } + + // Do not allow VMess outbound with Security == none and no TLS config in streamSettings + if vmCfg, ok := rawConfig.(*VMessOutboundConfig); ok { + sec := "" + if vmCfg.Address != nil { + sec = vmCfg.Security + } else if len(vmCfg.Receivers) > 0 { + rec := vmCfg.Receivers[0] + if len(rec.Users) > 0 { + acct := new(VMessAccount) + if err := json.Unmarshal(rec.Users[0], acct); err == nil { + sec = strings.ToLower(acct.Security) + } + } + } + if sec == "none" || sec == "auto" || sec == "" { + if senderSettings.StreamSettings == nil || senderSettings.StreamSettings.GetSecurityType() == "" { + return nil, errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") + } + } + } ts, err := rawConfig.(Buildable).Build() if err != nil { return nil, errors.New("failed to build outbound handler for protocol ", c.Protocol).Base(err) From 6f66c6d38e0b76f714e616d3c1680491490debfe Mon Sep 17 00:00:00 2001 From: Katana <115213458+OfficialKatana@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:30:31 +0800 Subject: [PATCH 2/5] Add detection for loopback and private addr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加了内网地址检测(IsLoopback和IsPrivate应该够了) --- infra/conf/xray.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 2f73cc42b72d..bbec83a939a5 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -335,8 +335,16 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { } } if sec == "none" || sec == "auto" || sec == "" { + // Unlikely if senderSettings.StreamSettings == nil || senderSettings.StreamSettings.GetSecurityType() == "" { - return nil, errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") + // Unlikely + if vmCfg.Address != nil { + toServer := vmCfg.Address.IP() + // Unlikely + if toServer != nil && !toServer.IsPrivate() && !toServer.IsLoopback() && !toServer.IsMulticast() && !toServer.IsLinkLocalUnicast() { + return nil, errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") + } + } } } } From 0e6e03bf3c68d836af32ff2650c23767823b5668 Mon Sep 17 00:00:00 2001 From: Katana <115213458+OfficialKatana@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:09:55 +0800 Subject: [PATCH 3/5] Implement address validation and TLS conflict checks on more protocols Add Vless Hysteria2 and Trojan support --- infra/conf/xray.go | 91 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index bbec83a939a5..a7825855bfec 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -226,6 +226,67 @@ func (c *OutboundDetourConfig) checkChainProxyConfig() error { return nil } +func isInternalOrInvalidAddress(address *Address) bool { + if address == nil { + return true + } + ip := address.IP() + if ip == nil { + return true + } + return ip.IsPrivate() || ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() +} + +func (c *OutboundDetourConfig) checkOutboundTLSConflict(rawConfig interface{}, senderSettings *proxyman.SenderConfig) error { + if senderSettings.StreamSettings != nil && senderSettings.StreamSettings.GetSecurityType() != "" { + return nil + } + // VMess + if vmCfg, ok := rawConfig.(*VMessOutboundConfig); ok { + return c.checkVMessTLSConflict(vmCfg) + } + + // VLESS should always with TLS + if vlessCfg, ok := rawConfig.(*VLessOutboundConfig); ok { + if !isInternalOrInvalidAddress(vlessCfg.Address) { + return errors.New("vless without TLS or reality is prohibited") + } + } + + // Trojan,unlikely + if _, ok := rawConfig.(*TrojanClientConfig); ok { + return errors.New("trojan without TLS is prohibited") + } + + // Hysteria, unlikely + if _, ok := rawConfig.(*HysteriaClientConfig); ok { + return errors.New("hysteria2 without TLS is prohibited") + } + + return nil +} +func (c *OutboundDetourConfig) checkVMessTLSConflict(vmCfg *VMessOutboundConfig) error { + sec := "" + if vmCfg.Address != nil { + sec = vmCfg.Security + } else if len(vmCfg.Receivers) > 0 { + rec := vmCfg.Receivers[0] + if len(rec.Users) > 0 { + acct := new(VMessAccount) + if err := json.Unmarshal(rec.Users[0], acct); err == nil { + sec = strings.ToLower(acct.Security) + } + } + } + + if sec == "none" || sec == "auto" || sec == "" { + if !isInternalOrInvalidAddress(vmCfg.Address) { + return errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") + } + } + return nil +} + // Build implements Buildable. func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { senderSettings := &proxyman.SenderConfig{} @@ -319,34 +380,8 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { if err != nil { return nil, errors.New("failed to load outbound detour config for protocol ", c.Protocol).Base(err) } - - // Do not allow VMess outbound with Security == none and no TLS config in streamSettings - if vmCfg, ok := rawConfig.(*VMessOutboundConfig); ok { - sec := "" - if vmCfg.Address != nil { - sec = vmCfg.Security - } else if len(vmCfg.Receivers) > 0 { - rec := vmCfg.Receivers[0] - if len(rec.Users) > 0 { - acct := new(VMessAccount) - if err := json.Unmarshal(rec.Users[0], acct); err == nil { - sec = strings.ToLower(acct.Security) - } - } - } - if sec == "none" || sec == "auto" || sec == "" { - // Unlikely - if senderSettings.StreamSettings == nil || senderSettings.StreamSettings.GetSecurityType() == "" { - // Unlikely - if vmCfg.Address != nil { - toServer := vmCfg.Address.IP() - // Unlikely - if toServer != nil && !toServer.IsPrivate() && !toServer.IsLoopback() && !toServer.IsMulticast() && !toServer.IsLinkLocalUnicast() { - return nil, errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") - } - } - } - } + if err := c.checkOutboundTLSConflict(rawConfig, senderSettings); err != nil { + return nil, err } ts, err := rawConfig.(Buildable).Build() if err != nil { From 24c7dd19c65a92ee26a6a2c1bfe6cdae26dc8412 Mon Sep 17 00:00:00 2001 From: Katana <115213458+OfficialKatana@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:22:45 +0800 Subject: [PATCH 4/5] Remove duplicated detection Removed duplicated TLS outbound detection on hysteria2 (should always with QUIC) --- infra/conf/xray.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index a7825855bfec..b730586a0db6 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -259,9 +259,9 @@ func (c *OutboundDetourConfig) checkOutboundTLSConflict(rawConfig interface{}, s } // Hysteria, unlikely - if _, ok := rawConfig.(*HysteriaClientConfig); ok { - return errors.New("hysteria2 without TLS is prohibited") - } + // if _, ok := rawConfig.(*HysteriaClientConfig); ok { + // return errors.New("hysteria2 without TLS is prohibited") + // } return nil } From 3603a0ad321e7a2856c1fa746a306828337fd601 Mon Sep 17 00:00:00 2001 From: Katana <115213458+OfficialKatana@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:11:37 +0800 Subject: [PATCH 5/5] Update error messages and security checks Add extra detection for Trojan protocol --- infra/conf/xray.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index b730586a0db6..150f1f37f36c 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -246,16 +246,19 @@ func (c *OutboundDetourConfig) checkOutboundTLSConflict(rawConfig interface{}, s return c.checkVMessTLSConflict(vmCfg) } - // VLESS should always with TLS + // VLESS should always with encryption if vlessCfg, ok := rawConfig.(*VLessOutboundConfig); ok { if !isInternalOrInvalidAddress(vlessCfg.Address) { - return errors.New("vless without TLS or reality is prohibited") + // When the encryption present, GetSecurityType() => should not be empty + return errors.New("vless without TLS or other encryption is prohibited") } } // Trojan,unlikely - if _, ok := rawConfig.(*TrojanClientConfig); ok { - return errors.New("trojan without TLS is prohibited") + if tjCfg, ok := rawConfig.(*TrojanClientConfig); ok { + if !isInternalOrInvalidAddress(tjCfg.Address) { + return errors.New("trojan without TLS is prohibited") + } } // Hysteria, unlikely @@ -279,9 +282,9 @@ func (c *OutboundDetourConfig) checkVMessTLSConflict(vmCfg *VMessOutboundConfig) } } - if sec == "none" || sec == "auto" || sec == "" { + if sec == "none" || sec == "auto" || sec == "" || sec == "zero" { if !isInternalOrInvalidAddress(vmCfg.Address) { - return errors.New("vmess with 'none' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") + return errors.New("vmess with 'none' or 'zero' security is conflicted without TLS in streamSettings, alternatively, you can use the socks5 protocol.") } } return nil