fix: force download for untyped (octet-stream) served blobs
The /files handler served the application/octet-stream fallback with no Content-Disposition, letting a browser render it inline — a stored-XSS vector if the blob is crafted HTML/JS. Add Content-Disposition: attachment for that type (belt to the existing nosniff); known image types stay inline. Test (new tests/api_files.rs): octet-stream → attachment; png → inline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -141,7 +141,7 @@ fn image_response(
|
||||
content_length: String,
|
||||
body: Body,
|
||||
) -> Response {
|
||||
let headers = [
|
||||
let mut headers = vec![
|
||||
(header::CONTENT_TYPE, content_type.to_string()),
|
||||
(header::CONTENT_LENGTH, content_length),
|
||||
// `nosniff` makes the contract explicit: the browser must trust the
|
||||
@@ -169,7 +169,17 @@ fn image_response(
|
||||
},
|
||||
),
|
||||
];
|
||||
(headers, body).into_response()
|
||||
// Known image types render inline (covers/pages display in the reader). The
|
||||
// `application/octet-stream` fallback is a blob we couldn't type — it could
|
||||
// be crafted HTML/JS, so force a download rather than let the browser render
|
||||
// it inline. Belt to `nosniff`'s braces.
|
||||
if content_type == "application/octet-stream" {
|
||||
headers.push((
|
||||
header::CONTENT_DISPOSITION,
|
||||
"attachment".to_string(),
|
||||
));
|
||||
}
|
||||
(axum::response::AppendHeaders(headers), body).into_response()
|
||||
}
|
||||
|
||||
/// Parse and clamp a requested thumbnail width. Returns `None` for absent /
|
||||
|
||||
Reference in New Issue
Block a user