From: Kovid Goyal <kovid@kovidgoyal.net>
Date: Tue, 24 Feb 2026 09:06:11 +0530
Subject: CVE-2026-27810: Content server: Sanitize content disposition
 received as query parameter

Forwarded: not-needed
Bug: https://github.com/kovidgoyal/calibre/security/advisories/GHSA-5fpj-fxw7-8grw
Origin: https://github.com/kovidgoyal/calibre/commit/a468ce0f268032eea1f7431853248148ffa2e06a

Signed-off-by: YOKOTA Hiroshi <yokota.hgml@gmail.com>
---
 src/calibre/srv/content.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py
index af28510..764c48e 100644
--- a/src/calibre/srv/content.py
+++ b/src/calibre/srv/content.py
@@ -6,6 +6,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
 
 import errno
 import os
+import re
 from contextlib import suppress
 from functools import partial
 from io import BytesIO
@@ -211,7 +212,7 @@ def book_fmt(ctx, rd, library_id, db, book_id, fmt):
             set_metadata(dest, mi, fmt)
             dest.seek(0)
 
-    cd = rd.query.get('content_disposition', 'attachment')
+    cd = sanitize_content_disposition(rd.query.get('content_disposition', 'attachment'))
     rd.outheaders['Content-Disposition'] = '''{}; filename="{}"; filename*=utf-8''{}'''.format(
         cd, book_filename(rd, book_id, mi, fmt), book_filename(rd, book_id, mi, fmt, as_encoded_unicode=True))
 
@@ -347,3 +348,7 @@ def get(ctx, rd, what, book_id, library_id):
                 return book_fmt(ctx, rd, library_id, db, book_id, what.lower())
             except NoSuchFormat:
                 raise HTTPNotFound(f'No {what.lower()} format for the book {book_id!r}')
+
+
+def sanitize_content_disposition(x: str) -> str:
+    return re.sub(r'[^a-zA-Z0-9./-]', '-', x)
