From 6ab718f8134d1445fe05e2e26663fc29a6b45d30 Mon Sep 17 00:00:00 2001 From: Kirill Rudenko Date: Sat, 26 Sep 2026 23:03:20 +0300 Subject: [PATCH] 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 Co-authored-by: Claude Opus 5.5 Co-authored-by: Sanaei --- docs/public/openapi.json | 8 +++++ frontend/public/openapi.json | 8 +++++ frontend/src/generated/examples.ts | 2 ++ frontend/src/generated/schemas.ts | 8 +++++ frontend/src/generated/types.ts | 2 ++ frontend/src/generated/zod.ts | 2 ++ frontend/src/models/setting.ts | 1 + .../pages/settings/HappSettingsContent.tsx | 17 ++++++++++ frontend/src/schemas/setting.ts | 1 + internal/sub/happ.go | 7 ++++ internal/sub/happ_test.go | 34 +++++++++++++++++++ internal/sub/sub.go | 1 + internal/web/entity/entity.go | 1 + internal/web/service/setting.go | 5 +++ internal/web/translation/ar-EG.json | 5 +++ internal/web/translation/en-US.json | 5 +++ internal/web/translation/es-ES.json | 5 +++ internal/web/translation/fa-IR.json | 5 +++ internal/web/translation/id-ID.json | 5 +++ internal/web/translation/ja-JP.json | 5 +++ internal/web/translation/pt-BR.json | 5 +++ internal/web/translation/ru-RU.json | 5 +++ internal/web/translation/tr-TR.json | 5 +++ internal/web/translation/uk-UA.json | 5 +++ internal/web/translation/vi-VN.json | 5 +++ internal/web/translation/zh-CN.json | 5 +++ internal/web/translation/zh-TW.json | 5 +++ 27 files changed, 162 insertions(+) diff --git a/docs/public/openapi.json b/docs/public/openapi.json index d35139a3b..ae423a7fa 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -288,6 +288,9 @@ "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -586,6 +589,7 @@ "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", @@ -943,6 +947,9 @@ "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -1249,6 +1256,7 @@ "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index d35139a3b..ae423a7fa 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -288,6 +288,9 @@ "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -586,6 +589,7 @@ "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", @@ -943,6 +947,9 @@ "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -1249,6 +1256,7 @@ "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 190ca1c07..27cb25065 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -80,6 +80,7 @@ export const EXAMPLES: Record = { "subHappExcludeApns": false, "subHappExcludeRoutes": "", "subHappFallbackUrl": "", + "subHappLocalProxyAuth": "", "subHappNewUrl": "", "subHappNoLimit": false, "subHappNotificationExpire": false, @@ -237,6 +238,7 @@ export const EXAMPLES: Record = { "subHappExcludeApns": false, "subHappExcludeRoutes": "", "subHappFallbackUrl": "", + "subHappLocalProxyAuth": "", "subHappNewUrl": "", "subHappNoLimit": false, "subHappNotificationExpire": false, diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index ee8650062..db8e7761e 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -262,6 +262,9 @@ export const SCHEMAS: Record = { "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -560,6 +563,7 @@ export const SCHEMAS: Record = { "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", @@ -917,6 +921,9 @@ export const SCHEMAS: Record = { "subHappFallbackUrl": { "type": "string" }, + "subHappLocalProxyAuth": { + "type": "string" + }, "subHappNewUrl": { "type": "string" }, @@ -1223,6 +1230,7 @@ export const SCHEMAS: Record = { "subHappExcludeApns", "subHappExcludeRoutes", "subHappFallbackUrl", + "subHappLocalProxyAuth", "subHappNewUrl", "subHappNoLimit", "subHappNotificationExpire", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index bb916a8b1..c9070e73c 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -87,6 +87,7 @@ export interface AllSetting { subHappExcludeApns: boolean; subHappExcludeRoutes: string; subHappFallbackUrl: string; + subHappLocalProxyAuth: string; subHappNewUrl: string; subHappNoLimit: boolean; subHappNotificationExpire: boolean; @@ -245,6 +246,7 @@ export interface AllSettingView { subHappExcludeApns: boolean; subHappExcludeRoutes: string; subHappFallbackUrl: string; + subHappLocalProxyAuth: string; subHappNewUrl: string; subHappNoLimit: boolean; subHappNotificationExpire: boolean; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index deb007369..e04f3a14a 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -101,6 +101,7 @@ export const AllSettingSchema = z.object({ subHappExcludeApns: z.boolean(), subHappExcludeRoutes: z.string(), subHappFallbackUrl: z.string(), + subHappLocalProxyAuth: z.string(), subHappNewUrl: z.string(), subHappNoLimit: z.boolean(), subHappNotificationExpire: z.boolean(), @@ -260,6 +261,7 @@ export const AllSettingViewSchema = z.object({ subHappExcludeApns: z.boolean(), subHappExcludeRoutes: z.string(), subHappFallbackUrl: z.string(), + subHappLocalProxyAuth: z.string(), subHappNewUrl: z.string(), subHappNoLimit: z.boolean(), subHappNotificationExpire: z.boolean(), diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index 38fffa794..65598828c 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -104,6 +104,7 @@ export class AllSetting { subHappAutoConnectType = 'lowestdelay'; subHappPerAppMode = 'off'; subHappPerAppList = ''; + subHappLocalProxyAuth = 'auto'; timeLocation = 'Local'; diff --git a/frontend/src/pages/settings/HappSettingsContent.tsx b/frontend/src/pages/settings/HappSettingsContent.tsx index bba39a454..6434330ac 100644 --- a/frontend/src/pages/settings/HappSettingsContent.tsx +++ b/frontend/src/pages/settings/HappSettingsContent.tsx @@ -382,6 +382,23 @@ export default function HappSettingsContent({ /> + +