Sensitive information sent via URL parameters
Need
Transmission of credentials in request headers or bodies instead of URLs
Context
• Usage of Rust 1.75+ for building application services
• Usage of the actix-web crate for handling HTTP requests
• Usage of the awc crate for outbound HTTP requests
Description
1. Non compliant code
use actix_web::{post, web, HttpResponse};
use awc::Client;
#[post("/contacts/sync")]
async fn sync_contacts(api_key: web::Data<String>) -> HttpResponse {
// The API key ends up in the access logs of every hop
let url = format!("https://partner.example.com/v1/contacts?api_key={}", api_key.get_ref());
match Client::new().get(url).send().await {...The `sync_contacts` handler below calls a partner API with `awc` and puts the application API key in the query string of the URL with `format!`. URLs are not treated as secrets. They are written to the access logs of the partner, of every proxy and load balancer on the way, and of the monitoring tools that record outbound requests, and they often appear in error messages and traces. Anyone with access to any of those logs obtains a working key. The same problem affects the browser side. Redirecting a user to `/reset?token=...`, or sending a session token or a password as a query parameter, stores the value in the browser history and in server logs, and leaks it to third parties through the `Referer` header when the page loads external resources.
2. Steps
• Send API keys, tokens and passwords in request headers, such as `Authorization`, or in the request body, never in the URL of `awc` or `reqwest` requests.
• Do not add tokens or credentials to the `Location` header of redirects built with `HttpResponse::Found()` or `Redirect::to`.
• Receive credentials from browsers in `POST` bodies or `HttpOnly` cookies instead of query parameters.
• Make tokens that must appear in links single-use and short-lived, and send `Referrer-Policy: no-referrer` on the pages that receive them.
• Configure access logs and tracing to redact query strings on endpoints that historically accepted secrets.
• Rotate any credential that was sent in a URL, since it may be stored in logs.
3. Secure code example
use actix_web::http::header::AUTHORIZATION;
use actix_web::{post, web, HttpResponse};
use awc::Client;
#[post("/contacts/sync")]
async fn sync_contacts(api_key: web::Data<String>) -> HttpResponse {
// The key travels in a header, which logs and proxies do not record by default
let request = Client::new()...The corrected handler sends the API key in the `Authorization` header with the `Bearer` scheme and keeps the URL free of secrets. Headers are part of the encrypted HTTPS request like the URL, but access logs, proxies and tracing tools do not record them by default, and they never reach browser history or `Referer` headers. When the partner defines a custom header such as `X-Api-Key`, the same approach applies. Parameters that are not secret, such as pagination or filters, can stay in the query string. For flows that involve the browser, sensitive values should travel in the body of a `POST` request or in an `HttpOnly` cookie. Password reset links that must carry a token should use a single-use, short-lived token, and the reset page should send `Referrer-Policy: no-referrer`.