logo

Database

Insecure or unset HTTP headers - Content-Security-Policy

Need

Enforcement of a restrictive Content-Security-Policy without unsafe directives

Context

• Usage of Rust 1.75+ for building application services

• Usage of the actix-web crate for handling HTTP requests

Description

1. Non compliant code

use actix_web::middleware::DefaultHeaders;
use actix_web::{get, App, HttpResponse, HttpServer};

fn security_headers() -> DefaultHeaders {
    // 'unsafe-inline' and 'unsafe-eval' let injected scripts run
    DefaultHeaders::new().add((
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'",...

The `security_headers` function below configures the actix-web `DefaultHeaders` middleware to send a Content-Security-Policy on every response. The policy allows scripts from `'self'`, but it also includes `'unsafe-inline'` and `'unsafe-eval'`. A Content-Security-Policy is the browser-side defense that stops injected scripts from running when an output encoding mistake lets them into the page. `'unsafe-inline'` allows every `<script>` block and every event handler attribute such as `onerror=` in the document, which are exactly what a cross-site scripting payload uses. `'unsafe-eval'` allows `eval`, `Function` and string arguments to `setTimeout`, which turns any injected string into code. With these directives the header is present but ineffective: a scanner sees a CSP, and an attacker who finds an XSS runs their script as if there were none. Wildcard sources such as `default-src *` or `script-src https:` weaken the policy in the same way.

2. Steps

• Remove `'unsafe-inline'` and `'unsafe-eval'` from every Content-Security-Policy set with `DefaultHeaders` or `insert_header`.

• Replace wildcard sources such as `*`, `https:` or `data:` in `default-src` and `script-src` with `'self'` and the specific hosts the application needs.

• Move inline scripts and event handler attributes to separate script files, or allow the few that remain with a per-response nonce or a hash.

• Add `object-src 'none'`, `base-uri 'self'` and `frame-ancestors 'none'` to the policy.

• Send the policy from a single middleware so every response, including error pages, receives it.

• Test changes with the `Content-Security-Policy-Report-Only` header and a `report-to` endpoint before enforcing them.

3. Secure code example

use actix_web::middleware::DefaultHeaders;
use actix_web::{get, App, HttpResponse, HttpServer};

const CSP: &str = "default-src 'self'; script-src 'self'; object-src 'none'; \
                   base-uri 'self'; form-action 'self'; frame-ancestors 'none'";

fn security_headers() -> DefaultHeaders {
    // Only same-origin script files run; inline code and eval are blocked...

The corrected policy removes `'unsafe-inline'` and `'unsafe-eval'` and restricts every resource type to the origin of the application. `default-src 'self'` and `script-src 'self'` only allow scripts loaded from files on the same origin, so inline blocks and event handler attributes injected into the page do not execute. `object-src 'none'` disables plugins, `base-uri 'self'` prevents an injected `<base>` tag from redirecting relative script URLs, `form-action 'self'` stops forms from posting data to other sites, and `frame-ancestors 'none'` prevents the page from being framed. Inline scripts must move to separate files. When a page really needs an inline block, the policy can allow it with a per-response random nonce (`'nonce-...'`) or a hash of its content, but never with `'unsafe-inline'`.