| Crates.io | actix-web-csp |
| lib.rs | actix-web-csp |
| version | 0.1.0 |
| created_at | 2025-08-14 16:51:48.102278+00 |
| updated_at | 2025-08-14 16:51:48.102278+00 |
| description | High-performance Content Security Policy middleware for Actix Web |
| homepage | https://github.com/hun756/actix_web_csp |
| repository | https://github.com/hun756/actix_web_csp |
| max_upload_size | |
| id | 1795098 |
| size | 338,246 |
A high-performance Content Security Policy (CSP) middleware for Actix Web applications. Built with security-first principles and optimized for production workloads.
Add to your Cargo.toml:
[dependencies]
actix_web_csp = "0.1.0"
actix-web = "4.3"
Basic usage:
use actix_web::{web, App, HttpServer, HttpResponse, Result};
use actix_web_csp::{CspPolicyBuilder, Source, csp_middleware};
async fn index() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html")
.body("<h1>Protected by CSP</h1>"))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([Source::Self_])
.style_src([Source::Self_])
.img_src([Source::Self_, Source::Scheme("https".into())])
.build_unchecked();
HttpServer::new(move || {
App::new()
.wrap(csp_middleware(policy.clone()))
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
For applications requiring maximum security:
let policy = CspPolicyBuilder::new()
.default_src([Source::None])
.script_src([Source::Self_])
.style_src([Source::Self_])
.img_src([Source::Self_])
.connect_src([Source::Self_])
.font_src([Source::Self_])
.object_src([Source::None])
.media_src([Source::None])
.frame_src([Source::None])
.base_uri([Source::Self_])
.form_action([Source::Self_])
.build_unchecked();
For development environments:
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([
Source::Self_,
Source::Host("localhost:3000".into()),
Source::Host("cdn.jsdelivr.net".into())
])
.style_src([
Source::Self_,
Source::UnsafeInline, // Only for development!
Source::Host("fonts.googleapis.com".into())
])
.img_src([
Source::Self_,
Source::Scheme("data".into()),
Source::Scheme("https".into())
])
.connect_src([
Source::Self_,
Source::Scheme("https".into()),
Source::Scheme("ws".into()) // WebSocket support
])
.font_src([
Source::Self_,
Source::Scheme("data".into()),
Source::Host("fonts.gstatic.com".into())
])
.report_uri("/csp-violations")
.build_unchecked();
Secure configuration for online stores:
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([
Source::Self_,
Source::Host("js.stripe.com".into()),
Source::Host("checkout.paypal.com".into())
])
.style_src([
Source::Self_,
Source::Host("fonts.googleapis.com".into())
])
.img_src([
Source::Self_,
Source::Scheme("https".into()),
Source::Scheme("data".into()) // For product images
])
.connect_src([
Source::Self_,
Source::Host("api.stripe.com".into()),
Source::Host("api.paypal.com".into()),
Source::Scheme("https".into())
])
.frame_src([
Source::Host("js.stripe.com".into()),
Source::Host("checkout.paypal.com".into())
])
.font_src([
Source::Self_,
Source::Scheme("data".into()),
Source::Host("fonts.gstatic.com".into())
])
.report_uri("/security/csp-report")
.build_unchecked();
For dynamic content with inline scripts:
use actix_web_csp::{csp_middleware_with_nonce, RequestNonce};
async fn secure_page(req: HttpRequest) -> Result<HttpResponse> {
let nonce = req.extensions()
.get::<RequestNonce>()
.map(|n| n.to_string())
.unwrap_or_default();
let html = format!(r#"
<!DOCTYPE html>
<html>
<head>
<script nonce="{}">
console.log('This script is allowed');
</script>
</head>
<body>
<h1>Secure Page</h1>
</body>
</html>
"#, nonce);
Ok(HttpResponse::Ok()
.content_type("text/html")
.body(html))
}
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([Source::Self_]) // Nonce will be added automatically
.build_unchecked();
let app = App::new()
.wrap(csp_middleware_with_nonce(policy, 32)) // 32-byte nonce
.route("/secure", web::get().to(secure_page));
Handle CSP violations in real-time:
use actix_web_csp::{csp_with_reporting, CspViolationReport};
fn handle_violation(report: CspViolationReport) {
println!("π¨ CSP Violation Detected:");
println!(" Document: {}", report.document_uri);
println!(" Violated: {}", report.violated_directive);
println!(" Blocked: {}", report.blocked_uri);
// Log to security monitoring system
// security_logger::log_csp_violation(&report);
}
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([Source::Self_])
.report_uri("/csp-report")
.build_unchecked();
let (middleware, configurator) = csp_with_reporting(policy, handle_violation);
let app = App::new()
.wrap(middleware)
.configure(configurator) // Adds /csp-report endpoint
.route("/", web::get().to(index));
Track CSP performance metrics:
use actix_web_csp::{CspStats, csp_middleware_with_stats};
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.build_unchecked();
let (middleware, stats) = csp_middleware_with_stats(policy);
// Monitor performance
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(60)).await;
println!("CSP Stats: {} requests processed", stats.total_requests());
println!("Average response time: {}ΞΌs", stats.avg_response_time_micros());
}
});
let app = App::new()
.wrap(middleware)
.route("/", web::get().to(index));
The library includes a comprehensive security testing tool:
use actix_web_csp::{CspSecurityTester, CspPolicyBuilder, Source};
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([Source::Self_])
.build_unchecked();
let mut tester = CspSecurityTester::new(policy);
let results = tester.run_comprehensive_test();
// Results show:
// β
XSS Protection - 4/4 XSS payloads blocked
// β
Inline Script Protection - Inline scripts blocked
// β
External Script Protection - 4/4 malicious domains blocked
// β
Overall Assessment: π’ Your CSP configuration looks secure!
Run the security tester:
cargo run --example csp_security_tester
The CspPolicyBuilder provides a fluent interface for policy construction:
let policy = CspPolicyBuilder::new()
// Content sources
.default_src([Source::Self_])
.script_src([Source::Self_, Source::Host("cdn.example.com".into())])
.style_src([Source::Self_, Source::UnsafeInline])
.img_src([Source::Self_, Source::Scheme("data".into())])
.connect_src([Source::Self_, Source::Scheme("https".into())])
.font_src([Source::Self_, Source::Host("fonts.gstatic.com".into())])
.object_src([Source::None])
.media_src([Source::Self_])
.frame_src([Source::None])
// Navigation sources
.base_uri([Source::Self_])
.form_action([Source::Self_])
// Reporting
.report_uri("/csp-violations")
.report_to("csp-endpoint")
// Build policy (validates configuration)
.build()
.expect("Invalid CSP policy");
use actix_web_csp::Source;
// Special keywords
Source::Self_ // 'self'
Source::None // 'none'
Source::UnsafeInline // 'unsafe-inline'
Source::UnsafeEval // 'unsafe-eval'
Source::StrictDynamic // 'strict-dynamic'
// Schemes
Source::Scheme("https".into()) // https:
Source::Scheme("data".into()) // data:
// Hosts
Source::Host("example.com".into()) // example.com
Source::Host("*.example.com".into()) // *.example.com
Source::Host("example.com:443".into()) // example.com:443
// Nonces (auto-generated)
Source::Nonce("random-value".into()) // 'nonce-random-value'
// Hashes (auto-calculated)
Source::Hash {
algorithm: HashAlgorithm::Sha256,
value: "base64-hash".into()
} // 'sha256-base64-hash'
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let policy = CspPolicyBuilder::new()
.default_src([Source::Self_])
.script_src([
Source::Self_,
Source::Host("cdnjs.cloudflare.com".into()),
Source::Host("cdn.jsdelivr.net".into())
])
.style_src([
Source::Self_,
Source::Host("fonts.googleapis.com".into()),
Source::Host("cdnjs.cloudflare.com".into())
])
.img_src([
Source::Self_,
Source::Scheme("https".into()),
Source::Scheme("data".into())
])
.connect_src([
Source::Self_,
Source::Host("api.example.com".into()),
Source::Scheme("https".into())
])
.font_src([
Source::Self_,
Source::Host("fonts.gstatic.com".into()),
Source::Scheme("data".into())
])
.frame_ancestors([Source::None])
.report_uri("/security/csp-violations")
.build_unchecked();
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.wrap(csp_middleware(policy.clone()))
.service(
web::scope("/api")
.route("/users", web::get().to(get_users))
.route("/products", web::get().to(get_products))
)
.service(Files::new("/", "./static").index_file("index.html"))
})
.bind("0.0.0.0:8080")?
.run()
.await
}
use actix_cors::Cors;
let policy = CspPolicyBuilder::new()
.default_src([Source::None])
.connect_src([
Source::Self_,
Source::Host("api.frontend.com".into())
])
.report_uri("/api/csp-violations")
.build_unchecked();
let app = App::new()
.wrap(
Cors::default()
.allowed_origin("https://frontend.com")
.allowed_methods(vec!["GET", "POST"])
.max_age(3600)
)
.wrap(csp_middleware(policy))
.route("/api/data", web::get().to(api_handler));
Benchmark results on a modern system:
Run benchmarks:
cargo bench
Licensed under the MIT License. See LICENSE for details.
Contributions are welcome! Please read our Contributing Guide for details.
Note: This middleware is production-ready and actively maintained. For security issues, please email ekemenms@gmail.com.