path-security: 85%+ Attack Vector Coverage for Path Traversal Prevention
Introducing path-security, Red Asgard's comprehensive Rust library defending against 85+ path traversal attack vectors including unicode, encoding, and exotic bypasses.
path-security: 85%+ Attack Vector Coverage for Path Traversal Prevention
Path traversal vulnerabilities remain one of the most common security issues in web applications. Today, Red Asgard releases path-security, a battle-tested Rust crate providing comprehensive defense against modern path traversal attacks.
Package: path-security on crates.io
Repository: github.com/redasgard/path-security
License: MIT
Version: 0.2.0
Downloads: 181+
Why Another Path Security Library?
Existing libraries typically check for basic ../ patterns. Modern attackers use far more sophisticated techniques:
85+ Attack Vectors Defended:
- URL encoding bypasses (
%2e%2e%2f, double encoding) - Unicode directory separators (U+2044, U+2215, U+FF0F)
- Null byte injection (
%00) - Windows reserved names (CON, PRN, AUX)
- Overlong UTF-8 encoding
- Zero-width character injection
- Homograph attacks
- And 75+ more variants
Our library was built from real-world audit findings across hundreds of applications.
Core Features
1. Comprehensive Validation
use path_security::PathValidator;
let validator = PathValidator::new();
// Test various inputs
let safe = validator.validate("/blog/my-post")?; // OK
let blocked = validator.validate("/blog/../.env"); // Error
let encoded = validator.validate("/blog/..%2F..%2Fconfig"); // Error
2. Multi-Layer Defense
pub struct PathValidator {
// Layer 1: Pattern detection (85+ patterns)
patterns: Vec<TraversalPattern>,
// Layer 2: Character whitelist
allowed_chars: CharSet,
// Layer 3: Path resolution verification
base_directory: PathBuf,
// Layer 4: Unicode normalization
unicode_mode: UnicodeNormalization,
}
3. Configurable Security Levels
use path_security::{PathValidator, SecurityLevel};
// Strict: Block anything suspicious (recommended for file access)
let strict = PathValidator::with_level(SecurityLevel::Strict);
// Moderate: Allow common patterns, block dangerous ones
let moderate = PathValidator::with_level(SecurityLevel::Moderate);
// Paranoid: Maximum protection, may have false positives
let paranoid = PathValidator::with_level(SecurityLevel::Paranoid);
Attack Vectors Covered
Basic Traversal
../- Parent directory../../- Multiple levels..\- Windows backslash- Absolute paths (
/etc/passwd)
URL Encoding
%2e%2e%2f- URL encoded../%252e%252e%252f- Double URL encoding%2E%2E%2F- Mixed case encoding%c0%ae- Overlong UTF-8 encoding
Unicode Attacks
\u2044- Fraction slash\u2215- Division slash\uff0f- Fullwidth solidus\uff3c- Fullwidth reverse solidus- Unicode normalization bypasses
Exotic Techniques
- Null byte injection (
file%00.txt) - Zero-width characters (
\u200b,\ufeff) - Windows reserved names (CON, PRN, AUX, NUL)
- Semicolon separators (
..;/) - Triple dots (
...) - Homograph attacks (Cyrillic lookalikes)
Edge Cases
- Empty strings
- Very long paths (buffer overflow)
- Trailing/leading spaces
- Mixed separators
- Case sensitivity tricks
Real-World Examples
Web Application File Access
use path_security::PathValidator;
use std::fs;
fn serve_user_file(user_path: &str) -> Result<Vec<u8>> {
let validator = PathValidator::with_base_dir("/var/www/uploads")?;
// Validate and get safe path
let safe_path = validator.validate_and_resolve(user_path)?;
// Read file (guaranteed to be in /var/www/uploads)
let content = fs::read(safe_path)?;
Ok(content)
}
// Usage
serve_user_file("document.pdf")?; // OK
serve_user_file("../../../etc/passwd")?; // Error: Path traversal blocked
API File Upload
use path_security::PathValidator;
async fn handle_upload(filename: &str, content: &[u8]) -> Result<()> {
let validator = PathValidator::with_base_dir("./uploads")?
.with_allowed_extensions(&["pdf", "jpg", "png"])?
.with_max_path_length(255)?;
let safe_filename = validator.sanitize_filename(filename)?;
let safe_path = validator.validate_and_resolve(&safe_filename)?;
tokio::fs::write(safe_path, content).await?;
Ok(())
}
Content Management System
use path_security::PathValidator;
struct BlogSystem {
validator: PathValidator,
}
impl BlogSystem {
fn new() -> Result<Self> {
Ok(Self {
validator: PathValidator::with_base_dir("./content/blog")?
.with_allowed_pattern(r"^[a-z0-9\-]+$")?
.strict_mode(true)?
})
}
fn get_post(&self, slug: &str) -> Result<BlogPost> {
let safe_path = self.validator.validate_and_resolve(slug)?;
let content = std::fs::read_to_string(safe_path)?;
Ok(BlogPost::parse(&content)?)
}
}
Configuration Options
use path_security::{PathValidator, Config};
let validator = PathValidator::with_config(Config {
// Base directory (all paths must be within this)
base_directory: "/var/www/data",
// Security level
security_level: SecurityLevel::Strict,
// Unicode handling
unicode_normalization: true,
unicode_form: UnicodeForm::NFC,
// Path constraints
max_path_length: 4096,
max_depth: 10,
allow_symlinks: false,
allow_hidden_files: false,
// Character restrictions
allowed_extensions: Some(vec!["md", "txt", "pdf"]),
blocked_extensions: vec!["exe", "sh", "bat"],
// Platform-specific
check_windows_reserved: true,
case_sensitive: cfg!(unix),
// Validation strictness
reject_on_sanitization: true,
fail_safe: true,
})?;
Performance
Optimized for high-throughput applications:
Benchmark Results:
Simple validation: 0.03ms (30 ?s)
Complex validation: 0.15ms (150 ?s)
With normalization: 0.25ms (250 ?s)
Pattern matching (85): 0.08ms (80 ?s)
Path resolution: 0.05ms (50 ?s)
Throughput:
- 60,000+ validations/second/core
- Zero allocations for valid paths
- Minimal memory footprint (~2KB/validator)
Attack Detection & Logging
use path_security::{PathValidator, AttackLog};
let validator = PathValidator::new()
.with_attack_logging(true)?;
match validator.validate(user_input) {
Err(e) if e.is_attack() => {
let attack = e.as_attack_info();
log::warn!(
"Path traversal attempt detected: {:?}, vector: {}, IP: {}",
attack.input,
attack.vector_type,
client_ip
);
// Optional: Add to WAF/rate limiter
firewall.block_ip(client_ip, Duration::hours(24))?;
}
Err(e) => return Err(e),
Ok(safe_path) => process(safe_path),
}
Integration Examples
Actix Web
use actix_web::{web, HttpResponse};
use path_security::PathValidator;
async fn download_file(
path: web::Path<String>,
validator: web::Data<PathValidator>
) -> Result<HttpResponse> {
let safe_path = validator.validate(&path)?;
let file = tokio::fs::read(safe_path).await?;
Ok(HttpResponse::Ok()
.content_type("application/octet-stream")
.body(file))
}
Axum
use axum::{extract::Path, http::StatusCode};
use path_security::PathValidator;
async fn get_document(
Path(filename): Path<String>,
) -> Result<Vec<u8>, StatusCode> {
let validator = PathValidator::with_base_dir("/docs")?;
let safe_path = validator
.validate_and_resolve(&filename)
.map_err(|_| StatusCode::BAD_REQUEST)?;
tokio::fs::read(safe_path)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
Rocket
use rocket::State;
use path_security::PathValidator;
#[get("/files/<filename>")]
fn download(
filename: String,
validator: &State<PathValidator>
) -> Option<Vec<u8>> {
let safe_path = validator.validate(&filename).ok()?;
std::fs::read(safe_path).ok()
}
Testing Your Application
The library includes a test suite generator:
use path_security::testing::AttackVectorGenerator;
#[cfg(test)]
mod security_tests {
use super::*;
#[test]
fn test_all_attack_vectors() {
let generator = AttackVectorGenerator::new();
let validator = PathValidator::new();
for vector in generator.all_vectors() {
let result = validator.validate(&vector.payload);
assert!(
result.is_err(),
"Attack vector not blocked: {:?}",
vector
);
}
}
}
Technical Implementation
Pattern Detection Engine
// Simplified internal structure
pub struct PatternMatcher {
// Compiled regex patterns
regex_patterns: Vec<Regex>,
// String patterns (faster for exact matches)
string_patterns: Vec<&'static str>,
// Unicode patterns
unicode_patterns: Vec<UnicodePattern>,
}
impl PatternMatcher {
pub fn check(&self, input: &str) -> Option<AttackVector> {
// Fast path: check common patterns first
for pattern in &self.string_patterns {
if input.contains(pattern) {
return Some(AttackVector::from_pattern(pattern));
}
}
// Slower: regex matching
for (i, regex) in self.regex_patterns.iter().enumerate() {
if regex.is_match(input) {
return Some(AttackVector::from_regex(i));
}
}
None
}
}
Path Resolution
pub fn resolve_and_verify(
&self,
input: &str,
base: &Path
) -> Result<PathBuf> {
// 1. Join paths
let joined = base.join(input);
// 2. Canonicalize (resolve symlinks, .., .)
let canonical = joined.canonicalize()
.map_err(|_| Error::PathNotFound)?;
// 3. Verify containment
let base_canonical = base.canonicalize()?;
if !canonical.starts_with(&base_canonical) {
return Err(Error::PathEscape {
attempted: canonical,
allowed_base: base_canonical,
});
}
Ok(canonical)
}
Get Involved
Install:
cargo add path-security
Report Issues: GitHub Issues
Contribute: We especially welcome:
- New attack vector discoveries
- Platform-specific test cases
- Performance improvements
- Integration examples
Professional Support
Using path-security in production?
Red Asgard offers:
- Security audits for your implementation
- Custom rule development
- Integration consulting
- Priority support contracts
Contact us for enterprise support
Released: October 24, 2025
Version: 0.2.0
Coverage: 85+ attack vectors
False positive rate: <1%
Share this article
Help spread the word about security best practices.
Related Articles
Balancer V2 Vault Exploit: Forensic Analysis of $117M Breach
Preliminary forensic analysis of the Nov 3, 2025 Balancer V2 exploit with on-chain verification and a per-chain verification plan. Official disclosure pending.
threat-intel: Comprehensive Threat Intelligence Framework for Rust
Red Asgard's threat-intel provides multi-source threat aggregation, CVE integration, and automated risk assessment for security operations teams.
Need Security Help?
Our team can help secure your blockchain, web applications, and infrastructure.