mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 10:20:21 +00:00
gateway: fix global Control UI 404s for symlinked wrappers and bundled package roots (#40385)
Merged via squash.
Prepared head SHA: 567b3ed684
Co-authored-by: velvet-shark <126378+velvet-shark@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:
committed by
GitHub
parent
13bd3db307
commit
4f42c03a49
@@ -45,6 +45,7 @@ describe("handleControlUiHttpRequest", () => {
|
||||
method: "GET" | "HEAD" | "POST";
|
||||
rootPath: string;
|
||||
basePath?: string;
|
||||
rootKind?: "resolved" | "bundled";
|
||||
}) {
|
||||
const { res, end } = makeMockHttpResponse();
|
||||
const handled = handleControlUiHttpRequest(
|
||||
@@ -52,7 +53,7 @@ describe("handleControlUiHttpRequest", () => {
|
||||
res,
|
||||
{
|
||||
...(params.basePath ? { basePath: params.basePath } : {}),
|
||||
root: { kind: "resolved", path: params.rootPath },
|
||||
root: { kind: params.rootKind ?? "resolved", path: params.rootPath },
|
||||
},
|
||||
);
|
||||
return { res, end, handled };
|
||||
@@ -326,6 +327,72 @@ describe("handleControlUiHttpRequest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects hardlinked index.html for non-package control-ui roots", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-index-hardlink-"));
|
||||
try {
|
||||
const outsideIndex = path.join(outsideDir, "index.html");
|
||||
await fs.writeFile(outsideIndex, "<html>outside-hardlink</html>\n");
|
||||
await fs.rm(path.join(tmp, "index.html"));
|
||||
await fs.link(outsideIndex, path.join(tmp, "index.html"));
|
||||
|
||||
const { res, end, handled } = runControlUiRequest({
|
||||
url: "/",
|
||||
method: "GET",
|
||||
rootPath: tmp,
|
||||
});
|
||||
expectNotFoundResponse({ handled, res, end });
|
||||
} finally {
|
||||
await fs.rm(outsideDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects hardlinked asset files for custom/resolved roots (security boundary)", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
const assetsDir = path.join(tmp, "assets");
|
||||
await fs.mkdir(assetsDir, { recursive: true });
|
||||
await fs.writeFile(path.join(assetsDir, "app.js"), "console.log('hi');");
|
||||
await fs.link(path.join(assetsDir, "app.js"), path.join(assetsDir, "app.hl.js"));
|
||||
|
||||
const { res, end, handled } = runControlUiRequest({
|
||||
url: "/assets/app.hl.js",
|
||||
method: "GET",
|
||||
rootPath: tmp,
|
||||
});
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(end).toHaveBeenCalledWith("Not Found");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("serves hardlinked asset files for bundled roots (pnpm global install)", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
const assetsDir = path.join(tmp, "assets");
|
||||
await fs.mkdir(assetsDir, { recursive: true });
|
||||
await fs.writeFile(path.join(assetsDir, "app.js"), "console.log('hi');");
|
||||
await fs.link(path.join(assetsDir, "app.js"), path.join(assetsDir, "app.hl.js"));
|
||||
|
||||
const { res, end, handled } = runControlUiRequest({
|
||||
url: "/assets/app.hl.js",
|
||||
method: "GET",
|
||||
rootPath: tmp,
|
||||
rootKind: "bundled",
|
||||
});
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(String(end.mock.calls[0]?.[0] ?? "")).toBe("console.log('hi');");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not handle POST to root-mounted paths (plugin webhook passthrough)", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
|
||||
Reference in New Issue
Block a user