From c1428e8df9a497d45b3ed3d35f70419d736fdd4b Mon Sep 17 00:00:00 2001 From: Sid Date: Mon, 2 Mar 2026 05:23:54 +0800 Subject: [PATCH] fix(gateway): prevent /api/* routes from returning SPA HTML when basePath is empty (#30333) Merged via squash. Prepared head SHA: 12591f304e5db80b0a49d44b3adeecace5ce228c Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com> Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com> Reviewed-by: @velvet-shark --- CHANGELOG.md | 1 + src/cron/service.issue-regressions.test.ts | 6 ++++-- src/gateway/control-ui.http.test.ts | 15 +++++++++++++++ src/gateway/control-ui.ts | 3 +++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 960c0f9e4..b5f829e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ Docs: https://docs.openclaw.ai - Web UI/Chat sessions: add a cron-session visibility toggle in the session selector, fix cron-key detection across `cron:*` and `agent:*:cron:*` formats, and localize the new control labels/tooltips. (#26976) Thanks @ianderrington. - Web UI/Cron jobs: add schedule-kind and last-run-status filters to the Jobs list, with reset control and client-side filtering over loaded results. (#9510) Thanks @guxu11. - Web UI/Control UI WebSocket defaults: include normalized `gateway.controlUi.basePath` (or inferred nested route base path) in the default `gatewayUrl` so first-load dashboard connections work behind path-based reverse proxies. (#30228) Thanks @gittb. +- Gateway/Control UI API routing: when `gateway.controlUi.basePath` is unset (default), stop serving Control UI SPA HTML for `/api` and `/api/*` so API paths fall through to normal gateway handlers/404 responses instead of `index.html`. (#30333) Fixes #30295. thanks @Sid-Qin. - Cron/One-shot reliability: retry transient one-shot failures with bounded backoff and configurable retry policy before disabling. (#24435) Thanks . - Gateway/Cron auditability: add gateway info logs for successful cron create, update, and remove operations. (#25090) Thanks . - Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks . diff --git a/src/cron/service.issue-regressions.test.ts b/src/cron/service.issue-regressions.test.ts index 09f5cf0b1..5a8753d4a 100644 --- a/src/cron/service.issue-regressions.test.ts +++ b/src/cron/service.issue-regressions.test.ts @@ -1325,7 +1325,6 @@ describe("Cron issue regressions", () => { }); it("respects abort signals while retrying main-session wake-now heartbeat runs", async () => { - vi.useRealTimers(); const abortController = new AbortController(); const runHeartbeatOnce = vi.fn( async (): Promise => ({ @@ -1364,7 +1363,10 @@ describe("Cron issue regressions", () => { abortController.abort(); }, 10); - const result = await executeJobCore(state, mainJob, abortController.signal); + const resultPromise = executeJobCore(state, mainJob, abortController.signal); + // Advance virtual time so the abort fires before the busy-wait fallback window expires. + await vi.advanceTimersByTimeAsync(10); + const result = await resultPromise; expect(result.status).toBe("error"); expect(result.error).toContain("timed out"); diff --git a/src/gateway/control-ui.http.test.ts b/src/gateway/control-ui.http.test.ts index aa8c923ae..06bca5e35 100644 --- a/src/gateway/control-ui.http.test.ts +++ b/src/gateway/control-ui.http.test.ts @@ -326,6 +326,21 @@ describe("handleControlUiHttpRequest", () => { }); }); + it("does not handle /api paths when basePath is empty", async () => { + await withControlUiRoot({ + fn: async (tmp) => { + for (const apiPath of ["/api", "/api/sessions", "/api/channels/nostr"]) { + const { handled } = runControlUiRequest({ + url: apiPath, + method: "GET", + rootPath: tmp, + }); + expect(handled, `expected ${apiPath} to not be handled`).toBe(false); + } + }, + }); + }); + it("rejects absolute-path escape attempts under basePath routes", async () => { await withBasePathRootFixture({ siblingDir: "ui-secrets", diff --git a/src/gateway/control-ui.ts b/src/gateway/control-ui.ts index ed7b7330e..18b8fb987 100644 --- a/src/gateway/control-ui.ts +++ b/src/gateway/control-ui.ts @@ -292,6 +292,9 @@ export function handleControlUiHttpRequest( respondNotFound(res); return true; } + if (pathname === "/api" || pathname.startsWith("/api/")) { + return false; + } } if (basePath) {