#smartcontracts

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.

Red Asgard Security Team
October 26, 2025
rustsecuritypath-traversalfilesystemvalidationopen-source

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.

Need Security Help?

Our team can help secure your blockchain, web applications, and infrastructure.