From f89b5c5e46ae3648eea1c3a2ad42389fa5a593b8 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:58:18 -0300 Subject: [PATCH] fix(proxy): make auto-selection fallback opt-in (#3332) (#3344) selectWorkingProxyFallback (Step 11 of resolveProxyForConnection) listed ALL registry proxies, ignoring assignments and per-connection proxy_enabled, and returned the first working one with level:'autoSelect'. So a single proxy added to the registry silently became a global fallback for every connection's traffic. Gate it behind a new PROXY_AUTO_SELECT_ENABLED feature flag (default off): the fallback now no-ops unless the operator opts in. No registry proxy becomes a silent global default anymore. Co-authored-by: hertznsk --- CHANGELOG.md | 1 + open-sse/utils/proxyFallback.ts | 7 +++ .../constants/featureFlagDefinitions.ts | 12 ++++ .../unit/proxy-autoselect-optin-3332.test.ts | 55 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 tests/unit/proxy-autoselect-optin-3332.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b46d9eace..bf834cac8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ _Development cycle in progress — entries are added as work merges into `releas ### 🔧 Bug Fixes +- **fix(proxy):** proxy auto-selection is now **opt-in** (new `PROXY_AUTO_SELECT_ENABLED` flag, default off). Previously a single proxy in the registry silently became a global fallback for **all** provider connections (the Step-11 fallback listed every registry proxy, ignoring assignments and per-connection `proxy_enabled`). It now no-ops unless the operator enables the flag. (#3332 — thanks @hertznsk) - **fix(cli):** write the OpenCode config to `~/.config/opencode/opencode.json` on **all** platforms — on Windows OmniRoute wrote to `%APPDATA%\opencode\` but OpenCode reads from `%USERPROFILE%\.config\opencode\` (XDG), so dashboard-saved config silently had no effect. (#3330 — thanks @abdulkadirozyurt) - **fix(catalog):** remove `minimaxai/minimax-m3` from the **NVIDIA NIM** tier — NVIDIA does not host it yet, so every request 404'd (`404 page not found`), while sibling `minimax-m2.7` on the same provider works. MiniMax M3 stays available on the tiers that actually serve it. (#3329 — thanks @mikmaneggahommie) - **fix(electron):** ship `loginManager.js` in the packaged app — #3292 added it (and a `require("./loginManager")` in `main.js`) without adding it to electron-builder's `build.files`, so the packaged app crashed at startup with "Cannot find module" on the Linux/macOS smoke tests. Plus a regression test asserting every local `require("./x")` in the Electron entry points is shipped. ([#3334](https://github.com/diegosouzapw/OmniRoute/pull/3334) — thanks @diegosouzapw) diff --git a/open-sse/utils/proxyFallback.ts b/open-sse/utils/proxyFallback.ts index 8e5b246072..165ce37854 100644 --- a/open-sse/utils/proxyFallback.ts +++ b/open-sse/utils/proxyFallback.ts @@ -10,6 +10,7 @@ import { fetch as undiciFetch } from "undici"; import { createProxyDispatcher, normalizeProxyUrl } from "./proxyDispatcher.ts"; import { resolveProxyForScopeFromRegistry, listProxies, listOneproxyProxies } from "@/lib/localDb"; +import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; // --------------------------------------------------------------------------- // Types @@ -347,6 +348,12 @@ export async function selectWorkingProxyFallback( levelId: string | null; source: string; } | null> { + // #3332: auto-selection is opt-in. Without this gate, any single proxy in the + // registry silently becomes a global fallback for ALL connections (ignoring + // assignments / per-connection proxy_enabled). Default OFF — only run when the + // operator explicitly enables PROXY_AUTO_SELECT_ENABLED. + if (!isFeatureFlagEnabled("PROXY_AUTO_SELECT_ENABLED")) return null; + const candidates = await getProxyCandidates(); if (candidates.length === 0) return null; diff --git a/src/shared/constants/featureFlagDefinitions.ts b/src/shared/constants/featureFlagDefinitions.ts index f7d273c42e..faf5427307 100644 --- a/src/shared/constants/featureFlagDefinitions.ts +++ b/src/shared/constants/featureFlagDefinitions.ts @@ -117,6 +117,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ requiresRestart: false, warningLevel: "info", }, + { + key: "PROXY_AUTO_SELECT_ENABLED", + label: "Proxy Auto-Selection Fallback", + description: + "When no proxy is assigned to a connection, auto-select the first working proxy from the registry. Off by default — otherwise any single registry proxy becomes a global fallback for all traffic (#3332).", + descriptionI18nKey: "settings.featureFlags.proxyAutoSelectEnabled", + category: "network", + defaultValue: "false", + type: "boolean", + requiresRestart: false, + warningLevel: "caution", + }, { key: "MITM_DISABLE_TLS_VERIFY", label: "Disable TLS Verify (MITM)", diff --git a/tests/unit/proxy-autoselect-optin-3332.test.ts b/tests/unit/proxy-autoselect-optin-3332.test.ts new file mode 100644 index 0000000000..35f58c77fe --- /dev/null +++ b/tests/unit/proxy-autoselect-optin-3332.test.ts @@ -0,0 +1,55 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-proxy-3332-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { FEATURE_FLAG_DEFINITIONS } = await import( + "../../src/shared/constants/featureFlagDefinitions.ts" +); +const { isFeatureFlagEnabled } = await import("../../src/shared/utils/featureFlags.ts"); +const { selectWorkingProxyFallback } = await import("../../open-sse/utils/proxyFallback.ts"); + +// #3332: a single proxy in the registry was silently applied to ALL connections +// via the auto-selection fallback. The fix makes auto-selection opt-in behind +// PROXY_AUTO_SELECT_ENABLED, default OFF — so no registry proxy becomes a global +// default unless the operator explicitly turns it on. + +test("PROXY_AUTO_SELECT_ENABLED exists and defaults to off (opt-in) (#3332)", () => { + const def = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === "PROXY_AUTO_SELECT_ENABLED"); + assert.ok(def, "PROXY_AUTO_SELECT_ENABLED flag must be defined"); + assert.equal(def.defaultValue, "false", "auto-selection must be opt-in (default off)"); + delete process.env.PROXY_AUTO_SELECT_ENABLED; + assert.equal(isFeatureFlagEnabled("PROXY_AUTO_SELECT_ENABLED"), false); +}); + +test("selectWorkingProxyFallback short-circuits to null when the flag is off, even with a candidate", async () => { + delete process.env.PROXY_AUTO_SELECT_ENABLED; + const prevAllProxy = process.env.ALL_PROXY; + // A candidate exists (env proxy) — yet auto-selection must NOT run while off. + process.env.ALL_PROXY = "http://127.0.0.1:1"; + try { + const result = await selectWorkingProxyFallback("conn-1"); + assert.equal(result, null, "no auto-selected proxy while the flag is off"); + } finally { + if (prevAllProxy === undefined) delete process.env.ALL_PROXY; + else process.env.ALL_PROXY = prevAllProxy; + } +}); + +test.after(() => { + try { + core.resetDbInstance?.(); + } catch { + /* ignore */ + } + try { + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + } catch { + /* ignore */ + } +});