VNX-RUST-008 – Path Traversal in Actix-web or Axum File-Serving Handler
Overview
This rule flags Rust web handlers (using Actix-web or Axum) that construct a filesystem path by joining a base directory with a value extracted from a URL path parameter, without subsequently calling canonicalize() and verifying that the result is a child of the expected base directory. When a user-supplied path component is joined directly, an attacker can embed ../ sequences to escape the intended directory and request arbitrary files from the server’s filesystem.
The rule detects this pattern by checking for path-construction calls (PathBuf::new, .join(), path::Path::new) within a code window that also references Actix-web or Axum path extraction APIs (Path(, axum::extract::Path, params.get(, etc.), and that does not already contain a canonicalize() call or a starts_with() check to confine the path.
This rule corresponds to CWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’).
Severity: High | CWE: CWE-22 – Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)
Why This Matters
Path traversal vulnerabilities in file-serving endpoints are straightforward to exploit and can have severe consequences. An attacker does not need to compromise the server or find an authentication bypass — they simply send a crafted URL to an endpoint that was intended for legitimate file access.
A typical attack begins with the attacker sending a request like GET /files/../../../../etc/passwd or the URL-encoded equivalent %2e%2e%2f sequences. If the handler blindly joins the base directory with this value, the resolved path escapes the intended directory. On a Linux server, common high-value targets include /etc/passwd, /etc/shadow, application configuration files containing database credentials, private TLS key files, and cloud instance metadata accessible via well-known paths.
In microservice architectures, the file server may run alongside services whose configuration files contain secrets for databases, message queues, or cloud providers. A path traversal vulnerability in the file-serving handler can expose the credentials of entirely unrelated services on the same host.
What Gets Flagged
The rule detects .rs files where path-joining operations appear within approximately 15 lines of Actix-web or Axum path parameter extraction, without a subsequent canonicalization and prefix check.
// FLAGGED: user path joined directly, no canonicalization
async fn serve_file(Path(filename): Path<String>) -> impl Responder {
let base = PathBuf::from("/var/www/files");
let path = base.join(&filename); // ../../etc/passwd escapes base
match tokio::fs::read(&path).await {
Ok(data) => HttpResponse::Ok().body(data),
Err(_) => HttpResponse::NotFound().finish(),
}
}
// FLAGGED: Axum handler with unvalidated path join
async fn download(axum::extract::Path(name): axum::extract::Path<String>) -> Vec<u8> {
let p = std::path::Path::new("/srv/data").join(&name);
tokio::fs::read(p).await.unwrap()
}
Remediation
- Canonicalize the resolved path and verify it starts with the base directory.
canonicalize()resolves all..components and symlinks, andstarts_with()then confirms the result is within the intended tree:
// SAFE: canonicalize + starts_with prevents traversal
async fn serve_file(Path(filename): Path<String>) -> impl Responder {
let base = PathBuf::from("/var/www/files").canonicalize().unwrap();
let requested = base.join(&filename);
let resolved = match requested.canonicalize() {
Ok(p) => p,
Err(_) => return HttpResponse::NotFound().finish(),
};
if !resolved.starts_with(&base) {
return HttpResponse::Forbidden().finish();
}
match tokio::fs::read(&resolved).await {
Ok(data) => HttpResponse::Ok().body(data),
Err(_) => HttpResponse::NotFound().finish(),
}
}
- Strip or reject
..components before joining. For simpler cases, reject any filename that contains a path separator or..:
// SAFE: reject filenames with path components
async fn serve_file(Path(filename): Path<String>) -> impl Responder {
if filename.contains('/') || filename.contains("..") {
return HttpResponse::BadRequest().finish();
}
let path = PathBuf::from("/var/www/files").join(&filename);
// ...
}
- Serve files from an index rather than a direct path. Map user-visible identifiers to server-side paths in a database or in-memory map, eliminating the need to accept arbitrary filesystem paths from users entirely.