diff --git a/src/media/web-media.test.ts b/src/media/web-media.test.ts index 069db3d76fd..94376d2a4f5 100644 --- a/src/media/web-media.test.ts +++ b/src/media/web-media.test.ts @@ -198,6 +198,19 @@ describe("loadWebMedia", () => { expect(result.contentType).toBe("text/csv"); }); + it("allows host-read Markdown files", async () => { + const mdFile = path.join(fixtureRoot, "notes.md"); + await fs.writeFile(mdFile, "# Title\n\nSome **bold** text.\n", "utf8"); + const result = await loadWebMedia(mdFile, { + maxBytes: 1024 * 1024, + localRoots: "any", + readFile: async (filePath) => await fs.readFile(filePath), + hostReadCapability: true, + }); + expect(result.kind).toBe("document"); + expect(result.contentType).toBe("text/markdown"); + }); + it("rejects binary data disguised as a CSV file", async () => { const fakeCsv = path.join(fixtureRoot, "evil.csv"); // Write a PNG header — binary, not text diff --git a/src/media/web-media.ts b/src/media/web-media.ts index ec987bbe3fb..c7a9c203efc 100644 --- a/src/media/web-media.ts +++ b/src/media/web-media.ts @@ -84,6 +84,7 @@ const HOST_READ_ALLOWED_DOCUMENT_MIMES = new Set([ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "text/csv", + "text/markdown", ]); const MB = 1024 * 1024; @@ -134,11 +135,13 @@ function assertHostReadMediaAllowed(params: { return; } const normalizedMime = normalizeMimeType(params.contentType); - // CSV exception: content sniffers report text/plain for CSV because CSV is structurally - // indistinguishable from plain text at the byte level. Allow it when: - // - The extension-derived MIME is text/csv (operator intent) + // CSV and Markdown exception: content sniffers report text/plain for both formats + // because they are structurally indistinguishable from plain text at the byte level. + // Allow them when: + // - The extension-derived MIME is text/csv or text/markdown (operator intent) // - The content sniffed as text/plain (confirming valid text, not binary data) - if (sniffedMime === "text/plain" && normalizedMime === "text/csv") { + const TEXT_PLAIN_ALIASES = new Set(["text/csv", "text/markdown"]); + if (sniffedMime === "text/plain" && normalizedMime && TEXT_PLAIN_ALIASES.has(normalizedMime)) { return; } if (