Salvo provides modular components and middleware for building robust web applications and APIs, but relies on developers to properly isolate sensitive routing surfaces, enforce strict CORS policies, and manage authentication state securely. While features like path normalization and OpenAPI validation help guard against malformed input, developers must explicitly configure security headers, bounded resource limits, and cryptographic secrets. Any gaps in middleware boundaries, secret management, or input validation should fail closed.
Essential implementation rules
Configure Explicit CORS Security Headers and Middleware Placement
Avoid permissive CORS settings such as Cors::permissive() or AllowOrigin::any() on sensitive endpoints. Explicitly restrict origins, methods, headers, and credentials, and attach the CORS handler to the Service instance using hoop() rather than to individual routers so that browser preflight requests are handled properly.
Verify Resource Ownership and Route Authorization
Extract URL path parameters using PathParam and validate resource ownership against the authenticated user retrieved from Salvo’s Depot before executing database transactions. Return a FORBIDDEN status code if authorization checks fail, and isolate public routes from protected sub-routers using distinct sibling structures and scoped .hoop() middleware.
Handle Untrusted Redirects Safely and Halt Execution on Failure
Use the fallible Redirect::with_status_code method instead of panicking convenience constructors like Redirect::found when processing untrusted or dynamic locations. In custom authentication middleware, always invoke ctrl.skip_rest() on the FlowCtrl parameter immediately after setting an unauthorized status to halt downstream handler execution.
Enforce Strict Claims Validation and Secure Token Extraction
When instantiating JWT decoders, use ConstDecoder::with_validation combined with an explicit Validation configuration to enforce expected claims like audience (aud) and issuer (iss). Restrict token extraction mechanisms to secure headers or cookies in production, avoiding query parameters to prevent exposure in logs and browser history.
Load Cryptographic Secrets Securely and Hash Passwords with Argon2id
Initialize cryptographic ciphers, session stores, and CSRF middleware using high-entropy random keys and secret bytes loaded from secure environment variables rather than hardcoded defaults. Always hash user passwords using memory-hard algorithms like Argon2id prior to database storage.
Configure CSRF Protection and Secure Cookie Policies
Enable the csrf feature flag and register session handlers in the router before the Csrf middleware. Explicitly enforce secure cookie attributes using .secure(true)—especially when deployed behind an upstream proxy terminating TLS—and configure appropriate rotation policies.
Isolate File Storage and Sanitize User Paths
Configure DiskStore uploads and StaticDir asset serving using dedicated absolute paths with restrictive file system permissions. Validate and sanitize all user-supplied upload identifiers and path components using is_safe_upload_id and sanitize_path_component to prevent traversal vulnerabilities, while keeping strict path normalization enabled on proxy configurations.
Enforce Input Validation, Parameter Boundaries, and Body Limits
Decorate request data transfer objects and endpoint annotations with OpenAPI validation attributes such as min_length, max_length, pattern, minimum, maximum, and collection item bounds to reject malformed input at compile and runtime. Restrict large payloads by applying SecureMaxSize middleware to specific resource routes.
Harden Network Boundaries, Timeouts, and Proxy Settings
Restrict transport negotiation to explicit ALPN protocols, configure server connection and request timeouts using FuseConfig, limit maximum concurrent connections on server acceptors, and use TrustedProxyIssuer with explicitly defined proxy IP addresses to prevent client IP spoofing.
Encode Dynamic Data Safely Before HTML Rendering
Avoid rendering unescaped dynamic variables directly into HTML responses via Text::Html. Place dynamic strings into an Askama template configured with HTML escape mode enabled, render the template safely, and pass the resulting sanitized string to Text::Html to prevent cross-site scripting.
salvo: All Security Cards
Approximately 7,443 tokens
On this card
Category: access control
Configure Explicit CORS Security Headers and Restrict Cross-Origin Access
Use when
Developing and configuring cross-origin resource sharing (CORS) security headers, preflight caching, and origin boundaries in Salvo applications.
Secure rules
Rule 1: Avoid permissive CORS settings and explicitly restrict allowed request origins.
Do not use Cors::very_permissive(), Cors::permissive(), AllowOrigin::mirror_request(), or AllowOrigin::any() on endpoints handling sensitive data. Instead, define explicit trusted origins using AllowOrigin::exact(...) or AllowOrigin::list(...) to prevent untrusted third-party websites from reading sensitive API responses.
use salvo::cors::AllowOrigin;use salvo::http::HeaderValue;let allow_origin = AllowOrigin::list([ HeaderValue::from_static("https://app.example.com"), HeaderValue::from_static("https://admin.example.com"),]);
Rule 2: Restrict cross-origin credentials using explicit dynamic validation.
Avoid globally enabling credentials with AllowCredentials::yes() unless origin boundaries are strictly restricted. Prefer dynamic validation using AllowCredentials::dynamic or AllowCredentials::dynamic_async to verify the Origin header and request context before emitting true.
use salvo::cors::AllowCredentials;let allow_credentials = AllowCredentials::dynamic(|origin, _req, _depot| { if let Some(origin) = origin { if let Ok(origin_str) = origin.to_str() { return origin_str == "https://app.example.com"; } } false});
Rule 3: Explicitly define allowed request headers instead of mirroring or wildcards.
Avoid permissive settings like AllowHeaders::any() or AllowHeaders::mirror_request() on endpoints handling sensitive data. Instead, explicitly define allowed cross-origin request headers using AllowHeaders::list(...) with concrete HeaderName values.
use salvo::cors::AllowHeaders;use salvo::http::header::{AUTHORIZATION, CONTENT_TYPE};let allow_headers = AllowHeaders::list([AUTHORIZATION, CONTENT_TYPE]);
Rule 4: Explicitly define allowed CORS HTTP methods.
Avoid using AllowMethods::any() or AllowMethods::mirror_request(). Explicitly define permitted HTTP methods using AllowMethods::list() or AllowMethods::exact() so that only designated HTTP verbs are allowed for cross-origin requests.
use salvo::cors::AllowMethods;use salvo::http::Method;let allow_methods = AllowMethods::list([Method::GET, Method::POST]);
Avoid indiscriminately granting access with AllowPrivateNetwork::yes() or true.into(). Use AllowPrivateNetwork::dynamic or AllowPrivateNetwork::dynamic_async to validate incoming Origin headers, target request paths, and context from the Depot before returning the private network access header.
use salvo::cors::AllowPrivateNetwork;use salvo::http::HeaderValue;let allow_pna = AllowPrivateNetwork::dynamic(|origin, req, _depot| { if let Some(origin_val) = origin { origin_val == HeaderValue::from_static("https://trusted.example.com") && req.uri().path().starts_with("/api/pna") } else { false }});
Rule 6: Restrict exposed CORS response headers to non-sensitive names.
Explicitly declare only non-sensitive header names with ExposeHeaders::list rather than using wildcard exposure via ExposeHeaders::any() to prevent exposing sensitive tokens or internal metadata.
use salvo::cors::ExposeHeaders;use salvo_core::http::header::{ACCEPT, CONTENT_TYPE};let expose = ExposeHeaders::list([CONTENT_TYPE, ACCEPT]);
Rule 7: Configure bounded max age for CORS preflight security headers.
Set explicit, bounded TTL values using MaxAge::exact, MaxAge::seconds, or dynamic resolvers (MaxAge::dynamic / MaxAge::dynamic_async) so preflight permissions are cached appropriately without lingering indefinitely.
use std::time::Duration;use salvo::cors::{Cors, MaxAge};let cors = Cors::new() .max_age(MaxAge::exact(Duration::from_secs(3600)));
Rule 8: Attach CORS header middleware to Service instead of Router.
Attach CORS middleware to the Service instance using hoop() rather than to a Router. Salvo routes do not naturally match browser preflight OPTIONS requests, so CORS headers will not be emitted for preflights if attached at the router level.
use salvo_core::prelude::*;use salvo_cors::Cors;let cors = Cors::new() .allow_origin("https://example.com") .allow_methods([Method::GET, Method::POST]) .into_handler();let router = Router::new().get(hello);let service = Service::new(router).hoop(cors);
Configure Strict CORS Policies and Middleware Placement in Salvo
Use when
Developing cross-origin resource sharing policies and attaching CORS handlers to Salvo services.
Secure rules
Rule 1: Restrict CORS allowed headers using explicit lists instead of wildcards or reflection.
Avoid using AllowHeaders::any() or AllowHeaders::mirror_request() in sensitive applications. Use AllowHeaders::list(...) or explicit HeaderName collections to enforce strict header boundaries.
use salvo_core::http::header;use salvo_cors::AllowHeaders;let allow_headers = AllowHeaders::list([ header::AUTHORIZATION, header::CONTENT_TYPE, header::ACCEPT,]);
Rule 2: Restrict allowed CORS HTTP methods to expected operations.
Avoid default broad allowances like AllowMethods::any() or AllowMethods::mirror_request() on sensitive routes. Declare minimal required methods using AllowMethods::list(...) or AllowMethods::exact(...).
use salvo::cors::AllowMethods;use salvo::http::Method;let allow_methods = AllowMethods::list([Method::GET, Method::POST]);
Rule 3: Validate origins explicitly when enabling CORS private network access.
Avoid indiscriminately enabling private network requests via AllowPrivateNetwork::yes(). Use AllowPrivateNetwork::dynamic or AllowPrivateNetwork::dynamic_async to inspect the request origin and path before granting access.
use salvo::cors::{AllowPrivateNetwork, Cors};use salvo::http::HeaderValue;let allow_private = AllowPrivateNetwork::dynamic(|origin, req, _depot| { let trusted_origin = origin == Some(&HeaderValue::from_static("https://trusted-internal-app.example.com")); let safe_endpoint = req.uri().path().starts_with("/api/pna-allowed"); trusted_origin && safe_endpoint});let cors_handler = Cors::new() .allow_private_network(allow_private) .into_handler();
Rule 4: Restrict exposed CORS headers to explicit header lists.
Avoid using ExposeHeaders::any() when responses contain sensitive headers. Explicitly specify only required response header names using ExposeHeaders::list().
use salvo::cors::ExposeHeaders;use salvo::http::header;let expose = ExposeHeaders::list([ header::HeaderName::from_static("x-request-id"),]);
Rule 5: Attach CORS middleware to Service instead of Router.
Attach the Cors handler to the Salvo Service instance using .hoop(), rather than registering it at the Router level, to ensure preflight OPTIONS requests are handled properly.
use salvo_core::http::Method;use salvo_core::prelude::*;use salvo_cors::Cors;let cors = Cors::new() .allow_origin("https://app.example.com") .allow_methods([Method::GET, Method::POST]) .allow_headers(["authorization", "content-type"]) .into_handler();let router = Router::new();let service = Service::new(router).hoop(cors);
Validate Path Parameters and Resource Ownership Against Authenticated Sessions
Use when
When building route handlers that retrieve, modify, or delete resources based on URL path parameters or identifiers.
Secure rules
Rule 1: Verify resource ownership and authorization against the authenticated user stored in the depot before executing database operations.
Extract identifiers from the URL using PathParam and retrieve the current user or security model from Salvo’s Depot. Perform a strict equality or permission check to ensure the authenticated actor owns the requested resource before proceeding with transactional logic, and return a FORBIDDEN status code if validation fails.
Use Fallible Redirect Constructor for Untrusted URIs
Use when
When constructing HTTP redirects using untrusted or dynamically computed input data in Salvo handlers.
Secure rules
Rule 1: Use the fallible Redirect::with_status_code method instead of panicking convenience constructors when handling potentially malformed or untrusted redirect locations.
Panicking convenience constructors like Redirect::found will assert and panic internally if the computed URI is not a valid HTTP header value. Pass untrusted input to Redirect::with_status_code and handle the resulting Result gracefully to prevent Denial of Service vulnerabilities caused by engineered panics.
Configure Explicit Claims Validation and Token Decoders in Salvo
Use when
Setting up JWT authentication or token decoders in Salvo applications to authenticate clients and verify credentials.
Secure rules
Rule 1: Explicitly configure audience and issuer validation when instantiating JWT decoders.
When instantiating ConstDecoder, use ConstDecoder::with_validation with a configured Validation object to enforce checks on expected claims like aud and iss to prevent token replay and privilege escalation across applications.
Rule 2: Restrict token extraction mechanisms in production environments.
Avoid extracting JWTs from URL query parameters using QueryFinder in production to prevent token exposure in logs, browser history, and referer headers. Use HeaderFinder or secure cookies instead.
use salvo::jwt_auth::{JwtAuth, ConstDecoder, HeaderFinder};let auth_handler: JwtAuth<JwtClaims, _> = JwtAuth::new( ConstDecoder::from_secret(b"your_secure_secret_key")).finders(vec![Box::new(HeaderFinder::new())]);
Category: boundary control
Enforce Middleware Boundaries by Isolating Public Routes
Use when
Structuring the router tree to ensure that authentication and authorization hoops are only applied to protected routes and do not inadvertently enclose public endpoints.
Secure rules
Rule 1: Isolate public routes from protected sub-routers to prevent unauthorized access or unintended authentication requirements.
Use distinct sibling routes and place authentication middleware only on sensitive branches of your router tree using the .hoop() method rather than applying it globally.
Disable query parameter configuration overrides in Swagger UI
Use when
Configuring Swagger UI endpoints in Salvo OpenAPI integrations where untrusted users could manipulate configuration parameters via URL query parameters.
Keep the query_config_enabled option disabled or explicitly set it to false to prevent attackers from overriding configuration parameters via URL query parameters and loading malicious external schemas.
use salvo_oapi::swagger_ui::Config;let config = Config::new(["/api-docs/openapi.json"]) .query_config_enabled(false);
Category: cryptography
Hash Passwords Securely Using Argon2id
Use when
When storing or verifying user credentials and passwords in Salvo database examples or applications.
Secure rules
Rule 1: Hash user credentials using memory-hard password hashing algorithms such as Argon2id prior to database storage.
Always hash passwords before database storage and use robust hashing libraries like argon2 to protect against offline brute-force attacks.
use argon2::{password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2};pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string(); Ok(password_hash)}
Use Cryptographically Secure Keys and Random Generators for Ciphers and Cookies
Use when
When initializing ciphers, session stores, or flash cookies requiring cryptographic keys or secure entropy.
Secure rules
Rule 1: Load cryptographic keys from safe environment stores instead of using hardcoded or predictable sequences of bytes.
Feed key-based ciphers like HmacCipher, AesGcmCipher, or CcpCipher with high-entropy cryptographically secure random keys loaded from safe environment variables to prevent token forgery.
use salvo_csrf::hmac_cookie_csrf;use salvo_csrf::HeaderFinder;let key_string = std::env::var("CSRF_SECRET_KEY").expect("CSRF_SECRET_KEY must be set");let mut key = [0u8; 32];let decoded = hex::decode(key_string).expect("Failed to decode key hex");key.copy_from_slice(&decoded[..32]);let csrf_middleware = hmac_cookie_csrf(key, HeaderFinder::new("x-csrf-token"));
Rule 2: Assign a stable cryptographic key to flash cookies and session stores.
Initialize stores like CookieStore with a stable, high-entropy secret loaded from environment variables rather than relying on random defaults that invalidate sessions on restart.
use salvo_core::http::cookie::Key;use salvo_flash::CookieStore;let secret_key_bytes = std::env::var("SESSION_SECRET_KEY") .expect("SESSION_SECRET_KEY must be set");let key = Key::from(secret_key_bytes.as_bytes());let store = CookieStore::new().key(key);
Category: csrf
Configure and Integrate Salvo CSRF Protection Middleware
Use when
Developing state-changing APIs and web routes in Salvo that require protection against cross-site request forgery using tokens, session stores, custom finders, and secure cookie configurations.
Secure rules
Rule 1: Enable the csrf feature and register session handlers before CSRF middleware
When protecting state-changing APIs using session stores, developers must enable the csrf feature flag and register the SessionHandler in the router before the Csrf middleware to ensure the request depot can successfully access session contexts.
Rule 2: Configure secure cookie policies and rotation settings
Explicitly enforce secure cookie attributes when hosting behind TLS-terminating proxies using .secure(true), and select appropriate rotation policies such as CsrfRotationPolicy::PerRequest or CsrfRotationPolicy::PerSession based on your application security and usability needs.
Rule 3: Embed and retrieve CSRF tokens safely using hidden inputs and headers
Retrieve the generated token from the depot using depot.csrf_token() to render it securely into form fields or use HeaderFinder to require custom headers like X-CSRF-Token for stronger cross-origin defense-in-depth.
#[handler]pub async fn get_page(depot: &mut Depot, res: &mut Response) { let csrf_token = depot.csrf_token().unwrap_or_default(); let html = format!("<input type=\"hidden\" name=\"csrf_token\" value=\"{}\" />", csrf_token); res.render(Text::Html(html));}
Category: file handling
Configure Secure File Storage Roots and Path Containment in Salvo
Use when
Configuring static asset directories, file upload storage stores, or local file handling paths in Salvo applications.
Secure rules
Rule 1: Isolate file storage and static asset directories to dedicated absolute paths with restrictive permissions.
When configuring DiskStore for uploads or StaticDir for serving assets, always define a dedicated, isolated directory path using absolute paths rather than default relative locations. On Unix platforms, restrict file system permissions such as using 0700 modes on cache or storage directories so that only the application process owner can access sensitive contents.
use salvo_core::prelude::*;use salvo_serve_static::StaticDir;let router = Router::new().push( Router::with_path("static/{**}").get( StaticDir::new("/var/www/my-app/static-assets") .defaults("index.html") ),);
Rule 2: Validate and sanitize all user-supplied paths, filenames, and upload identifiers before filesystem operations.
Verify that all user-supplied upload IDs are strictly validated using is_safe_upload_id and sanitize individual directory or filename components using sanitize_path_component before combining them into a full storage directory path to prevent path traversal.
Enforce Input and Parameter Constraints Using OpenAPI Validation Attributes
Use when
When defining request data transfer objects, parameters, and schema structures using Salvo’s OpenAPI integration macros to ensure malformed input is rejected.
Secure rules
Rule 1: Apply macro-driven validation attributes to enforce explicit structural and size boundaries on data structures.
Decorate your request data transfer objects with schema constraints such as min_length, max_length, pattern, minimum, maximum, max_items, and min_items to restrict input values and formats directly at compile time.
Rule 2: Specify parameter boundaries within endpoint annotations for path and query inputs.
Use parameter validation attributes supported by the #[endpoint] macro to enforce size, length, and pattern constraints on query and path parameters.
use salvo_core::prelude::*;use salvo_oapi::endpoint;#[endpoint( parameters( ( "user_id" = String, Path, description = "The target user's UUID", max_length = 36, min_length = 36, pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ), ( "limit" = i32, Query, description = "Max number of items to fetch", minimum = 1, maximum = 100 ) ))]pub async fn get_user_data() {}
Rule 3: Restrict parameter formats directly in route paths using wisps or regular expressions.
Prevent unconstrained path parameters by utilizing built-in wisps like {id:num} or custom regular expressions registered via PathFilter::register_wisp_regex to centralize pattern matching.
use salvo_core::prelude::*;use salvo_core::routing::filters::PathFilter;#[handler]async fn show_article(req: &mut Request) { let id = req.param::<i64>("id").unwrap();}let guid_regex = regex::Regex::new("[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}").unwrap();PathFilter::register_wisp_regex("guid", guid_regex);let router = Router::new() .push(Router::with_path("articles/{id:num}").get(show_article)) .push(Router::with_path("users/{id:guid}").get(show_article));
Rule 4: Apply explicit limits on collection fields using item count bounds.
Specify explicit constraints like max_items and min_items on collection fields such as vectors or sets to prevent unbounded payload processing and resource exhaustion.
Keep Strict Path Normalization Enabled to Prevent Path Confusion and Traversal
Use when
Configuring proxy routing in Salvo to handle upstream requests safely and prevent attackers from bypassing security controls via encoded or relative path components.
Secure rules
Rule 1: Ensure strict path normalization remains enabled on proxy configurations to reject ambiguous path components and percent-encoded characters.
Do not disable path normalization on proxy instances unless you have verified that the upstream target is completely immune to path traversal and path confusion vulnerabilities. Keep the normalization filter active so that literal relative path components and ambiguous percent-encoded characters are properly rejected before reaching the upstream server.
Configure Protocol Versions and Timeouts to Prevent Protocol Abuse
Use when
Configuring network connection settings, transport negotiation protocols, and request timeouts in Salvo applications.
Secure rules
Rule 1: Restrict transport negotiation to explicit, secure ALPN protocols instead of relying on default fallbacks.
Call .alpn_protocols() on your configuration object to explicitly restrict negotiation to a specific subset of protocols, such as HTTP/2 only, preventing unencrypted or outdated protocol fallbacks.
let openssl_config = OpensslConfig::new(keycert) .alpn_protocols(vec![b"\x02h2".to_vec()]);
Rule 2: Manage HTTP/1 header timeouts using server fuse policies rather than lower-level protocol builder settings.
Configure header and connection timeouts via FuseConfig to properly handle slow client connections and prevent hangs during initial protocol detection.
use salvo_core::prelude::*;use salvo_core::fuse::FuseConfig;#[tokio::main]async fn main() { let acceptor = TcpListener::new("127.0.0.1:8698").bind().await; let server = Server::new(acceptor).fuse_config(FuseConfig::default()); server.serve(Router::new()).await;}
Category: network boundary
Configure Trusted Proxy Identifiers to Prevent Client IP Spoofing
Use when
Configuring rate-limiting, proxy routing, or client origin identification behind network load balancers or reverse proxies where incoming request headers can be manipulated.
Secure rules
Rule 1: Use TrustedProxyIssuer with explicitly defined proxy IP addresses instead of unconditionally trusting client-controlled forwarding headers.
Initialize the TrustedProxyIssuer with a verified list of your proxy IPs to securely identify clients while avoiding header-spoofing attacks that bypass rate limits.
Escape untrusted dynamic data before rendering in HTML responses
Use when
Rendering dynamic variables retrieved from session states or flash messages into HTML responses using Text::Html in Salvo handlers.
Secure rules
Rule 1: Render dynamic HTML values through an Askama HTML template
Text::Html marks its supplied string as HTML but does not escape that string. When a response contains request parameters, flash-message values, or other dynamic strings, place those values in an Askama template configured for HTML escape mode, render the template, and pass the rendered result to Text::Html. Keep the .html or ext = "html" escape mode enabled rather than concatenating dynamic values directly into an HTML string.
This example requires salvo = { version = "0.94.0", features = ["flash"] }, askama = "0.11", and Tokio with its macros feature.
When defining routes and handling incoming requests in Salvo to prevent resource exhaustion from large payloads or unbounded path parameters.
Secure rules
Rule 1: Restrict large payloads using route-specific body size limits rather than expanding global settings.
Keep the global default request body size at its secure value and register the SecureMaxSize middleware only on specific routers or routes that require handling larger payloads.
Rule 2: Apply explicit length constraints on character-based path parameters.
When configuring path parameters using wisps like CharsWispBuilder, specify an explicit min and max range to prevent dynamic heap allocation issues from malicious inputs.
use salvo::prelude::*;let router = Router::with_path("users/{id:num(1..=12)}");
Limit Concurrent Connections and Request Timeouts
Use when
When initializing server acceptors and configuring timeout behaviors to protect against slow HTTP and connection exhaustion attacks.
Secure rules
Rule 1: Configure maximum concurrent connections on the server.
Apply the max_connections method during server initialization to limit active concurrent connections and defend against denial-of-service attacks.
use salvo_core::prelude::*;#[tokio::main]async fn main() { let acceptor = TcpListener::new("127.0.0.1:8698").bind().await; let server = Server::new(acceptor).max_connections(1000); server.serve(Router::new()).await;}
Rule 2: Configure strict timeouts for standard REST or JSON APIs.
Opt for FuseConfig::strict() or explicitly configure idle, write-stall, and request body timeouts when serving applications that do not require long-lived streaming.
Load Cryptographic Secret Keys and Session Secrets Securely at Runtime
Use when
When configuring authentication, session handlers, JWT decoders, or encrypted keys in Salvo applications.
Secure rules
Rule 1: Use a cryptographically random session secret of at least 64 bytes
Generate the secret passed to SessionHandler with a cryptographically secure random number generator. Salvo requires this secret to contain at least 64 bytes and uses it to derive the key that signs and verifies session cookies.
Halt Execution on Authentication Failure in Custom Salvo Middleware
Use when
When writing custom authentication handlers or middleware in Salvo that need to reject unauthorized requests and prevent downstream handler execution.
Secure rules
Rule 1: Call skip_rest() on FlowCtrl when an authentication failure occurs to halt the execution chain.
When writing custom authentication handlers or middleware in Salvo, you must explicitly halt the routing execution chain on failure. Always retrieve the FlowCtrl parameter in your handler and call ctrl.skip_rest() immediately after setting the unauthorized response status to ensure Salvo does not continue executing downstream handlers in the routing table.
Force Secure Cookie Attributes for Sessions Behind Upstream TLS Termination
Use when
Configuring session handling in Salvo applications deployed behind an upstream proxy or load balancer that terminates TLS.
Secure rules
Rule 1: Explicitly force the secure attribute on session cookies when TLS is terminated upstream.
By default, SessionHandler automatically detects whether to append the Secure flag based on the request URI scheme. When deployed behind an upstream proxy that terminates TLS, the local request may appear as unencrypted HTTP, causing Salvo to omit the Secure attribute. To prevent session cookies from being transmitted over unencrypted HTTP channels, explicitly configure the session handler builder with secure_cookie(true).
use salvo::session::SessionHandler;use saysion::MemoryStore;let store = MemoryStore::new();let secret = [0u8; 64];let handler = SessionHandler::builder(store, &secret) .secure_cookie(true) .build() .unwrap();
Configure Explicit CORS Security Headers and Restrict Cross-Origin Access
Approximately 2,016 tokens
Use when
Developing and configuring cross-origin resource sharing (CORS) security headers, preflight caching, and origin boundaries in Salvo applications.
Secure rules
Rule 1: Avoid permissive CORS settings and explicitly restrict allowed request origins.
Do not use Cors::very_permissive(), Cors::permissive(), AllowOrigin::mirror_request(), or AllowOrigin::any() on endpoints handling sensitive data. Instead, define explicit trusted origins using AllowOrigin::exact(...) or AllowOrigin::list(...) to prevent untrusted third-party websites from reading sensitive API responses.
use salvo::cors::AllowOrigin;use salvo::http::HeaderValue;let allow_origin = AllowOrigin::list([ HeaderValue::from_static("https://app.example.com"), HeaderValue::from_static("https://admin.example.com"),]);
Rule 2: Restrict cross-origin credentials using explicit dynamic validation.
Avoid globally enabling credentials with AllowCredentials::yes() unless origin boundaries are strictly restricted. Prefer dynamic validation using AllowCredentials::dynamic or AllowCredentials::dynamic_async to verify the Origin header and request context before emitting true.
use salvo::cors::AllowCredentials;let allow_credentials = AllowCredentials::dynamic(|origin, _req, _depot| { if let Some(origin) = origin { if let Ok(origin_str) = origin.to_str() { return origin_str == "https://app.example.com"; } } false});
Rule 3: Explicitly define allowed request headers instead of mirroring or wildcards.
Avoid permissive settings like AllowHeaders::any() or AllowHeaders::mirror_request() on endpoints handling sensitive data. Instead, explicitly define allowed cross-origin request headers using AllowHeaders::list(...) with concrete HeaderName values.
use salvo::cors::AllowHeaders;use salvo::http::header::{AUTHORIZATION, CONTENT_TYPE};let allow_headers = AllowHeaders::list([AUTHORIZATION, CONTENT_TYPE]);
Rule 4: Explicitly define allowed CORS HTTP methods.
Avoid using AllowMethods::any() or AllowMethods::mirror_request(). Explicitly define permitted HTTP methods using AllowMethods::list() or AllowMethods::exact() so that only designated HTTP verbs are allowed for cross-origin requests.
use salvo::cors::AllowMethods;use salvo::http::Method;let allow_methods = AllowMethods::list([Method::GET, Method::POST]);
Avoid indiscriminately granting access with AllowPrivateNetwork::yes() or true.into(). Use AllowPrivateNetwork::dynamic or AllowPrivateNetwork::dynamic_async to validate incoming Origin headers, target request paths, and context from the Depot before returning the private network access header.
use salvo::cors::AllowPrivateNetwork;use salvo::http::HeaderValue;let allow_pna = AllowPrivateNetwork::dynamic(|origin, req, _depot| { if let Some(origin_val) = origin { origin_val == HeaderValue::from_static("https://trusted.example.com") && req.uri().path().starts_with("/api/pna") } else { false }});
Rule 6: Restrict exposed CORS response headers to non-sensitive names.
Explicitly declare only non-sensitive header names with ExposeHeaders::list rather than using wildcard exposure via ExposeHeaders::any() to prevent exposing sensitive tokens or internal metadata.
use salvo::cors::ExposeHeaders;use salvo_core::http::header::{ACCEPT, CONTENT_TYPE};let expose = ExposeHeaders::list([CONTENT_TYPE, ACCEPT]);
Rule 7: Configure bounded max age for CORS preflight security headers.
Set explicit, bounded TTL values using MaxAge::exact, MaxAge::seconds, or dynamic resolvers (MaxAge::dynamic / MaxAge::dynamic_async) so preflight permissions are cached appropriately without lingering indefinitely.
use std::time::Duration;use salvo::cors::{Cors, MaxAge};let cors = Cors::new() .max_age(MaxAge::exact(Duration::from_secs(3600)));
Rule 8: Attach CORS header middleware to Service instead of Router.
Attach CORS middleware to the Service instance using hoop() rather than to a Router. Salvo routes do not naturally match browser preflight OPTIONS requests, so CORS headers will not be emitted for preflights if attached at the router level.
use salvo_core::prelude::*;use salvo_cors::Cors;let cors = Cors::new() .allow_origin("https://example.com") .allow_methods([Method::GET, Method::POST]) .into_handler();let router = Router::new().get(hello);let service = Service::new(router).hoop(cors);
Configure Strict CORS Policies and Middleware Placement in Salvo
Use when
Developing cross-origin resource sharing policies and attaching CORS handlers to Salvo services.
Secure rules
Rule 1: Restrict CORS allowed headers using explicit lists instead of wildcards or reflection.
Avoid using AllowHeaders::any() or AllowHeaders::mirror_request() in sensitive applications. Use AllowHeaders::list(...) or explicit HeaderName collections to enforce strict header boundaries.
use salvo_core::http::header;use salvo_cors::AllowHeaders;let allow_headers = AllowHeaders::list([ header::AUTHORIZATION, header::CONTENT_TYPE, header::ACCEPT,]);
Rule 2: Restrict allowed CORS HTTP methods to expected operations.
Avoid default broad allowances like AllowMethods::any() or AllowMethods::mirror_request() on sensitive routes. Declare minimal required methods using AllowMethods::list(...) or AllowMethods::exact(...).
use salvo::cors::AllowMethods;use salvo::http::Method;let allow_methods = AllowMethods::list([Method::GET, Method::POST]);
Rule 3: Validate origins explicitly when enabling CORS private network access.
Avoid indiscriminately enabling private network requests via AllowPrivateNetwork::yes(). Use AllowPrivateNetwork::dynamic or AllowPrivateNetwork::dynamic_async to inspect the request origin and path before granting access.
use salvo::cors::{AllowPrivateNetwork, Cors};use salvo::http::HeaderValue;let allow_private = AllowPrivateNetwork::dynamic(|origin, req, _depot| { let trusted_origin = origin == Some(&HeaderValue::from_static("https://trusted-internal-app.example.com")); let safe_endpoint = req.uri().path().starts_with("/api/pna-allowed"); trusted_origin && safe_endpoint});let cors_handler = Cors::new() .allow_private_network(allow_private) .into_handler();
Rule 4: Restrict exposed CORS headers to explicit header lists.
Avoid using ExposeHeaders::any() when responses contain sensitive headers. Explicitly specify only required response header names using ExposeHeaders::list().
use salvo::cors::ExposeHeaders;use salvo::http::header;let expose = ExposeHeaders::list([ header::HeaderName::from_static("x-request-id"),]);
Rule 5: Attach CORS middleware to Service instead of Router.
Attach the Cors handler to the Salvo Service instance using .hoop(), rather than registering it at the Router level, to ensure preflight OPTIONS requests are handled properly.
use salvo_core::http::Method;use salvo_core::prelude::*;use salvo_cors::Cors;let cors = Cors::new() .allow_origin("https://app.example.com") .allow_methods([Method::GET, Method::POST]) .allow_headers(["authorization", "content-type"]) .into_handler();let router = Router::new();let service = Service::new(router).hoop(cors);
Validate Path Parameters and Resource Ownership Against Authenticated Sessions
Use when
When building route handlers that retrieve, modify, or delete resources based on URL path parameters or identifiers.
Secure rules
Rule 1: Verify resource ownership and authorization against the authenticated user stored in the depot before executing database operations.
Extract identifiers from the URL using PathParam and retrieve the current user or security model from Salvo’s Depot. Perform a strict equality or permission check to ensure the authenticated actor owns the requested resource before proceeding with transactional logic, and return a FORBIDDEN status code if validation fails.
Use Fallible Redirect Constructor for Untrusted URIs
Approximately 286 tokens
Use when
When constructing HTTP redirects using untrusted or dynamically computed input data in Salvo handlers.
Secure rules
Rule 1: Use the fallible Redirect::with_status_code method instead of panicking convenience constructors when handling potentially malformed or untrusted redirect locations.
Panicking convenience constructors like Redirect::found will assert and panic internally if the computed URI is not a valid HTTP header value. Pass untrusted input to Redirect::with_status_code and handle the resulting Result gracefully to prevent Denial of Service vulnerabilities caused by engineered panics.
Configure Explicit Claims Validation and Token Decoders in Salvo
Approximately 344 tokens
Use when
Setting up JWT authentication or token decoders in Salvo applications to authenticate clients and verify credentials.
Secure rules
Rule 1: Explicitly configure audience and issuer validation when instantiating JWT decoders.
When instantiating ConstDecoder, use ConstDecoder::with_validation with a configured Validation object to enforce checks on expected claims like aud and iss to prevent token replay and privilege escalation across applications.
Rule 2: Restrict token extraction mechanisms in production environments.
Avoid extracting JWTs from URL query parameters using QueryFinder in production to prevent token exposure in logs, browser history, and referer headers. Use HeaderFinder or secure cookies instead.
use salvo::jwt_auth::{JwtAuth, ConstDecoder, HeaderFinder};let auth_handler: JwtAuth<JwtClaims, _> = JwtAuth::new( ConstDecoder::from_secret(b"your_secure_secret_key")).finders(vec![Box::new(HeaderFinder::new())]);
Enforce Middleware Boundaries by Isolating Public Routes
Approximately 234 tokens
Use when
Structuring the router tree to ensure that authentication and authorization hoops are only applied to protected routes and do not inadvertently enclose public endpoints.
Secure rules
Rule 1: Isolate public routes from protected sub-routers to prevent unauthorized access or unintended authentication requirements.
Use distinct sibling routes and place authentication middleware only on sensitive branches of your router tree using the .hoop() method rather than applying it globally.
Disable query parameter configuration overrides in Swagger UI
Approximately 169 tokens
Use when
Configuring Swagger UI endpoints in Salvo OpenAPI integrations where untrusted users could manipulate configuration parameters via URL query parameters.
Keep the query_config_enabled option disabled or explicitly set it to false to prevent attackers from overriding configuration parameters via URL query parameters and loading malicious external schemas.
use salvo_oapi::swagger_ui::Config;let config = Config::new(["/api-docs/openapi.json"]) .query_config_enabled(false);
Hash Passwords Securely Using Argon2id
Approximately 576 tokens
Use when
When storing or verifying user credentials and passwords in Salvo database examples or applications.
Secure rules
Rule 1: Hash user credentials using memory-hard password hashing algorithms such as Argon2id prior to database storage.
Always hash passwords before database storage and use robust hashing libraries like argon2 to protect against offline brute-force attacks.
use argon2::{password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2};pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string(); Ok(password_hash)}
Use Cryptographically Secure Keys and Random Generators for Ciphers and Cookies
Use when
When initializing ciphers, session stores, or flash cookies requiring cryptographic keys or secure entropy.
Secure rules
Rule 1: Load cryptographic keys from safe environment stores instead of using hardcoded or predictable sequences of bytes.
Feed key-based ciphers like HmacCipher, AesGcmCipher, or CcpCipher with high-entropy cryptographically secure random keys loaded from safe environment variables to prevent token forgery.
use salvo_csrf::hmac_cookie_csrf;use salvo_csrf::HeaderFinder;let key_string = std::env::var("CSRF_SECRET_KEY").expect("CSRF_SECRET_KEY must be set");let mut key = [0u8; 32];let decoded = hex::decode(key_string).expect("Failed to decode key hex");key.copy_from_slice(&decoded[..32]);let csrf_middleware = hmac_cookie_csrf(key, HeaderFinder::new("x-csrf-token"));
Rule 2: Assign a stable cryptographic key to flash cookies and session stores.
Initialize stores like CookieStore with a stable, high-entropy secret loaded from environment variables rather than relying on random defaults that invalidate sessions on restart.
use salvo_core::http::cookie::Key;use salvo_flash::CookieStore;let secret_key_bytes = std::env::var("SESSION_SECRET_KEY") .expect("SESSION_SECRET_KEY must be set");let key = Key::from(secret_key_bytes.as_bytes());let store = CookieStore::new().key(key);
Configure and Integrate Salvo CSRF Protection Middleware
Approximately 592 tokens
Use when
Developing state-changing APIs and web routes in Salvo that require protection against cross-site request forgery using tokens, session stores, custom finders, and secure cookie configurations.
Secure rules
Rule 1: Enable the csrf feature and register session handlers before CSRF middleware
When protecting state-changing APIs using session stores, developers must enable the csrf feature flag and register the SessionHandler in the router before the Csrf middleware to ensure the request depot can successfully access session contexts.
Rule 2: Configure secure cookie policies and rotation settings
Explicitly enforce secure cookie attributes when hosting behind TLS-terminating proxies using .secure(true), and select appropriate rotation policies such as CsrfRotationPolicy::PerRequest or CsrfRotationPolicy::PerSession based on your application security and usability needs.
Rule 3: Embed and retrieve CSRF tokens safely using hidden inputs and headers
Retrieve the generated token from the depot using depot.csrf_token() to render it securely into form fields or use HeaderFinder to require custom headers like X-CSRF-Token for stronger cross-origin defense-in-depth.
#[handler]pub async fn get_page(depot: &mut Depot, res: &mut Response) { let csrf_token = depot.csrf_token().unwrap_or_default(); let html = format!("<input type=\"hidden\" name=\"csrf_token\" value=\"{}\" />", csrf_token); res.render(Text::Html(html));}
Configure Secure File Storage Roots and Path Containment in Salvo
Approximately 358 tokens
Use when
Configuring static asset directories, file upload storage stores, or local file handling paths in Salvo applications.
Secure rules
Rule 1: Isolate file storage and static asset directories to dedicated absolute paths with restrictive permissions.
When configuring DiskStore for uploads or StaticDir for serving assets, always define a dedicated, isolated directory path using absolute paths rather than default relative locations. On Unix platforms, restrict file system permissions such as using 0700 modes on cache or storage directories so that only the application process owner can access sensitive contents.
use salvo_core::prelude::*;use salvo_serve_static::StaticDir;let router = Router::new().push( Router::with_path("static/{**}").get( StaticDir::new("/var/www/my-app/static-assets") .defaults("index.html") ),);
Rule 2: Validate and sanitize all user-supplied paths, filenames, and upload identifiers before filesystem operations.
Verify that all user-supplied upload IDs are strictly validated using is_safe_upload_id and sanitize individual directory or filename components using sanitize_path_component before combining them into a full storage directory path to prevent path traversal.
Enforce Input and Parameter Constraints Using OpenAPI Validation Attributes
Approximately 772 tokens
Use when
When defining request data transfer objects, parameters, and schema structures using Salvo’s OpenAPI integration macros to ensure malformed input is rejected.
Secure rules
Rule 1: Apply macro-driven validation attributes to enforce explicit structural and size boundaries on data structures.
Decorate your request data transfer objects with schema constraints such as min_length, max_length, pattern, minimum, maximum, max_items, and min_items to restrict input values and formats directly at compile time.
Rule 2: Specify parameter boundaries within endpoint annotations for path and query inputs.
Use parameter validation attributes supported by the #[endpoint] macro to enforce size, length, and pattern constraints on query and path parameters.
use salvo_core::prelude::*;use salvo_oapi::endpoint;#[endpoint( parameters( ( "user_id" = String, Path, description = "The target user's UUID", max_length = 36, min_length = 36, pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ), ( "limit" = i32, Query, description = "Max number of items to fetch", minimum = 1, maximum = 100 ) ))]pub async fn get_user_data() {}
Rule 3: Restrict parameter formats directly in route paths using wisps or regular expressions.
Prevent unconstrained path parameters by utilizing built-in wisps like {id:num} or custom regular expressions registered via PathFilter::register_wisp_regex to centralize pattern matching.
use salvo_core::prelude::*;use salvo_core::routing::filters::PathFilter;#[handler]async fn show_article(req: &mut Request) { let id = req.param::<i64>("id").unwrap();}let guid_regex = regex::Regex::new("[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}").unwrap();PathFilter::register_wisp_regex("guid", guid_regex);let router = Router::new() .push(Router::with_path("articles/{id:num}").get(show_article)) .push(Router::with_path("users/{id:guid}").get(show_article));
Rule 4: Apply explicit limits on collection fields using item count bounds.
Specify explicit constraints like max_items and min_items on collection fields such as vectors or sets to prevent unbounded payload processing and resource exhaustion.
Keep Strict Path Normalization Enabled to Prevent Path Confusion and Traversal
Approximately 187 tokens
Use when
Configuring proxy routing in Salvo to handle upstream requests safely and prevent attackers from bypassing security controls via encoded or relative path components.
Secure rules
Rule 1: Ensure strict path normalization remains enabled on proxy configurations to reject ambiguous path components and percent-encoded characters.
Do not disable path normalization on proxy instances unless you have verified that the upstream target is completely immune to path traversal and path confusion vulnerabilities. Keep the normalization filter active so that literal relative path components and ambiguous percent-encoded characters are properly rejected before reaching the upstream server.
Configure Protocol Versions and Timeouts to Prevent Protocol Abuse
Approximately 318 tokens
Use when
Configuring network connection settings, transport negotiation protocols, and request timeouts in Salvo applications.
Secure rules
Rule 1: Restrict transport negotiation to explicit, secure ALPN protocols instead of relying on default fallbacks.
Call .alpn_protocols() on your configuration object to explicitly restrict negotiation to a specific subset of protocols, such as HTTP/2 only, preventing unencrypted or outdated protocol fallbacks.
let openssl_config = OpensslConfig::new(keycert) .alpn_protocols(vec![b"\x02h2".to_vec()]);
Rule 2: Manage HTTP/1 header timeouts using server fuse policies rather than lower-level protocol builder settings.
Configure header and connection timeouts via FuseConfig to properly handle slow client connections and prevent hangs during initial protocol detection.
use salvo_core::prelude::*;use salvo_core::fuse::FuseConfig;#[tokio::main]async fn main() { let acceptor = TcpListener::new("127.0.0.1:8698").bind().await; let server = Server::new(acceptor).fuse_config(FuseConfig::default()); server.serve(Router::new()).await;}
Configure Trusted Proxy Identifiers to Prevent Client IP Spoofing
Approximately 267 tokens
Use when
Configuring rate-limiting, proxy routing, or client origin identification behind network load balancers or reverse proxies where incoming request headers can be manipulated.
Secure rules
Rule 1: Use TrustedProxyIssuer with explicitly defined proxy IP addresses instead of unconditionally trusting client-controlled forwarding headers.
Initialize the TrustedProxyIssuer with a verified list of your proxy IPs to securely identify clients while avoiding header-spoofing attacks that bypass rate limits.
Escape untrusted dynamic data before rendering in HTML responses
Approximately 562 tokens
Use when
Rendering dynamic variables retrieved from session states or flash messages into HTML responses using Text::Html in Salvo handlers.
Secure rules
Rule 1: Render dynamic HTML values through an Askama HTML template
Text::Html marks its supplied string as HTML but does not escape that string. When a response contains request parameters, flash-message values, or other dynamic strings, place those values in an Askama template configured for HTML escape mode, render the template, and pass the rendered result to Text::Html. Keep the .html or ext = "html" escape mode enabled rather than concatenating dynamic values directly into an HTML string.
This example requires salvo = { version = "0.94.0", features = ["flash"] }, askama = "0.11", and Tokio with its macros feature.
When defining routes and handling incoming requests in Salvo to prevent resource exhaustion from large payloads or unbounded path parameters.
Secure rules
Rule 1: Restrict large payloads using route-specific body size limits rather than expanding global settings.
Keep the global default request body size at its secure value and register the SecureMaxSize middleware only on specific routers or routes that require handling larger payloads.
Rule 2: Apply explicit length constraints on character-based path parameters.
When configuring path parameters using wisps like CharsWispBuilder, specify an explicit min and max range to prevent dynamic heap allocation issues from malicious inputs.
use salvo::prelude::*;let router = Router::with_path("users/{id:num(1..=12)}");
Limit Concurrent Connections and Request Timeouts
Use when
When initializing server acceptors and configuring timeout behaviors to protect against slow HTTP and connection exhaustion attacks.
Secure rules
Rule 1: Configure maximum concurrent connections on the server.
Apply the max_connections method during server initialization to limit active concurrent connections and defend against denial-of-service attacks.
use salvo_core::prelude::*;#[tokio::main]async fn main() { let acceptor = TcpListener::new("127.0.0.1:8698").bind().await; let server = Server::new(acceptor).max_connections(1000); server.serve(Router::new()).await;}
Rule 2: Configure strict timeouts for standard REST or JSON APIs.
Opt for FuseConfig::strict() or explicitly configure idle, write-stall, and request body timeouts when serving applications that do not require long-lived streaming.
Load Cryptographic Secret Keys and Session Secrets Securely at Runtime
Approximately 213 tokens
Use when
When configuring authentication, session handlers, JWT decoders, or encrypted keys in Salvo applications.
Secure rules
Rule 1: Use a cryptographically random session secret of at least 64 bytes
Generate the secret passed to SessionHandler with a cryptographically secure random number generator. Salvo requires this secret to contain at least 64 bytes and uses it to derive the key that signs and verifies session cookies.
Halt Execution on Authentication Failure in Custom Salvo Middleware
Approximately 229 tokens
Use when
When writing custom authentication handlers or middleware in Salvo that need to reject unauthorized requests and prevent downstream handler execution.
Secure rules
Rule 1: Call skip_rest() on FlowCtrl when an authentication failure occurs to halt the execution chain.
When writing custom authentication handlers or middleware in Salvo, you must explicitly halt the routing execution chain on failure. Always retrieve the FlowCtrl parameter in your handler and call ctrl.skip_rest() immediately after setting the unauthorized response status to ensure Salvo does not continue executing downstream handlers in the routing table.
Force Secure Cookie Attributes for Sessions Behind Upstream TLS Termination
Approximately 248 tokens
Use when
Configuring session handling in Salvo applications deployed behind an upstream proxy or load balancer that terminates TLS.
Secure rules
Rule 1: Explicitly force the secure attribute on session cookies when TLS is terminated upstream.
By default, SessionHandler automatically detects whether to append the Secure flag based on the request URI scheme. When deployed behind an upstream proxy that terminates TLS, the local request may appear as unencrypted HTTP, causing Salvo to omit the Secure attribute. To prevent session cookies from being transmitted over unencrypted HTTP channels, explicitly configure the session handler builder with secure_cookie(true).
use salvo::session::SessionHandler;use saysion::MemoryStore;let store = MemoryStore::new();let secret = [0u8; 64];let handler = SessionHandler::builder(store, &secret) .secure_cookie(true) .build() .unwrap();