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
This commit is contained in:
@@ -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 .
|
||||
|
||||
@@ -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<HeartbeatRunResult> => ({
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -292,6 +292,9 @@ export function handleControlUiHttpRequest(
|
||||
respondNotFound(res);
|
||||
return true;
|
||||
}
|
||||
if (pathname === "/api" || pathname.startsWith("/api/")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (basePath) {
|
||||
|
||||
Reference in New Issue
Block a user