From d4d9f92adeaf3ae6d312b3f1a98db13f9e6d2bf5 Mon Sep 17 00:00:00 2001
From: strawberry <strawberry@puppygock.gay>
Date: Tue, 7 May 2024 12:53:30 -0400
Subject: [PATCH] add security response HTTP headers if not present

Signed-off-by: strawberry <strawberry@puppygock.gay>
---
 Cargo.toml                     |  1 +
 src/api/client_server/media.rs |  2 +-
 src/router/mod.rs              | 35 +++++++++++++++++++++++++++++++++-
 3 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 54cd8268..ea2d1770 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -109,6 +109,7 @@ features = [
     "add-extension",
     "cors",
     "sensitive-headers",
+    "set-header",
     "trace",
     "util",
     "catch-panic",
diff --git a/src/api/client_server/media.rs b/src/api/client_server/media.rs
index 0de61233..12899b39 100644
--- a/src/api/client_server/media.rs
+++ b/src/api/client_server/media.rs
@@ -25,7 +25,7 @@ use crate::{
 const MXC_LENGTH: usize = 32;
 
 /// Cache control for immutable objects
-const CACHE_CONTROL_IMMUTABLE: &str = "public, max-age=31536000, immutable";
+const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
 
 const CORP_CROSS_ORIGIN: &str = "cross-origin";
 
diff --git a/src/router/mod.rs b/src/router/mod.rs
index 03e8be60..aa928685 100644
--- a/src/router/mod.rs
+++ b/src/router/mod.rs
@@ -6,7 +6,7 @@ use axum::{
 	Router,
 };
 use http::{
-	header::{self, HeaderName},
+	header::{self, HeaderName, HeaderValue},
 	Method, StatusCode, Uri,
 };
 use ruma::api::client::{
@@ -17,6 +17,7 @@ use tower::ServiceBuilder;
 use tower_http::{
 	catch_panic::CatchPanicLayer,
 	cors::{self, CorsLayer},
+	set_header::SetResponseHeaderLayer,
 	trace::{DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, TraceLayer},
 	ServiceBuilderExt as _,
 };
@@ -32,6 +33,9 @@ pub(crate) async fn build(server: &Server) -> io::Result<axum::routing::IntoMake
 	let base_middlewares = base_middlewares.layer(sentry_tower::NewSentryLayer::<http::Request<_>>::new_from_top());
 
 	let x_forwarded_for = HeaderName::from_static("x-forwarded-for");
+	let permissions_policy = HeaderName::from_static("permissions-policy");
+	let origin_agent_cluster = HeaderName::from_static("origin-agent-cluster"); // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin-Agent-Cluster
+
 	let middlewares = base_middlewares
 		.sensitive_headers([header::AUTHORIZATION])
 		.sensitive_request_headers([x_forwarded_for].into())
@@ -44,6 +48,33 @@ pub(crate) async fn build(server: &Server) -> io::Result<axum::routing::IntoMake
 				.on_response(DefaultOnResponse::new().level(Level::DEBUG)),
 		)
 		.layer(axum::middleware::from_fn(request_handle))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			origin_agent_cluster,
+			HeaderValue::from_static("?1"),
+		))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			header::X_CONTENT_TYPE_OPTIONS,
+			HeaderValue::from_static("nosniff"),
+		))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			header::X_XSS_PROTECTION,
+			HeaderValue::from_static("0"),
+		))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			header::X_FRAME_OPTIONS,
+			HeaderValue::from_static("DENY"),
+		))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			permissions_policy,
+			HeaderValue::from_static("interest-cohort=(),browsing-topics=()"),
+		))
+		.layer(SetResponseHeaderLayer::if_not_present(
+			header::CONTENT_SECURITY_POLICY,
+			HeaderValue::from_static(
+				"sandbox; default-src 'none'; font-src 'none'; script-src 'none'; plugin-types application/pdf; \
+				 style-src 'unsafe-inline'; object-src 'self'; frame-ancesors 'none';",
+			),
+		))
 		.layer(cors_layer(server))
 		.layer(DefaultBodyLimit::max(
 			server
@@ -130,6 +161,8 @@ fn request_result_log(method: &Method, uri: &Uri, result: &axum::response::Respo
 	}
 }
 
+/// Cross-Origin-Resource-Sharing header as defined by spec:
+/// <https://spec.matrix.org/latest/client-server-api/#web-browser-clients>
 fn cors_layer(_server: &Server) -> CorsLayer {
 	const METHODS: [Method; 7] = [
 		Method::GET,