Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/svg-upload-allowlist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emdash": patch
---

Fixes the default media upload allowlist accepting `image/svg+xml` via its bare `"image/"` prefix match, with no upload-time content validation for SVG. A logged-in Contributor+ user could upload an SVG containing an embedded `<script>`; the global allowlist now enumerates safe raster types (`image/png`, `image/jpeg`, `image/gif`, `image/webp`) explicitly instead of prefix-matching all `image/*`. Fields that explicitly configure `image/svg+xml` in their own `allowedMimeTypes` are unaffected.
15 changes: 13 additions & 2 deletions packages/core/src/api/handlers/media-allowlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ import { parseAllowedMimeTypes } from "../../media/mime.js";
/**
* MIME types allowed for upload by default (when no field-specific list
* overrides this). Entries ending with "/" are prefix-matched (e.g.
* "image/" matches "image/jpeg", "image/png", etc.).
* "video/" matches "video/mp4", "video/webm", etc.).
*
* Image types are enumerated explicitly rather than using a bare "image/"
* prefix so that "image/svg+xml" is excluded by default: there is no
* upload-time content validation for SVG, so an unvalidated bare-prefix
* allowlist would let a Contributor+ upload an SVG with an embedded
* `<script>`. Fields that genuinely need SVG uploads can still opt in via
* their own `allowedMimeTypes` (see `resolveFieldAllowlist` below), which is
* unaffected by this default.
*/
export const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [
"image/",
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"video/",
"audio/",
"application/pdf",
Expand Down
23 changes: 23 additions & 0 deletions packages/core/tests/unit/media/media-allowlist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, it, expect } from "vitest";

import { GLOBAL_UPLOAD_ALLOWLIST } from "../../../src/api/handlers/media-allowlist.js";
import { matchesMimeAllowlist } from "../../../src/media/mime.js";

describe("GLOBAL_UPLOAD_ALLOWLIST", () => {
it("rejects image/svg+xml (no upload-time content validation exists for SVG scripts)", () => {
expect(matchesMimeAllowlist("image/svg+xml", GLOBAL_UPLOAD_ALLOWLIST)).toBe(false);
});

it("still allows common raster image types", () => {
expect(matchesMimeAllowlist("image/png", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
expect(matchesMimeAllowlist("image/jpeg", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
expect(matchesMimeAllowlist("image/gif", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
expect(matchesMimeAllowlist("image/webp", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
});

it("still allows video, audio, and pdf", () => {
expect(matchesMimeAllowlist("video/mp4", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
expect(matchesMimeAllowlist("audio/mpeg", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
expect(matchesMimeAllowlist("application/pdf", GLOBAL_UPLOAD_ALLOWLIST)).toBe(true);
});
});
Loading