fix(media): also allow host-local Markdown uploads

Extend the text/plain sniff exception to cover text/markdown in addition
to text/csv. Both formats are structurally indistinguishable from plain
text at the byte level, so the same pattern applies.
This commit is contained in:
Chen Chia Yang
2026-04-15 15:23:55 +08:00
committed by Frank Yang
parent 5735772de6
commit cbc040c3ab
2 changed files with 20 additions and 4 deletions

View File

@@ -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

View File

@@ -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 (