mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2026-07-05 06:55:30 +00:00
feat: enable strict CSP with nonce-based script-src
Replaces the disabled CSP middleware with a working implementation: - Generates a per-request nonce for script-src - Injects nonce into the inline SvelteKit bootstrap script - Uses 'strict-dynamic' so dynamically imported modules are trusted - SPA fallback serves index.html with CSP header
This commit is contained in:
@@ -1,16 +1,35 @@
|
|||||||
use axum::{body::Body, extract::Request, http::HeaderValue, middleware::Next, response::Response};
|
use axum::{
|
||||||
|
http::HeaderValue,
|
||||||
|
response::{Html, IntoResponse, Response},
|
||||||
|
};
|
||||||
|
use ring::rand::SecureRandom;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
const CUSTOM_HEADER_NAME: &str = "Content-Security-Policy";
|
const CSP_POLICY: &str = "default-src 'self'; script-src 'nonce-{nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; connect-src 'self'";
|
||||||
const CUSTOM_HEADER_VALUE: &str = "default-src 'self'; script-src 'report-sample' 'self'; style-src 'report-sample' 'self'; object-src 'none'; base-uri 'self'; connect-src 'self' data:; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; worker-src 'none';";
|
|
||||||
|
|
||||||
lazy_static! {
|
fn index_html() -> &'static str {
|
||||||
static ref HEADER_VALUE: HeaderValue = HeaderValue::from_static(CUSTOM_HEADER_VALUE);
|
static HTML: OnceLock<String> = OnceLock::new();
|
||||||
|
HTML.get_or_init(|| {
|
||||||
|
let path = format!("{}index.html", *crate::config::FRONTEND_PATH);
|
||||||
|
std::fs::read_to_string(&path).expect("Failed to read index.html for CSP injection")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_csp_header(request: Request<Body>, next: Next) -> Response {
|
fn generate_nonce() -> String {
|
||||||
let mut response = next.run(request).await;
|
let rng = ring::rand::SystemRandom::new();
|
||||||
|
let mut bytes = [0u8; 32];
|
||||||
|
rng.fill(&mut bytes).expect("Failed to generate CSP nonce");
|
||||||
|
bs62::encode_data(&bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn spa_fallback() -> Response {
|
||||||
|
let nonce = generate_nonce();
|
||||||
|
let csp = CSP_POLICY.replace("{nonce}", &nonce);
|
||||||
|
let html = index_html().replace("<script>", &format!("<script nonce=\"{}\">", nonce));
|
||||||
|
|
||||||
|
let mut response = Html(html).into_response();
|
||||||
response
|
response
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
.append(CUSTOM_HEADER_NAME, HEADER_VALUE.clone());
|
.insert("Content-Security-Policy", HeaderValue::from_str(&csp).unwrap());
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ use tower::Layer;
|
|||||||
use tower_http::{
|
use tower_http::{
|
||||||
compression::CompressionLayer,
|
compression::CompressionLayer,
|
||||||
normalize_path::NormalizePathLayer,
|
normalize_path::NormalizePathLayer,
|
||||||
services::{ServeDir, ServeFile},
|
services::ServeDir,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -50,14 +50,12 @@ async fn main() {
|
|||||||
.merge(health_routes)
|
.merge(health_routes)
|
||||||
.merge(status_routes);
|
.merge(status_routes);
|
||||||
|
|
||||||
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
|
|
||||||
let serve_dir =
|
|
||||||
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api", api_routes)
|
.nest("/api", api_routes)
|
||||||
.fallback_service(serve_dir)
|
.fallback_service(
|
||||||
// Disabled for now, as svelte inlines scripts
|
ServeDir::new(config::FRONTEND_PATH.to_string())
|
||||||
// .layer(middleware::from_fn(csp::add_csp_header))
|
.not_found_service(axum::Router::new().fallback(csp::spa_fallback)),
|
||||||
|
)
|
||||||
.layer(DefaultBodyLimit::max(*config::LIMIT))
|
.layer(DefaultBodyLimit::max(*config::LIMIT))
|
||||||
.layer(
|
.layer(
|
||||||
CompressionLayer::new()
|
CompressionLayer::new()
|
||||||
|
|||||||
Reference in New Issue
Block a user