From 8da0a3049794e7f866a60313817127ba9b19e65c Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Sat, 5 Sep 2026 09:04:29 +0200 Subject: [PATCH] feat: send security headers on every response Nothing set a CSP, a referrer policy, nosniff or a framing rule, so the app relied entirely on the same-origin policy. The referrer policy is the one with a concrete leak behind it: a share token is part of the URL path, so following any outbound link from a shared entry handed a third party a working link to a private entry. `no-referrer` closes that. The layer is `if_not_present`, so the media routes keep the stricter sandbox CSP they set for themselves. --- Cargo.toml | 2 +- src/main.rs | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 03db474..8132b03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ axum = { version = "0.8", features = ["multipart", "macros"] } tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal", "fs"] } tower = "0.5" tokio-util = { version = "0.7", features = ["io"] } -tower-http = { version = "0.6", features = ["trace", "compression-gzip", "limit"] } +tower-http = { version = "0.6", features = ["trace", "compression-gzip", "limit", "set-header"] } sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite", "migrate", "macros"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/main.rs b/src/main.rs index 8ddf192..cd178e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,10 @@ use std::sync::Arc; use anyhow::{Context, Result}; use axum::Router; use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}; -use tower_http::{compression::CompressionLayer, trace::TraceLayer}; +use axum::http::{header, HeaderValue}; +use tower_http::{ + compression::CompressionLayer, set_header::SetResponseHeaderLayer, trace::TraceLayer, +}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use crate::{config::Config, state::AppState}; @@ -66,6 +69,7 @@ async fn main() -> Result<()> { .fallback(static_files::handler) .layer(axum::extract::DefaultBodyLimit::max(max_upload + 1024 * 1024)) .layer(CompressionLayer::new()) + .layer(security_headers()) .layer(TraceLayer::new_for_http()) .with_state(state); @@ -81,6 +85,33 @@ async fn main() -> Result<()> { Ok(()) } +/// Content-Security-Policy for the app shell. `unsafe-inline` is granted to +/// styles only, because CodeMirror injects its theme as a