Cors Wildcard in Actix with Bearer Tokens
Cors Wildcard in Actix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
In Actix-web, configuring CORS with a wildcard origin ("*") while also requiring Bearer token authentication can unintentionally expose authenticated endpoints to cross-origin requests. When allow_credentials is not set to true, browsers do not include cookies or authorization headers in cross-origin requests. However, some JavaScript clients and older browser behaviors may still send credentials when the server responds with a wildcard origin and allows credentials, creating a mismatch between intended scope and actual exposure.
When an Actix service uses a permissive CORS policy such as .allow_any_origin() in combination with routes that validate Bearer tokens via Authorization headers, the effective authentication boundary becomes ambiguous. A frontend hosted on any origin can make requests that include the Authorization header if the server also sets .allow_credentials(true). This can bypass same-origin policy expectations, enabling a cross-origin attacker to leverage an authenticated user’s token if the token is stored in a way that is inadvertently exposed (e.g., in browser storage and sent automatically by the client in cross-origin contexts).
In a black-box scan, middleBrick tests for this by probing endpoints that require Bearer tokens while simulating cross-origin requests with varied CORS headers. It checks whether responses expose credentials leakage, whether preflight responses include overly permissive origins, and whether the server distinguishes properly between authenticated and unauthenticated origins. A typical vulnerable pattern is:
use actix_cors::Cors;
use actix_web::{web, App, HttpServer, Responder};
async fn protected() -> impl Responder {
"secret data"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let cors = Cors::permissive(); // allows any origin
HttpServer::new(move || {
App::new()
.wrap(cors.clone())
.route("/api/secret", web::get().to(protected))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Here, Cors::permissive() is effectively a wildcard origin with no origin restrictions, and if credentials are allowed, any site can make authenticated requests to /api/secret. This pattern violates the principle of least privilege for cross-origin access and can lead to unauthorized data exposure when tokens are transmitted cross-origin.
Additionally, if the server relies on static configuration and does not dynamically validate the Origin header against an allowlist, an attacker can simply set the Origin header to any value and still receive a successful response if the server is misconfigured to trust all origins. middleBrick’s checks for C+BOLA and Property Authorization help surface these misconfigurations by correlating CORS policy with authentication enforcement across origins.
Bearer Tokens-Specific Remediation in Actix — concrete code fixes
Remediation focuses on tightening CORS policy to avoid wildcard origins when credentials are required and explicitly defining which origins are allowed. In Actix-web, use actix-cors to specify allowed origins, methods, and headers rather than relying on permissive settings.
If your service requires Bearer tokens in the Authorization header, ensure that CORS does not permit arbitrary origins and that credentials are only allowed for trusted origins. The following example demonstrates a secure configuration:
use actix_cors::Cors;
use actix_web::{web, App, HttpServer, Responder, HttpRequest};
use actix_web::http::header::HeaderValue;
async fn protected(req: HttpRequest) -> impl Responder {
// Validate Bearer token here, e.g., extract and verify token
format!("Authenticated: {:?}", req.headers().get("Authorization"))
}
fn build_cors() -> Cors {
let allowed_origin = "https://trusted.example.com".parse().unwrap();
Cors::default()
.allowed_origin(&allowed_origin)
.allowed_methods(vec!["GET", "POST"])
.allowed_header(actix_web::http::header::AUTHORIZATION)
.allowed_header(actix_web::http::header::CONTENT_TYPE)
.max_age(3600)
.allow_credentials(true) // only if you need cookies/sessions across origins
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let cors = build_cors();
HttpServer::new(move || {
App::new()
.wrap(cors.clone())
.route("/api/secure", web::get().to(protected))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Key points in this configuration:
allowed_originis explicitly set to a trusted origin rather than usingpermissive()orallow_any_origin().- Only necessary HTTP methods and headers are allowed; Authorization is explicitly permitted.
allow_credentials(true)is used only when the frontend requires cookies or HTTP authentication across origins; otherwise, omit it to reduce risk.- The server should validate the Bearer token independently of CORS, as CORS is a browser-enforced mechanism and does not protect non-browser clients.
For token-based APIs, also ensure that tokens are not leaked in logs or error messages and that preflight responses do not echo the Origin header unsafely. middleBrick’s scans will flag permissive CORS setups alongside authentication checks and provide specific remediation steps aligned with the finding’s severity.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |