From db6236b2ff5efc84b50bcb4d33f64585cc4d5d25 Mon Sep 17 00:00:00 2001 From: SidQin-cyber Date: Sun, 1 Mar 2026 13:25:18 +0800 Subject: [PATCH] fix(gateway): prevent /api/* routes from returning SPA HTML when basePath is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When controlUiBasePath is unset (empty string), handleControlUiHttpRequest acts as a catch-all and serves index.html for every unmatched path — including /api/* routes. This causes API consumers (browser UI, curl, Discord) to receive an HTML page instead of the expected JSON 404. Add an early return false for /api and /api/* paths in the empty-basePath branch so they fall through to the gateway's normal 404 handler. Closes #30295 --- src/gateway/control-ui.http.test.ts | 15 +++++++++++++++ src/gateway/control-ui.ts | 3 +++ 2 files changed, 18 insertions(+) diff --git a/src/gateway/control-ui.http.test.ts b/src/gateway/control-ui.http.test.ts index aa8c923aed9..06bca5e35e9 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 ed7b7330e91..18b8fb98753 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) {