Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit fbccdfc

Browse files
authored
Merge pull request #273 from closedloop-ai/hotfix-design-renderer-assets
HOTFIX: Serve design dashboard assets
2 parents 6ac17cb + 985e065 commit fbccdfc

3 files changed

Lines changed: 22 additions & 9 deletions

File tree

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "desktop",
3-
"version": "0.15.113",
3+
"version": "0.15.114",
44
"description": "ClosedLoop Desktop",
55
"author": "ClosedLoop AI <support@closedloop.ai>",
66
"private": true,

apps/desktop/src/main/window.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
88

99
const RENDERER_DIR = path.resolve(__dirname, "..", "renderer");
1010
const DESIGN_RENDERER_DIR = path.join(RENDERER_DIR, "design-system");
11+
const ASSETS_RENDERER_DIR = path.join(RENDERER_DIR, "assets");
1112
const DESIGN_RENDERER_URL = "app://renderer/design-system/index.html";
1213
const APP_PROTOCOL = "app";
1314
const EXTERNAL_LINK_HOSTS = new Set([
@@ -89,11 +90,12 @@ function serveAppProtocolAsset(request: Request): Response {
8990
return new Response("Bad request", { status: 400 });
9091
}
9192

92-
if (
93-
!decodedPathname.startsWith("/design-system/") ||
94-
decodedPathname.includes("\0") ||
95-
decodedPathname.includes("\\")
96-
) {
93+
if (decodedPathname.includes("\0") || decodedPathname.includes("\\")) {
94+
return new Response("Not found", { status: 404 });
95+
}
96+
97+
const assetRoot = resolveAppProtocolRoot(decodedPathname);
98+
if (!assetRoot) {
9799
return new Response("Not found", { status: 404 });
98100
}
99101

@@ -104,7 +106,7 @@ function serveAppProtocolAsset(request: Request): Response {
104106
}
105107

106108
const filePath = path.resolve(RENDERER_DIR, relativePath);
107-
if (!isPathInside(filePath, DESIGN_RENDERER_DIR)) {
109+
if (!isPathInside(filePath, assetRoot)) {
108110
return new Response("Forbidden", { status: 403 });
109111
}
110112

@@ -120,7 +122,7 @@ function serveAppProtocolAsset(request: Request): Response {
120122
let realRoot: string;
121123
let realFile: string;
122124
try {
123-
realRoot = realpathSync(DESIGN_RENDERER_DIR);
125+
realRoot = realpathSync(assetRoot);
124126
realFile = realpathSync(filePath);
125127
if (!statSync(realFile).isFile() || !isPathInside(realFile, realRoot)) {
126128
return new Response("Forbidden", { status: 403 });
@@ -136,6 +138,16 @@ function serveAppProtocolAsset(request: Request): Response {
136138
});
137139
}
138140

141+
function resolveAppProtocolRoot(pathname: string): string | null {
142+
if (pathname.startsWith("/design-system/")) {
143+
return DESIGN_RENDERER_DIR;
144+
}
145+
if (pathname.startsWith("/assets/")) {
146+
return ASSETS_RENDERER_DIR;
147+
}
148+
return null;
149+
}
150+
139151
export class DesktopWindow {
140152
private browserWindow: BrowserWindow | null = null;
141153
private disposing = false;

apps/desktop/test/agent-dashboard-boundary.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,10 @@ test("design-system BrowserWindow protocol and navigation guards are fail-closed
255255
assert.match(windowSource, /url\.hostname !== "renderer"/);
256256
assert.match(windowSource, /decodeURIComponent\(url\.pathname\)/);
257257
assert.match(windowSource, /startsWith\("\/design-system\/"\)/);
258+
assert.match(windowSource, /startsWith\("\/assets\/"\)/);
258259
assert.match(windowSource, /pathParts\.includes\("\.\."\)/);
259260
assert.match(windowSource, /APP_PROTOCOL_EXTENSIONS\.has\(ext\)/);
260-
assert.match(windowSource, /realpathSync\(DESIGN_RENDERER_DIR\)/);
261+
assert.match(windowSource, /realpathSync\(assetRoot\)/);
261262
assert.match(windowSource, /statSync\(realFile\)\.isFile\(\)/);
262263
assert.match(windowSource, /isPathInside\(realFile, realRoot\)/);
263264
assert.match(windowSource, /new URL\(url\)\.href === this\.allowedRendererUrl/);

0 commit comments

Comments
 (0)