mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-16 02:44:59 +03:00
fix(sub): make Happ require auth on its local SOCKS/HTTP proxy (#6628)
Happ ships its local SOCKS5 (127.0.0.1:10808) and HTTP inbounds with authorization disabled by default. Any app on the same device can then connect to that proxy, bypassing Android's per-app VPN routing, and learn the VPN server address - the leak publicly described in March-April 2026 for Happ, v2rayNG and other VLESS clients. Happ fixed its Xray API exposure, but the unauthenticated local proxy remained. Happ exposes a standard subscription header for this (no Provider ID required): socks-auth-mode / http-auth-mode = auto|manual|from-json| disable. A new subscription setting, subHappLocalProxyAuth (default "auto"), sends both headers to Happ clients. Like every other Happ header it is emitted only when Happ auto-detect is enabled and the User-Agent is Happ, so panels that never opted into the Happ integration see no change. An empty value sends nothing and keeps the client's own setting. Verified on Happ Android 4.4.1 (Xray 26.7.28): a subscription carrying socks-auth-mode manual + a test user/password switched the client's Inbounds screen to Manual with those credentials on "refresh subscription", and "auto" switched it to Auto with generated credentials. Co-authored-by: Kirill Rudenko <rudenko@npp-energy.ru> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -34,6 +34,7 @@ type HappConfig struct {
|
||||
AutoConnectType string
|
||||
PerAppMode string
|
||||
PerAppList string
|
||||
LocalProxyAuth string
|
||||
}
|
||||
|
||||
// IsHappClient checks if the client user-agent identifies as Happ.
|
||||
@@ -129,6 +130,12 @@ func ApplyHappHeaders(c *gin.Context, cfg HappConfig, isHapp bool) {
|
||||
c.Writer.Header().Set("Subscription-Autoconnect-Type", autoType)
|
||||
}
|
||||
}
|
||||
// Happ's local SOCKS/HTTP inbounds default to no auth, so any app on the device can use them
|
||||
// to reach the tunnel and learn the server address; these are standard headers (no ProviderID).
|
||||
if mode := strings.TrimSpace(cfg.LocalProxyAuth); mode != "" {
|
||||
c.Writer.Header().Set("Socks-Auth-Mode", mode)
|
||||
c.Writer.Header().Set("Http-Auth-Mode", mode)
|
||||
}
|
||||
if mode := strings.TrimSpace(cfg.PerAppMode); mode != "" && mode != "off" {
|
||||
switch strings.ToLower(mode) {
|
||||
case "include":
|
||||
|
||||
@@ -348,3 +348,37 @@ func TestIsHappClient(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHappHeaders_LocalProxyAuth(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
cases := []struct {
|
||||
name string
|
||||
autoDetect bool
|
||||
userAgent string
|
||||
mode string
|
||||
want string
|
||||
}{
|
||||
{"Happ gets auto for both inbounds", true, "Happ/4.4.1 (Android)", "auto", "auto"},
|
||||
{"explicit disable is forwarded", true, "Happ/4.4.1 (Android)", "disable", "disable"},
|
||||
{"empty mode sends nothing", true, "Happ/4.4.1 (Android)", "", ""},
|
||||
{"non-Happ client gets nothing", true, "v2rayNG/1.8.5", "auto", ""},
|
||||
{"AutoDetect off sends nothing", false, "Happ/4.4.1 (Android)", "auto", ""},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
|
||||
ctx.Request.Header.Set("User-Agent", tc.userAgent)
|
||||
|
||||
controller := &SUBController{happConfig: HappConfig{AutoDetect: tc.autoDetect, LocalProxyAuth: tc.mode}}
|
||||
controller.ApplyCommonHeaders(ctx, "", "", "Title", "", "", "", false, "", false)
|
||||
|
||||
for _, h := range []string{"Socks-Auth-Mode", "Http-Auth-Mode"} {
|
||||
if got := recorder.Header().Get(h); got != tc.want {
|
||||
t.Fatalf("%s = %q, want %q", h, got, tc.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +253,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
happCfg.AutoConnectType, _ = s.settingService.GetSubHappAutoConnectType()
|
||||
happCfg.PerAppMode, _ = s.settingService.GetSubHappPerAppMode()
|
||||
happCfg.PerAppList, _ = s.settingService.GetSubHappPerAppList()
|
||||
happCfg.LocalProxyAuth, _ = s.settingService.GetSubHappLocalProxyAuth()
|
||||
|
||||
// set per-request localizer from headers/cookies
|
||||
engine.Use(locale.LocalizerMiddleware())
|
||||
|
||||
Reference in New Issue
Block a user