fix(api): remove duplicate origin check causing LAN 403 on health-autopilot actions (#6277) (#6507)

fix(api): remove duplicate origin check causing LAN 403 on health-autopilot actions (#6277). Integrated into release/v3.8.47.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-07 18:14:40 -03:00
committed by GitHub
parent de9d748dac
commit f686e97d6c
3 changed files with 48 additions and 6 deletions

View File

@@ -14,6 +14,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
- **fix(providers):** grok-cli (Grok Build) now strips `reasoning_effort`/`reasoning` before forwarding the request ([#6288](https://github.com/diegosouzapw/OmniRoute/issues/6288)) — Claude Code sends `reasoning_effort` on every request (routing the Opus slot), which Grok Build's upstream chat-proxy endpoint rejects with a 400; `transformRequest()`'s existing `UNSUPPORTED` sampling-param strip list (#5273) never covered it. Regression guard: `tests/unit/grok-cli-reasoning-strip-6288.test.ts`.
- **fix(cli):** `omniroute serve` no longer hangs silently on a readiness timeout ([#6321](https://github.com/diegosouzapw/OmniRoute/issues/6321)) — the child server's stdout was piped to `"ignore"` whenever `--log`/`OMNIROUTE_SHOW_LOG` wasn't set (the default), discarding any debug output, and `runWithSupervisor`'s `waitForServer(...).then((up) => { if (up) {...} })` had no `else` branch, so a boot that never became ready produced zero further output after "⏳ Starting server...". Stdout is now buffered alongside stderr (`ServerSupervisor.getRecentLog()`), and a timeout prints a clear diagnostic plus the buffered output instead of staying silent. Does not by itself explain why boot never completes on a given machine — see the issue for further reproduction. Regression guard: `tests/unit/cli-serve-readiness-timeout-6321.test.ts`.
- **fix(pricing):** Pricing Sync dashboard no longer stuck on "Next Sync: Never" / "Synced Models: 0" ([#6325](https://github.com/diegosouzapw/OmniRoute/issues/6325)) — `pricingSync.ts` kept sync state (`lastSyncTime`, `lastSyncModelCount`) in module-level vars, but the background periodic sync (`instrumentation-node.ts`) and the dashboard status route (`/api/pricing/sync`) each import the module from separate Next.js standalone webpack chunks, giving each its own independent state; `getSyncStatus()` read the (empty) API-route instance's vars. Sync status is now additionally persisted to a new `pricing_sync_status` `key_value` namespace and `getSyncStatus()` falls back to it when the local module instance never ran a sync itself. Regression guard: `tests/unit/pricing-sync-cross-instance.test.ts`.
- **fix(api):** stop spuriously 403-ing "Invalid request origin" on `POST /api/providers/health-autopilot/actions` for Docker/LAN dashboard requests ([#6277](https://github.com/diegosouzapw/OmniRoute/issues/6277)) — the route carried a duplicate per-route `validateBrowserMutationOrigin` check re-added by the v3.8.42 release squash after PR #5278 centralized origin enforcement in the authz pipeline; the pipeline strips `PEER_IP_HEADER` before forwarding, so the stale duplicate check could no longer resolve the LAN "direct-local-host" candidate and rejected legitimate same-origin LAN mutations (e.g. clicking "remove cooldown" when accessed via a LAN IP). Removed the duplicate check — origin validation is now solely enforced by the centralized pipeline check, which already handles this case correctly. Regression guard: `tests/unit/serial/provider-health-autopilot.test.ts`.
### 📝 Maintenance

View File

@@ -3,7 +3,6 @@ import { z } from "zod";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { executeProviderHealthAutopilotAction } from "@/lib/monitoring/providerHealthAutopilot";
import { validateBrowserMutationOrigin } from "@/server/origin/publicOrigin";
import { validateBody } from "@/shared/validation/helpers";
const actionSchema = z.object({
@@ -29,11 +28,11 @@ export async function POST(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
const originVerdict = validateBrowserMutationOrigin(request);
if (!originVerdict.ok) {
return NextResponse.json({ error: { message: "Invalid request origin" } }, { status: 403 });
}
// Origin validation for browser mutations is centralized in the authz pipeline
// (src/server/authz/pipeline.ts) for MANAGEMENT routes — see PR #5278. Do NOT
// re-check origin here: the pipeline strips PEER_IP_HEADER before forwarding,
// so a duplicate per-route check cannot resolve the LAN "direct-local-host"
// candidate and spuriously rejects same-origin LAN/Docker requests (#6277).
try {
let rawBody: unknown;
try {

View File

@@ -217,6 +217,48 @@ test("provider health autopilot action rejects cross-site mutations", async () =
assert.ok(unchanged.rateLimitedUntil);
});
test("provider health autopilot action accepts LAN dashboard requests (#6277)", async () => {
// #6277: Docker/LAN deployments (accessed via LAN IP, not localhost) got a
// spurious 403 "Invalid request origin" clicking "remove cooldown". Root
// cause: this route carried a DUPLICATE per-route validateBrowserMutationOrigin
// check re-added by the v3.8.42 release squash after PR #5278 centralized
// origin enforcement in the authz pipeline. The pipeline's centralized check
// (src/server/authz/pipeline.ts) strips PEER_IP_HEADER before forwarding the
// request to the route handler, so by the time this handler runs the peer
// stamp is gone — exactly what a real post-middleware request looks like.
// The route must trust the pipeline's verdict and NOT re-validate origin
// itself; without PEER_IP_HEADER, the per-route check cannot resolve the LAN
// "direct-local-host" candidate and rejects a same-origin-but-LAN mutation.
await enableManagementAuth();
const connection = await createCooldownConnection();
const report = await autopilot.buildProviderHealthAutopilotReport({
provider: PROVIDER,
includeHealthy: true,
});
const action = findAction(report, "clear_connection_cooldown");
assert.ok(action);
const request = await makeManagementSessionRequest(
"http://localhost/api/providers/health-autopilot/actions",
{
method: "POST",
headers: { origin: "http://192.168.1.50:20128", "sec-fetch-site": "same-origin" },
body: {
type: action.type,
target: action.target,
preconditionsHash: action.preconditionsHash,
confirm: true,
},
}
);
assert.equal(request.headers.get("x-omniroute-peer-ip"), null);
const response = await actionsRoute.POST(request);
assert.equal(response.status, 200);
const body = await response.json();
assert.equal(body.success, true);
});
test("provider health autopilot action rejects malformed JSON", async () => {
await enableManagementAuth();