diff --git a/extensions/browser/src/browser/paths.test.ts b/extensions/browser/src/browser/paths.test.ts index bbf651e657f..9489d681c85 100644 --- a/extensions/browser/src/browser/paths.test.ts +++ b/extensions/browser/src/browser/paths.test.ts @@ -345,6 +345,24 @@ describe("resolveExistingUploadPaths", () => { }); }); + it("rejects traversal-shaped inbound media URI references before URL normalization", async () => { + await withFixtureRoot(async ({ inboundMediaDir, uploadsDir }) => { + const inboundFile = path.join(inboundMediaDir, "report.pdf"); + await fs.writeFile(inboundFile, "pdf", "utf8"); + + const result = await resolveExistingUploadPaths({ + uploadDir: uploadsDir, + inboundMediaDir, + requestedPaths: ["media://inbound/nested/../report.pdf"], + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("Invalid media reference"); + } + }); + }); + it("rejects nested absolute inbound media paths", async () => { await withFixtureRoot(async ({ inboundMediaDir, uploadsDir }) => { const nestedDir = path.join(inboundMediaDir, "nested"); diff --git a/extensions/browser/src/browser/paths.ts b/extensions/browser/src/browser/paths.ts index 3d301fc4ded..994e99cc84a 100644 --- a/extensions/browser/src/browser/paths.ts +++ b/extensions/browser/src/browser/paths.ts @@ -94,6 +94,8 @@ function resolveManagedInboundMediaRef( } if (/^media:\/\//i.test(normalizedSource)) { + const rawUriMatch = /^media:\/\/[^/?#]*([^?#]*)/iu.exec(normalizedSource); + const rawPath = rawUriMatch?.[1] ?? ""; let parsed: URL; try { parsed = new URL(normalizedSource); @@ -106,7 +108,10 @@ function resolveManagedInboundMediaRef( error: `Unsupported media reference location: ${parsed.hostname || "(missing)"}`, }; } - const decoded = decodeInboundMediaId(parsed.pathname.replace(/^\/+/, ""), normalizedSource); + if (!rawPath.startsWith("/") || rawPath.slice(1).includes("/") || rawPath.includes("\\")) { + return { ok: false, error: `Invalid media reference: ${normalizedSource}` }; + } + const decoded = decodeInboundMediaId(rawPath.slice(1), normalizedSource); return decoded?.ok ? { ok: true,