Insecure or unset HTTP headers - X-Frame Options
Need
Protection against clickjacking with the Content-Security-Policy frame-ancestors directive
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;
fn security_headers() -> DefaultHeaders {
// ALLOW-FROM is ignored by current browsers, so any site can frame the page
DefaultHeaders::new().add(("X-Frame-Options", "ALLOW-FROM https://partner.example.com"))
}The `security_headers` function below tries to restrict which sites can embed the application in a frame by sending `X-Frame-Options: ALLOW-FROM https://partner.example.com`. `X-Frame-Options` is a legacy header, and its `ALLOW-FROM` value is not supported by current versions of Chrome, Edge, Firefox or Safari. Those browsers ignore the header completely, so the page can be embedded by any website. An attacker can load it in an invisible frame on top of a harmless-looking page and trick users into clicking buttons of the real application, such as confirming a payment or changing security settings (clickjacking). Even the supported values `DENY` and `SAMEORIGIN` cannot express a list of trusted origins, which is why the header has been superseded by the `frame-ancestors` directive of the Content-Security-Policy.
2. Steps
• Control framing with the `frame-ancestors` directive of the Content-Security-Policy instead of the `X-Frame-Options` header.
• Use `frame-ancestors 'none'` for pages that must never be embedded, and list only trusted origins otherwise.
• Remove `X-Frame-Options: ALLOW-FROM`, which current browsers ignore.
• Send the policy from a `DefaultHeaders` middleware so every response, including error pages, carries it.
• Require an explicit confirmation step for sensitive actions such as payments or security changes.
3. Secure code example
use actix_web::middleware::DefaultHeaders;
fn security_headers() -> DefaultHeaders {
// frame-ancestors is enforced by all current browsers and accepts an allowlist
DefaultHeaders::new().add((
"Content-Security-Policy",
"frame-ancestors 'self' https://partner.example.com",
))...The corrected function sends `Content-Security-Policy: frame-ancestors 'self' https://partner.example.com`. Every current browser enforces this directive: the page can only be framed by the application itself and by the listed partner, and any other site gets a blocked frame. When the page should never be framed, the directive becomes `frame-ancestors 'none'`. The value can list several origins, which `X-Frame-Options` cannot do. The directive belongs to the same Content-Security-Policy as the other policy directives, so it should be added to the existing policy of the application rather than sent in a second header.