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`));
+ }
+});