From b040635e9da7a6216437193208066d35c5935cea Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Mon, 18 May 2026 23:15:05 -0700 Subject: [PATCH] Set X-Content-Type-Options: nosniff on all server responses --- Cargo.lock | 5 +++-- Cargo.toml | 1 + src/subcommand/serve.rs | 39 +++++++++++++++++++++++++++------------ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37ba042c..de7beb4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1006,6 +1006,7 @@ dependencies = [ "tokio", "tokio-util", "tower", + "tower-http", "url", "usized", "walkdir", @@ -3129,9 +3130,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", diff --git a/Cargo.toml b/Cargo.toml index efc8b731..7991e722 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ sysinfo = { version = "0.39.1", default-features = false, features = ["system"] tempfile = "3.24.0" tokio = { version = "1.52.3", features = ["fs", "io-util", "macros", "rt-multi-thread"] } tokio-util = { version = "0.7.18", features = ["io"] } +tower-http = { version = "0.6.11", features = ["set-header"] } url = "2.5.8" usized = "0.0.2" walkdir = "2.5.0" diff --git a/src/subcommand/serve.rs b/src/subcommand/serve.rs index 2116bdba..2d338076 100644 --- a/src/subcommand/serve.rs +++ b/src/subcommand/serve.rs @@ -3,7 +3,7 @@ use { axum::{ Router, extract::{Extension, Path}, - http::{Uri, header}, + http::{HeaderValue, Uri, header}, response::Redirect, routing::{get, put}, }, @@ -15,6 +15,7 @@ use { sysinfo::System, tokio::{net::TcpListener, runtime}, tokio_util::io::ReaderStream, + tower_http::set_header::SetResponseHeaderLayer, }; static THREAD_COUNTER: AtomicU64 = AtomicU64::new(0); @@ -210,6 +211,16 @@ impl Serve { Redirect::to(&destination) } + fn redirect_router(destination: String) -> Router { + Router::new() + .fallback(Self::redirect_http_to_https) + .layer(Extension(destination)) + .layer(SetResponseHeaderLayer::overriding( + header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + )) + } + pub(crate) fn router(server: Arc, auth_config: Option>) -> Router { let router = Router::new() .route("/", get(Self::home)) @@ -219,7 +230,11 @@ impl Serve { .route("/install.sh", get(Self::install_script)) .route("/static/{*path}", get(Self::static_asset)) .fallback(Self::fallback) - .layer(Extension(server)); + .layer(Extension(server)) + .layer(SetResponseHeaderLayer::overriding( + header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + )); if let Some(auth_config) = auth_config { router.layer(Extension(auth_config)) @@ -392,12 +407,7 @@ impl Serve { axum_server::from_tcp(listener) .context(error::Serve)? .handle(handle) - .serve( - Router::new() - .fallback(Self::redirect_http_to_https) - .layer(Extension(destination)) - .into_make_service(), - ) + .serve(Self::redirect_router(destination).into_make_service()) .await .context(error::Serve)?; } @@ -488,7 +498,10 @@ mod tests { method, path: path.into(), response_body: Vec::new(), - response_headers: BTreeMap::new(), + response_headers: BTreeMap::from([( + header::X_CONTENT_TYPE_OPTIONS.to_string(), + "nosniff".into(), + )]), router, status: StatusCode::OK, token: None, @@ -773,15 +786,17 @@ mod tests { #[tokio::test] async fn redirect_http_to_https() { async fn case(path: &str, location: &str) { - let response = Router::new() - .fallback(Serve::redirect_http_to_https) - .layer(Extension("https://foo".to_string())) + let response = Serve::redirect_router("https://foo".into()) .oneshot(Request::builder().uri(path).body(Body::empty()).unwrap()) .await .unwrap(); assert_eq!(response.status(), StatusCode::SEE_OTHER); assert_eq!(response.headers()[header::LOCATION], location); + assert_eq!( + response.headers()[header::X_CONTENT_TYPE_OPTIONS], + "nosniff" + ); } case("/", "https://foo/").await;