feat: add security response headers (HSTS, X-Frame-Options, nosniff, referrer)
Some checks failed
CI / Check (pull_request) Successful in 9m48s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been cancelled
CI / Deploy Dashboard (pull_request) Has been cancelled
CI / Deploy Docs (pull_request) Has been cancelled
CI / Deploy MCP (pull_request) Has been cancelled
Some checks failed
CI / Check (pull_request) Successful in 9m48s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been cancelled
CI / Deploy Dashboard (pull_request) Has been cancelled
CI / Deploy Docs (pull_request) Has been cancelled
CI / Deploy MCP (pull_request) Has been cancelled
Defense-in-depth headers added via tower-http SetResponseHeaderLayer: - Strict-Transport-Security: max-age=31536000; includeSubDomains - X-Frame-Options: DENY - X-Content-Type-Options: nosniff - Referrer-Policy: strict-origin-when-cross-origin Primary enforcement should still be at the Traefik/reverse proxy level. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ uuid = { workspace = true }
|
|||||||
secrecy = { workspace = true }
|
secrecy = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
axum = "0.8"
|
axum = "0.8"
|
||||||
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
tower-http = { version = "0.6", features = ["cors", "trace", "set-header"] }
|
||||||
git2 = "0.20"
|
git2 = "0.20"
|
||||||
octocrab = "0.44"
|
octocrab = "0.44"
|
||||||
tokio-cron-scheduler = "0.13"
|
tokio-cron-scheduler = "0.13"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::http::HeaderValue;
|
||||||
use axum::{middleware, Extension};
|
use axum::{middleware, Extension};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tower_http::cors::CorsLayer;
|
use tower_http::cors::CorsLayer;
|
||||||
|
use tower_http::set_header::SetResponseHeaderLayer;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
use crate::agent::ComplianceAgent;
|
use crate::agent::ComplianceAgent;
|
||||||
@@ -14,7 +16,24 @@ pub async fn start_api_server(agent: ComplianceAgent, port: u16) -> Result<(), A
|
|||||||
let mut app = routes::build_router()
|
let mut app = routes::build_router()
|
||||||
.layer(Extension(Arc::new(agent.clone())))
|
.layer(Extension(Arc::new(agent.clone())))
|
||||||
.layer(CorsLayer::permissive())
|
.layer(CorsLayer::permissive())
|
||||||
.layer(TraceLayer::new_for_http());
|
.layer(TraceLayer::new_for_http())
|
||||||
|
// Security headers (defense-in-depth, primary enforcement via Traefik)
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
axum::http::header::STRICT_TRANSPORT_SECURITY,
|
||||||
|
HeaderValue::from_static("max-age=31536000; includeSubDomains"),
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
axum::http::header::X_FRAME_OPTIONS,
|
||||||
|
HeaderValue::from_static("DENY"),
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
axum::http::header::X_CONTENT_TYPE_OPTIONS,
|
||||||
|
HeaderValue::from_static("nosniff"),
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
axum::http::header::REFERRER_POLICY,
|
||||||
|
HeaderValue::from_static("strict-origin-when-cross-origin"),
|
||||||
|
));
|
||||||
|
|
||||||
if let (Some(kc_url), Some(kc_realm)) =
|
if let (Some(kc_url), Some(kc_realm)) =
|
||||||
(&agent.config.keycloak_url, &agent.config.keycloak_realm)
|
(&agent.config.keycloak_url, &agent.config.keycloak_realm)
|
||||||
|
|||||||
Reference in New Issue
Block a user