From b6ffe8ce20d549338cc75a8c3e28688b709300e2 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:32:05 -0300 Subject: [PATCH] fix(proxy): make "Test All" read-only + add bulk enable/disable (#6246) (#6299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged into release/v3.8.45. Delta do par #6246 (Test-All read-only + bulk enable/disable), reconciliado com o núcleo #6296 já mergeado. CHANGELOG restaurado (ambos bullets do #6246 coexistem). Reds pré-existentes: dast-smoke (infra) + file-size drift. --- .env.example | 5 + CHANGELOG.md | 2 + docs/reference/ENVIRONMENT.md | 1 + .../settings/components/ProxyBatchActions.tsx | 14 ++ .../components/ProxyRegistryManager.tsx | 8 + .../components/useProxyBatchOperations.ts | 31 ++++ .../api/settings/proxies/auto-test/route.ts | 10 +- .../settings/proxies/batch-activate/route.ts | 72 ++++++++ src/i18n/messages/en.json | 1 + src/lib/proxyHealth/statusPolicy.ts | 41 +++++ tests/unit/proxy-health-6246.test.ts | 161 ++++++++++++++++++ 11 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 src/app/api/settings/proxies/batch-activate/route.ts create mode 100644 src/lib/proxyHealth/statusPolicy.ts create mode 100644 tests/unit/proxy-health-6246.test.ts diff --git a/.env.example b/.env.example index 76da31ccf2..0790badeae 100644 --- a/.env.example +++ b/.env.example @@ -1521,6 +1521,11 @@ APP_LOG_TO_FILE=true # PROXY_AUTO_REMOVE=false # Consecutive failures before an auto-remove fires. Default: 3. # PROXY_AUTO_REMOVE_AFTER=3 +# Let automated reachability probes (the scheduler + the "Test All" button) WRITE +# a proxy's status. Default "false": probes are read-only and never deactivate a +# proxy — only the operator sets active/inactive (a flaky probe must not strand an +# assigned proxy; #6246). Set "true" to restore the legacy test-and-set behaviour. +# PROXY_HEALTH_AUTO_DEACTIVATE=false # Allow OAuth and provider validation flows to bypass a pinned proxy and connect # directly when proxy reachability pre-checks fail. Default: false. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4388bba3d1..396ae33f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - **fix(proxy):** stop the v3.8.44 proxy regression that leaked the real IP and disabled healthy proxies ([#6246](https://github.com/diegosouzapw/OmniRoute/issues/6246)). Two coupled defects from the new health scheduler: (1) **IP leak** — when a proxy assigned to a connection was marked `inactive`, resolution fell through to a **direct** egress instead of blocking, exposing the operator's real IP; (2) **over-deactivation** — the sweep flipped a proxy to `inactive` on the **first** failed probe and counted our own 5s timeout / a probe-target `5xx` as the proxy's fault, so healthy paid proxies vanished from egress selection ("my proxies are not being used anymore"). Fix: the sweep decision is extracted into a pure, network-free `decideProxyHealthAction` (`src/lib/proxyHealth/decision.ts`) — by default the health check now **only counts/logs and never downgrades status** (a proxy is downgraded/removed only with `PROXY_AUTO_REMOVE=true`, after `PROXY_AUTO_REMOVE_AFTER` **consecutive** conclusive failures); probes are classified tri-state so an inconclusive result (our timeout, or a `5xx` from the probe target) never penalizes the proxy, and the probe timeout is raised 5s→15s. Separately, `safeResolveProxy` now **fails closed** via the existing policy: a connection whose assigned proxy is dead is blocked instead of leaking direct (`hasBlockingProxyAssignment`), honoring the explicit `proxy off` toggles and the `PROXY_FAIL_OPEN=true` opt-out. Existing proxies stuck `inactive` by the old behavior need a one-time manual re-activate (the operator owns proxy status). Regression guards: `tests/unit/proxy-health-decide-action-6246.test.ts`, `tests/unit/proxy-assigned-unavailable-6246.test.ts`. +- **fix(proxy):** make "Test All" read-only and add bulk enable/disable ([#6246](https://github.com/diegosouzapw/OmniRoute/issues/6246)). Complements the core fail-closed / scheduler fix (#6296) with the two remaining reporter asks. (1) The **"Test All" button** (`POST /api/settings/proxies/auto-test`) used to flip a proxy to `inactive` on a failed reachability probe; since the egress selector excludes `inactive` proxies, a flaky probe (an unreachable `httpbin.org`, a proxy that blocks `HEAD`, or a slow paid proxy) silently disabled every proxy that failed — "Test All" is now **read-only by default** (only the operator sets a proxy active/inactive; opt back into the legacy test-and-set with `PROXY_HEALTH_AUTO_DEACTIVATE=true`). (2) Adds a **bulk enable/disable** proxies endpoint + toolbar action (`POST /api/settings/proxies/batch-activate`) so an operator can re-activate proxies in one click. Regression guard: `tests/unit/proxy-health-6246.test.ts`. (thanks @tenshiak) + - **chatcore (tools): stop the default 128-tool cap from silently dropping opencode's `task`/MCP tools.** opencode (used as an MCP/agent host) sends a large tool list; when it exceeds the speculative `MAX_TOOLS_LIMIT` (128) default, `truncateToolList` did a blind `tools.slice(0, 128)`, dropping every tool past index 128 — including opencode's built-in `task` tool (subagent launch) and many MCP tools, so models routed through OmniRoute could no longer spawn subagents or reach part of their tools. The cap exists to avoid upstream `400`s for providers with real hard limits (e.g. grok-cli 200), so it is kept for those: detection of the opencode client (`isOpencodeClient` — any `x-opencode-*` header, or `opencode` in the user-agent) now only bypasses the **speculative 128 default**, never a known provider ceiling. Precedence is explicit — a proactive/detected provider limit always truncates (even for opencode); otherwise opencode forwards its full tool list; otherwise the unchanged 128 default applies to every other client. Refactors `getEffectiveToolLimit` into `getKnownToolLimit(provider) ?? DEFAULT_LIMIT` (byte-identical for existing callers) and fixes a cosmetic debug-log that reported the truncated count instead of the original. Regression guard: `tests/unit/tool-limit-detector.test.ts`. - **fix(mitm):** the macOS MITM-cert install check now matches the system keychain again. `security find-certificate -a -Z` prints the SHA-1 as a colon-less hex string, but the installed-check compared it against `getCertFingerprint()`'s colon-separated form, so the substring match never hit — the cert was reported as not-installed and re-prompted for the sudo install on every run. Fingerprints are now normalized (colons stripped, upper-cased) on both sides via the extracted `macCertOutputHasFingerprint` helper. Regression guard: `tests/unit/mitm-cert-mac-fingerprint.test.ts`. ([#6204](https://github.com/diegosouzapw/OmniRoute/pull/6204), closes [#6134](https://github.com/diegosouzapw/OmniRoute/issues/6134) — thanks @rianonehub) diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 3dada4840a..95bfa5bcf5 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -836,6 +836,7 @@ Anthropic-compatible provider instead. | `PROXY_HEALTH_ENABLED` | `true` | `src/lib/proxyHealth/scheduler.ts` | Set `false` to disable the background proxy health scheduler that periodically probes registered proxies. | | `PROXY_HEALTH_INTERVAL_MS` | `600000` | `src/lib/proxyHealth/scheduler.ts` | Background health-scheduler sweep interval in ms (minimum `60000`). | | `PROXY_HEALTH_TEST_URL` | `https://httpbin.org/ip` | `src/lib/proxyHealth/scheduler.ts` | Reachability probe target used by the scheduler and the `/api/settings/proxies/auto-test` endpoint. Point it at an internal/self-hosted URL to avoid the public default. | +| `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. | | `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. | | `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). | | `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Effective precedence is Feature Flags DB override > env var > default. | diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyBatchActions.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyBatchActions.tsx index a9e537a884..dd90571427 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyBatchActions.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyBatchActions.tsx @@ -7,7 +7,9 @@ interface ProxyBatchActionsProps { selectedCount: number; batchDeleting: boolean; autoTesting: boolean; + batchActivating: boolean; onBatchDelete: () => void; + onBatchActivate: () => void; onAutoTestAll: () => void; } @@ -15,7 +17,9 @@ export function ProxyBatchActions({ selectedCount, batchDeleting, autoTesting, + batchActivating, onBatchDelete, + onBatchActivate, onAutoTestAll, }: ProxyBatchActionsProps) { const t = useTranslations("proxyRegistry"); @@ -27,6 +31,16 @@ export function ProxyBatchActions({ {t("batchSelectedCount", { count: selectedCount })} +