Skip to content

DK26/soft-canonicalize-rs

Repository files navigation

soft-canonicalize

Crates.io Documentation License Build Status

A pure Rust library for path canonicalization that works with non-existing paths.

Unlike std::fs::canonicalize(), this library resolves and normalizes paths even when components don't exist on the filesystem. Useful for security validation, path preprocessing, and working with paths before file creation.

Comprehensive test suite with 51 tests ensuring 100% behavioral compatibility with std::fs::canonicalize for existing paths.

Inspired by Python's pathlib.Path.resolve() behavior.

Features

  • 🚀 Works with non-existing paths: Canonicalizes paths that don't exist yet
  • 🌍 Cross-platform: Windows, macOS, and Linux support
  • ⚡ Zero dependencies: Only uses std library
  • 🔒 Security focused: Proper .. and symlink handling
  • 🧮 Pure algorithm: No filesystem modification

Quick Start

Add this to your Cargo.toml:

[dependencies]
soft-canonicalize = "0.1.2"

Basic Usage

use soft_canonicalize::soft_canonicalize;

// Works even if file doesn't exist!
let user_path = soft_canonicalize("../../../etc/passwd")?;

let jail_path = std::fs::canonicalize("/safe/jail/dir").expect("Jail directory must exist");

let is_safe = user_path.starts_with(&jail_path); // false - attack blocked!

Use Cases

  • Web servers: Path validation before file creation
  • Build tools: Resolving non-existing output paths
  • Security applications: Safe path handling with symlink resolution

How It Works

  1. Lexical Resolution: Process .. and . components without filesystem access
  2. Incremental Symlink Resolution: Resolve symlinks as encountered using std::fs::canonicalize
  3. Hybrid Approach: Uses std::fs::canonicalize for existing path portions, lexical resolution for non-existing parts
  4. Optimized Access: Only check filesystem when components exist

Implementation: Finds the longest existing path prefix, canonicalizes it with std::fs::canonicalize, then appends the remaining non-existing components. This ensures you get the same results as the standard library for existing paths, with extended support for non-existing paths.

Performance & Compatibility

  • Time: O(n) path components
  • Space: O(n) component storage
  • Cross-platform: Windows (drive letters, UNC), Unix (symlinks)
  • Comprehensive Testing: 51 tests including Python-inspired edge cases and cross-platform validation
  • 100% Behavioral Compatibility: Passes all std::fs::canonicalize tests for existing paths

Security

  • Directory Traversal Prevention: .. components resolved before filesystem access
  • Symlink Resolution: Existing symlinks properly resolved
  • No Side Effects: No temporary files created
  • Path Injection Protection: Handles various path formats safely

Security Advantage: Resolves symlinks, preventing jail break attacks where malicious symlinks point outside intended boundaries (unlike path_absolutize).

Comparison with Alternatives

Use Case soft_canonicalize std::fs::canonicalize dunce::canonicalize normpath::normalize path_absolutize::absolutize jailed-path*
Works with non-existing paths
Resolves symlinks
Zero dependencies ❌ (uses this crate)
Handles .. components ✅ (resolves) ✅ (resolves) ✅ (resolves) ✅ (resolves) ✅ (safe with virtual root) ❌ (rejects)
Prevents symlink jail breaks N/A ❌ (vulnerable)
Built-in path jailing ✅ (virtual root)

*jailed-path uses soft_canonicalize as a dependency.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

License

Licensed under either of:

Changelog

See CHANGELOG.md for a detailed history of changes.