You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Serve file content with X-Content-Type-Options: nosniff #1605
GET /api/v1/files/{file_id}/content answers with the media type the uploader declared, stored on the row as mime_type. The response carries no X-Content-Type-Options: nosniff, so a browser is free to sniff the body and treat it as a type the response did not claim.
What already stands between that and a problem is Content-Disposition: attachment, which the route builds with CRLF and quote stripping plus an RFC 5987 encoded filename, so the browser downloads rather than renders. The header below is defense in depth on top of that, not a fix for a live rendering path.
Proposal
Add X-Content-Type-Options: nosniff to the response headers in get_file_content (api/routes/files.py), beside the existing Content-Disposition. Pin it with an assertion in the existing download test in tests/integration/test_files_endpoint.py.
Worth deciding at the same time whether the header belongs on every response rather than this one route, which would be a deployment-wide change and a separate issue.
Scope
Unchanged from before otari#1483, which moved the route's body into a service and left the headers alone. It affects standalone self-hosted deployments, which are the ones that serve the Files API; hybrid and hosted deployments do not mount it.
Already in place. SecurityHeadersMiddleware in src/gateway/main.py sets X-Content-Type-Options: nosniff on every response, including file content, and tests/integration/test_cache_control_headers.py asserts it.
Activity
peteski22 commentedon Oct 5, 2026
Already in place.
SecurityHeadersMiddlewareinsrc/gateway/main.pysetsX-Content-Type-Options: nosniffon every response, including file content, andtests/integration/test_cache_control_headers.pyasserts it.