From 4a65c7e6ba9b3c7e448c01e8a9b4f38f6d650b49 Mon Sep 17 00:00:00 2001 From: Jakub Domeracki Date: Mon, 22 Jun 2026 19:29:46 +0000 Subject: [PATCH 1/3] fix(internal/api): harden file responses --- internal/api/src/routes/files.server.ts | 4 +++- internal/api/src/routes/files.test.ts | 26 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/api/src/routes/files.server.ts b/internal/api/src/routes/files.server.ts index edb6a8b8..640144f0 100644 --- a/internal/api/src/routes/files.server.ts +++ b/internal/api/src/routes/files.server.ts @@ -49,8 +49,10 @@ export default function mountFiles(server: APIServer) { // Note: this happens with createServer from node:http, not with Bun.serve. return c.body(file.stream, 200, { "Content-Type": file.type, - // Inline to prevent the browser from downloading the file. "Content-Disposition": `inline; filename="${file.name}"`, + "X-Content-Type-Options": "nosniff", + "Content-Security-Policy": + "default-src 'none'; sandbox; frame-ancestors 'none'", }); }); } diff --git a/internal/api/src/routes/files.test.ts b/internal/api/src/routes/files.test.ts index 11eff827..5280524d 100644 --- a/internal/api/src/routes/files.test.ts +++ b/internal/api/src/routes/files.test.ts @@ -3,7 +3,7 @@ import { serve } from "../test"; test("POST+GET /api/files", async () => { const { helpers } = await serve(); - const { client, user } = await helpers.createUser(); + const { client } = await helpers.createUser(); const file = new File(["Hello, world!"], "test.txt"); const resp = await client.files.upload(file); expect(resp.id).toBeString(); @@ -12,3 +12,27 @@ test("POST+GET /api/files", async () => { const fileResp = await client.files.get(resp.id); expect(await fileResp.text()).toBe("Hello, world!"); }); + +test("GET /api/files serves uploaded files inline with restrictive headers", async () => { + const { helpers } = await serve(); + const { client } = await helpers.createUser(); + const file = new File(["

content

"], "content.html", { + type: "text/html", + }); + + const uploaded = await client.files.upload(file); + const response = await fetch(uploaded.url); + + expect(response.status).toBe(200); + expect(await response.text()).toBe("

content

"); + expect(response.headers.get("content-type")).toStartWith("text/html"); + expect(response.headers.get("content-disposition")).toBe( + 'inline; filename="content.html"' + ); + expect(response.headers.get("x-content-type-options")).toBe("nosniff"); + const csp = response.headers.get("content-security-policy"); + expect(csp).toContain("default-src 'none'"); + expect(csp).toContain("sandbox"); + expect(csp).toContain("frame-ancestors 'none'"); + expect(response.headers.get("x-frame-options")).toBeNull(); +}); From 14bff7d967c0a7f35c7e4e74e21ec1e7e0f087fd Mon Sep 17 00:00:00 2001 From: Jakub Domeracki Date: Mon, 22 Jun 2026 20:36:05 +0000 Subject: [PATCH 2/3] fix(internal/database): ignore closed test sockets --- internal/database/src/postgres-worker.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/database/src/postgres-worker.ts b/internal/database/src/postgres-worker.ts index 5542fdbe..ecf1f8e5 100644 --- a/internal/database/src/postgres-worker.ts +++ b/internal/database/src/postgres-worker.ts @@ -506,6 +506,7 @@ self.onmessage = async (e) => { }, async onMessage(data, { isAuthenticated }) { if (!isAuthenticated) return; + if (data[0] === FrontendMessageCode.Terminate) return; maybeClaimOnEnqueue(socket, data); return new Promise((resolve, reject) => { taskQueue.push({ socket, data, resolve, reject }); @@ -519,9 +520,12 @@ self.onmessage = async (e) => { const cleanupSocket = () => { releaseOwnerIf(socket); for (let i = taskQueue.length - 1; i >= 0; i--) { - if (taskQueue[i]!.socket === socket) { + const task = taskQueue[i]; + if (task?.socket === socket) { try { - taskQueue[i]!.reject(new Error("Socket closed")); + // Closed sockets cannot receive pending responses, so complete + // queued work without producing an error. + task.resolve(new Uint8Array(0)); } catch {} taskQueue.splice(i, 1); } From af37fb60b7cf19a3afa75ab96ce695a44841425a Mon Sep 17 00:00:00 2001 From: Jakub Domeracki Date: Mon, 22 Jun 2026 20:51:26 +0000 Subject: [PATCH 3/3] test(internal/site): preload lexical in input tests --- .../site/components/chat-message-input.test.tsx | 12 +++++++----- .../site/components/chat-multimodal-input.test.tsx | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/internal/site/components/chat-message-input.test.tsx b/internal/site/components/chat-message-input.test.tsx index 8d48d97f..87c75446 100644 --- a/internal/site/components/chat-message-input.test.tsx +++ b/internal/site/components/chat-message-input.test.tsx @@ -1,5 +1,7 @@ -import { render, waitFor } from "@testing-library/react"; +// Load the core package first so Bun initializes Lexical's shared exports before @lexical/react. +import "lexical"; import { afterAll, beforeAll, expect, test } from "bun:test"; +import { render, waitFor } from "@testing-library/react"; import { Window } from "happy-dom"; import { ChatMessageInput, @@ -14,13 +16,13 @@ beforeAll(() => { }); afterAll(async () => { - // @ts-ignore + // @ts-expect-error delete globalThis.window; - // @ts-ignore + // @ts-expect-error delete globalThis.document; - // @ts-ignore + // @ts-expect-error delete globalThis.MutationObserver; - // @ts-ignore + // @ts-expect-error delete globalThis.getComputedStyle; }); diff --git a/internal/site/components/chat-multimodal-input.test.tsx b/internal/site/components/chat-multimodal-input.test.tsx index eca8381f..a112562a 100644 --- a/internal/site/components/chat-multimodal-input.test.tsx +++ b/internal/site/components/chat-multimodal-input.test.tsx @@ -1,7 +1,9 @@ -import type { UIAttachment } from "@/hooks/use-attachments"; -import { fireEvent, render, waitFor } from "@testing-library/react"; +// Load the core package first so Bun initializes Lexical's shared exports before @lexical/react. +import "lexical"; import { afterAll, beforeAll, expect, mock, test } from "bun:test"; +import { fireEvent, render, waitFor } from "@testing-library/react"; import { Window } from "happy-dom"; +import type { UIAttachment } from "@/hooks/use-attachments"; import type { ChatMessageInputRef } from "./chat-message-input"; import { ChatMultimodalInput, @@ -17,13 +19,13 @@ beforeAll(() => { }); afterAll(async () => { - // @ts-ignore + // @ts-expect-error delete globalThis.window; - // @ts-ignore + // @ts-expect-error delete globalThis.document; - // @ts-ignore + // @ts-expect-error delete globalThis.MutationObserver; - // @ts-ignore + // @ts-expect-error delete globalThis.getComputedStyle; });