import { expect, it } from "bun:test"; import express, { type Request } from "express"; import { createHash } from "node:crypto"; import { mkdtemp, mkdir, writeFile, rm, symlink } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { config } from "../../src/config.js"; import { attachTrustedAgentContext, getLocalAgentContext, ProductContextError } from "../../src/context/localContext.js"; import { registerArtifactRoutes } from "../../src/routes/artifact-routes.js"; import { SessionMetadataStore } from "../../src/sessions/metadataStore.js"; it("requires verified actor, project and session ownership for exports and rejects symlinks", async () => { const originalFetch = globalThis.fetch; const originalEnv = config.NODE_ENV; const originalRoot = config.CONVERSATION_WORKSPACE_ROOT; const root = await mkdtemp(join(tmpdir(), "artifact-route-")); const store = new SessionMetadataStore(join(root, "metadata")); const app = express(); let calls = 0; globalThis.fetch = (async (input: Parameters[0], init: Parameters[1]) => { if (String(input).endsWith("/api/v1/agent-auth-context")) { const headers = new Headers(init?.headers); return Response.json({ user_id: headers.get("Authorization") === "Bearer OWNER" ? "owner" : "other", project_id: headers.get("X-Project-Id"), network: "supply" }); } return originalFetch(input, init); }) as typeof fetch; config.NODE_ENV = "production"; config.CONVERSATION_WORKSPACE_ROOT = root; const owner = { header: (name: string) => ({ authorization: "Bearer OWNER", "x-project-id": "project" })[name.toLowerCase() as "authorization" | "x-project-id"] } as Request; await attachTrustedAgentContext(owner); const context = getLocalAgentContext(owner); const session = "export-session"; await store.ensure({ ...context, sessionId: session }); const directory = join(root, context.actorKey, context.projectKey, `session-${createHash("sha256").update(session).digest("hex")}`, "workspace"); await mkdir(join(directory, "files"), { recursive: true }); const filename = "mcp-11111111-1111-1111-1111-111111111111.zip"; await writeFile(join(directory, "files", filename), "fixture"); const router = express.Router(); router.use(async (req, res, next) => { try { await attachTrustedAgentContext(req); next(); } catch (error) { res.status(error instanceof ProductContextError ? error.status : 503).end(); } }); registerArtifactRoutes(router, { sessionMetadataStore: store, runtime: { ensureSessionWorkspace: async () => { calls++; return directory; }, } }); app.use("/api/v1", router); const server = app.listen(0, "127.0.0.1"); await new Promise((resolve) => server.once("listening", resolve)); const url = `http://127.0.0.1:${(server.address() as { port: number }).port}/api/v1/session/${session}/files/${filename}`; const headers = { Authorization: "Bearer OWNER", "X-Project-Id": "project" }; try { expect((await originalFetch(url)).status).toBe(401); expect((await originalFetch(url, { headers: { ...headers, Authorization: "Bearer OTHER" } })).status).toBe(404); expect((await originalFetch(url, { headers: { ...headers, "X-Project-Id": "other" } })).status).toBe(404); expect(calls).toBe(0); const response = await originalFetch(url, { headers }); expect(response.status).toBe(200); expect(response.headers.get("Cache-Control")).toBe("private, no-store"); expect(response.headers.get("Content-Disposition")).toContain("attachment"); expect(await response.text()).toBe("fixture"); await rm(join(directory, "files", filename)); await symlink(join(root, "metadata"), join(directory, "files", filename)); expect((await originalFetch(url, { headers })).status).toBe(404); } finally { globalThis.fetch = originalFetch; config.NODE_ENV = originalEnv; config.CONVERSATION_WORKSPACE_ROOT = originalRoot; await new Promise((resolve) => server.close(() => resolve())); await rm(root, { recursive: true, force: true }); } });