diff --git a/open-sse/utils/proxyDispatcher.ts b/open-sse/utils/proxyDispatcher.ts index acd399315d..4b49eaf770 100644 --- a/open-sse/utils/proxyDispatcher.ts +++ b/open-sse/utils/proxyDispatcher.ts @@ -138,8 +138,15 @@ function buildProxyUrlString(parsed: URL, port: string): string { return `${parsed.protocol}//${auth}${parsed.hostname}:${port}`; } +/** + * SOCKS5 proxy support defaults ON (opt-OUT). A fresh deploy with no env set + * should honour SOCKS5 proxies out of the box — they were silently rejected + * before (default OFF), making accounts fall back to the host IP. Only an + * explicit falsey value (false/0/no/off) disables it. + */ export function isSocks5ProxyEnabled(): boolean { - return process.env.ENABLE_SOCKS5_PROXY === "true"; + const raw = (process.env.ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase(); + return !["false", "0", "no", "off"].includes(raw); } export function proxyUrlForLogs(proxyUrl: string): string { @@ -173,7 +180,7 @@ export function normalizeProxyUrl( } if (parsed.protocol === "socks5:" && !allowSocks5) { throw new Error( - "[ProxyDispatcher] SOCKS5 proxy is disabled (set ENABLE_SOCKS5_PROXY=true to enable)" + "[ProxyDispatcher] SOCKS5 proxy is disabled (remove ENABLE_SOCKS5_PROXY=false to enable — it is ON by default)" ); } if (!parsed.hostname) { @@ -233,7 +240,7 @@ export function proxyConfigToUrl( } if (protocol === "socks5:" && !allowSocks5) { throw new Error( - "[ProxyDispatcher] SOCKS5 proxy is disabled (set ENABLE_SOCKS5_PROXY=true to enable)" + "[ProxyDispatcher] SOCKS5 proxy is disabled (remove ENABLE_SOCKS5_PROXY=false to enable — it is ON by default)" ); } diff --git a/src/app/(dashboard)/dashboard/settings/components/proxy/DocumentationTab.tsx b/src/app/(dashboard)/dashboard/settings/components/proxy/DocumentationTab.tsx index ea6ddde29f..d5a0861480 100644 --- a/src/app/(dashboard)/dashboard/settings/components/proxy/DocumentationTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/proxy/DocumentationTab.tsx @@ -41,7 +41,8 @@ export default function DocumentationTab() {

SOCKS5

{t("proxyDocumentationSocks5DescBefore")}{" "} - ENABLE_SOCKS5_PROXY=true to enable. + ENABLE_SOCKS5_PROXY=false to disable + (ON by default).

diff --git a/src/app/api/settings/proxies/route.ts b/src/app/api/settings/proxies/route.ts index 9d923cf3c2..4bb156f9b6 100644 --- a/src/app/api/settings/proxies/route.ts +++ b/src/app/api/settings/proxies/route.ts @@ -42,7 +42,10 @@ export async function GET(request: Request) { return Response.json({ items: proxies, total: proxies.length, - socks5Enabled: process.env.ENABLE_SOCKS5_PROXY === "true", + // Default ON (opt-out): only an explicit falsey value disables SOCKS5. + socks5Enabled: !["false", "0", "no", "off"].includes( + (process.env.ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase() + ), }); } catch (error) { return createErrorResponseFromUnknown(error, "Failed to load proxies"); diff --git a/src/app/api/settings/proxy/route.ts b/src/app/api/settings/proxy/route.ts index 1c59ee6353..2a878316b8 100755 --- a/src/app/api/settings/proxy/route.ts +++ b/src/app/api/settings/proxy/route.ts @@ -31,7 +31,9 @@ const PROXY_LEVEL_TO_REGISTRY_SCOPE = { } as const; function isSocks5Enabled() { - return process.env.ENABLE_SOCKS5_PROXY === "true"; + // Default ON (opt-out): only an explicit falsey value disables SOCKS5. + const raw = (process.env.ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase(); + return !["false", "0", "no", "off"].includes(raw); } function getSupportedProxyTypes() { @@ -106,7 +108,7 @@ function normalizeAndValidateProxy( const type = String(proxy.type || "http").toLowerCase() as NonNullable; if (type === "socks5" && !isSocks5Enabled()) { throw createInvalidProxyError( - "SOCKS5 proxy is disabled (set ENABLE_SOCKS5_PROXY=true to enable)" + "SOCKS5 proxy is disabled (remove ENABLE_SOCKS5_PROXY=false to enable — it is ON by default)" ); } if (type.startsWith("socks") && type !== "socks5") { diff --git a/src/shared/components/ProxyConfigModal.tsx b/src/shared/components/ProxyConfigModal.tsx index 1ec9cb2922..ce5f0f3e0a 100644 --- a/src/shared/components/ProxyConfigModal.tsx +++ b/src/shared/components/ProxyConfigModal.tsx @@ -12,7 +12,10 @@ const ALL_PROXY_TYPES = [ ]; // Build-time fallback (static deploys). The live value comes from GET /api/settings/proxies // (server ENABLE_SOCKS5_PROXY) so a runtime Docker env is honoured — #3508. -const BUILD_TIME_SOCKS5 = process.env.NEXT_PUBLIC_ENABLE_SOCKS5_PROXY === "true"; +// Default ON (opt-out) to match the server: only an explicit falsey value hides SOCKS5. +const BUILD_TIME_SOCKS5 = !["false", "0", "no", "off"].includes( + (process.env.NEXT_PUBLIC_ENABLE_SOCKS5_PROXY ?? "").trim().toLowerCase() +); export function buildProxyTypes(socks5Enabled: boolean) { return socks5Enabled ? ALL_PROXY_TYPES : ALL_PROXY_TYPES.filter((type) => type.value !== "socks5"); } diff --git a/tests/unit/socks5-default-on.test.ts b/tests/unit/socks5-default-on.test.ts new file mode 100644 index 0000000000..02ed37c616 --- /dev/null +++ b/tests/unit/socks5-default-on.test.ts @@ -0,0 +1,43 @@ +/** + * TDD — SOCKS5 proxy support must default ON (opt-OUT), so a fresh deploy + * (Docker/npm/Electron with no .env) honours SOCKS5 proxies out of the box. + * Previously the code defaulted OFF (`=== "true"`), so SOCKS5 proxies were + * silently rejected unless the operator set the env explicitly — accounts then + * fell back to the host IP. Only an explicit falsey value disables it now. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { isSocks5ProxyEnabled } = await import("../../open-sse/utils/proxyDispatcher.ts"); + +function withEnv(value: string | undefined, fn: () => void) { + const prev = process.env.ENABLE_SOCKS5_PROXY; + if (value === undefined) delete process.env.ENABLE_SOCKS5_PROXY; + else process.env.ENABLE_SOCKS5_PROXY = value; + try { + fn(); + } finally { + if (prev === undefined) delete process.env.ENABLE_SOCKS5_PROXY; + else process.env.ENABLE_SOCKS5_PROXY = prev; + } +} + +test("SOCKS5 is ON by default when the env is unset", () => { + withEnv(undefined, () => assert.equal(isSocks5ProxyEnabled(), true)); +}); + +test("SOCKS5 is ON for empty string (treated as unset)", () => { + withEnv("", () => assert.equal(isSocks5ProxyEnabled(), true)); +}); + +test("SOCKS5 stays ON for explicit true-ish values", () => { + for (const v of ["true", "TRUE", "1", "yes", "on"]) { + withEnv(v, () => assert.equal(isSocks5ProxyEnabled(), true, `value ${v} must enable`)); + } +}); + +test("SOCKS5 is OFF only for explicit falsey values (opt-out)", () => { + for (const v of ["false", "FALSE", "0", "no", "off"]) { + withEnv(v, () => assert.equal(isSocks5ProxyEnabled(), false, `value ${v} must disable`)); + } +});