



























@@ -108,9 +108,13 @@ async function captureHandlerResponse(
108108return await captureHttpResponse(handler.handleHttpRequest, url, method);
109109}
110110111-async function captureA2uiResponse(url: string, method = "GET"): Promise<CapturedResponse> {
112-const { handleA2uiHttpRequest } = await import("./a2ui.js");
113-return await captureHttpResponse(handleA2uiHttpRequest, url, method);
111+async function captureA2uiFixtureResponse(
112+rootDir: string,
113+url: string,
114+method = "GET",
115+): Promise<CapturedResponse> {
116+const { createA2uiHttpRequestHandler } = await import("./a2ui.js");
117+return await captureHttpResponse(createA2uiHttpRequestHandler({ rootDir }), url, method);
114118}
115119116120describe("canvas host", () => {
@@ -439,46 +443,56 @@ describe("canvas host", () => {
439443});
440444441445it("serves A2UI scaffold and blocks traversal/symlink escapes", async () => {
442-const a2uiRoot = path.resolve(process.cwd(), "extensions/canvas/src/host/a2ui");
443-const bundlePath = path.join(a2uiRoot, "a2ui.bundle.js");
446+const a2uiRoot = await createCaseDir();
447+const nestedAssetDir = path.join(a2uiRoot, "assets", "demo");
444448const linkName = `test-link-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`;
445449const linkPath = path.join(a2uiRoot, linkName);
446-let createdBundle = false;
447-448-try {
449-await fs.stat(bundlePath);
450-} catch {
451-await fs.writeFile(bundlePath, "window.openclawA2UI = {};", "utf8");
452-createdBundle = true;
453-}
454450451+await fs.mkdir(nestedAssetDir, { recursive: true });
452+await fs.writeFile(
453+path.join(a2uiRoot, "index.html"),
454+`<openclaw-a2ui-host></openclaw-a2ui-host>
455+<script>openclawCanvasA2UIAction</script>`,
456+"utf8",
457+);
458+await fs.writeFile(path.join(a2uiRoot, "a2ui.bundle.js"), "window.openclawA2UI = {};", "utf8");
459+await fs.writeFile(path.join(nestedAssetDir, "sample.txt"), "nested asset", "utf8");
455460await fs.symlink(path.join(process.cwd(), "package.json"), linkPath);
456461457462try {
458-const res = await captureA2uiResponse(`${A2UI_PATH}/`);
463+const res = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/`);
459464const html = res.body;
460465expect(res.status).toBe(200);
461466expect(html).toContain("openclaw-a2ui-host");
462467expect(html).toContain("openclawCanvasA2UIAction");
463468464-const bundleRes = await captureA2uiResponse(`${A2UI_PATH}/a2ui.bundle.js`);
469+const bundleRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/a2ui.bundle.js`);
465470const js = bundleRes.body;
466471expect(bundleRes.status).toBe(200);
467472expect(js).toContain("openclawA2UI");
468-const traversalRes = await captureA2uiResponse(`${A2UI_PATH}/%2e%2e%2fpackage.json`);
473+474+const assetRes = await captureA2uiFixtureResponse(
475+a2uiRoot,
476+`${A2UI_PATH}/assets/demo/sample.txt`,
477+);
478+expect(assetRes.status).toBe(200);
479+expect(assetRes.headers["content-type"]).toBe("text/plain");
480+expect(assetRes.body).toBe("nested asset");
481+482+const traversalRes = await captureA2uiFixtureResponse(
483+a2uiRoot,
484+`${A2UI_PATH}/%2e%2e%2fpackage.json`,
485+);
469486expect(traversalRes.status).toBe(404);
470487expect(traversalRes.body).toBe("not found");
471-const malformedRes = await captureA2uiResponse(`${A2UI_PATH}/%E0%A4%A`);
488+const malformedRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/%E0%A4%A`);
472489expect(malformedRes.status).toBe(404);
473490expect(malformedRes.body).toBe("not found");
474-const symlinkRes = await captureA2uiResponse(`${A2UI_PATH}/${linkName}`);
491+const symlinkRes = await captureA2uiFixtureResponse(a2uiRoot, `${A2UI_PATH}/${linkName}`);
475492expect(symlinkRes.status).toBe(404);
476493expect(symlinkRes.body).toBe("not found");
477494} finally {
478495await fs.rm(linkPath, { force: true });
479-if (createdBundle) {
480-await fs.rm(bundlePath, { force: true });
481-}
482496}
483497});
484498});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。