Stage 6: Ecosystem Integration
"Compose strict-path with ecosystem tools — security primitives, not wrappers."
You've mastered the core concepts: boundaries, markers, authorization, and virtual paths. Now you'll learn how to integrate strict-path with popular Rust ecosystem crates for real-world applications.
The Philosophy
strict-path provides security primitives. You compose them with ecosystem tools.
We don't wrap external crates behind feature flags. Instead, we show you how to use them together effectively — giving you full control and explicit security.
Quick Integration Examples
Temporary Directories (tempfile)
#![allow(unused)] fn main() { use strict_path::PathBoundary; use tempfile::TempDir; fn process_upload() -> Result<(), Box<dyn std::error::Error>> { // Create temp directory with RAII cleanup let temp_dir = tempfile::tempdir()?; // Establish strict boundary let upload_boundary = PathBoundary::try_new(temp_dir.path())?; // Safe operations within boundary let user_file = upload_boundary.strict_join("user/data.txt")?; user_file.create_parent_dir_all()?; user_file.write(b"uploaded content")?; // Escape attempts are blocked match upload_boundary.strict_join("../../etc/passwd") { Ok(_) => panic!("Should not escape!"), Err(e) => println!("✓ Attack blocked: {}", e), } Ok(()) // temp_dir automatically deleted when dropped } }
Why this works: RAII cleanup from tempfile, security from strict-path.
Portable App Paths (app-path)
#![allow(unused)] fn main() { use strict_path::PathBoundary; use app_path::AppPath; fn setup_portable_app() -> Result<(), Box<dyn std::error::Error>> { // Executable-relative path let app_path = AppPath::new("MyPortableApp"); let app_dir = app_path.get_app_dir(); // Establish boundary let boundary = PathBoundary::try_new_create(&app_dir)?; // Safe config access let config = boundary.strict_join("config/settings.ini")?; config.create_parent_dir_all()?; config.write(b"[settings]\nportable=true\n")?; println!("App directory: {}", boundary.strictpath_display()); Ok(()) } }
Use cases: USB drives, CI/CD, containers with custom paths.
OS Directories (dirs)
#![allow(unused)] fn main() { use strict_path::PathBoundary; fn setup_config() -> Result<(), Box<dyn std::error::Error>> { // Platform-specific config directory let config_base = dirs::config_dir() .ok_or("No config directory")?; // App-specific subdirectory boundary let app_config = config_base.join("myapp"); let boundary = PathBoundary::try_new_create(&app_config)?; // Cross-platform locations: // Linux: ~/.config/myapp/ // Windows: C:\Users\Alice\AppData\Roaming\myapp\ // macOS: ~/Library/Application Support/myapp/ let settings = boundary.strict_join("settings.toml")?; settings.write(b"[app]\nversion = '1.0'\n")?; Ok(()) } }
Serialization (serde via FromStr)
PathBoundary and VirtualRoot implement FromStr, so they deserialize automatically:
#![allow(unused)] fn main() { use strict_path::PathBoundary; use serde::Deserialize; #[derive(Deserialize)] struct AppConfig { // Deserializes via FromStr automatically upload_dir: PathBoundary, data_dir: PathBoundary, // Validate these manually user_paths: Vec<String>, } fn load_config() -> Result<(), Box<dyn std::error::Error>> { let json = r#"{ "upload_dir": "./uploads", "data_dir": "./data", "user_paths": ["file1.txt", "../../etc/passwd"] }"#; let config: AppConfig = serde_json::from_str(json)?; // Boundaries are ready to use for path_str in &config.user_paths { match config.upload_dir.strict_join(path_str) { Ok(safe_path) => { safe_path.write(b"uploaded")?; println!("✓ {}", safe_path.strictpath_display()); } Err(e) => { eprintln!("✗ Blocked '{}': {}", path_str, e); } } } Ok(()) } }
Key insight: Explicit validation is a feature, not a bug. Security operations should be visible.
Real-World Application
Combining all integrations:
#![allow(unused)] fn main() { use strict_path::{PathBoundary, VirtualRoot}; use tempfile::TempDir; use serde::Deserialize; struct AppConfig; struct UserFiles; struct TempProcessing; #[derive(Deserialize)] struct Config { config_dir: PathBoundary<AppConfig>, } struct Application { config: PathBoundary<AppConfig>, user_root: VirtualRoot<UserFiles>, } impl Application { fn new(user_id: u64) -> Result<Self, Box<dyn std::error::Error>> { // OS-specific config let config_base = dirs::config_dir() .ok_or("No config directory")?; let config = PathBoundary::try_new_create( config_base.join("myapp") )?; // Per-user virtual root let user_storage = format!("users/user_{}", user_id); let user_root = VirtualRoot::try_new_create(user_storage)?; Ok(Self { config, user_root }) } fn process_file(&self, filename: &str) -> Result<String, Box<dyn std::error::Error>> { // Temp directory for processing let temp = TempDir::new()?; let temp_dir: PathBoundary<TempProcessing> = PathBoundary::try_new(temp.path())?; // Get user file (virtual path) let user_file = self.user_root.virtual_join(filename)?; let data = user_file.read()?; // Process in temp let temp_file = temp_dir.strict_join("processing.tmp")?; temp_file.write(&data)?; Ok(format!("Processed {} bytes", data.len())) // temp auto-cleaned } } }
Why No Feature Flags?
Previous approach (deprecated):
strict-path = { version = "0.1", features = ["tempfile", "dirs", "app-path", "serde"] }
New approach (recommended):
strict-path = "0.1"
tempfile = "3.0"
dirs = "5.0"
app-path = "1.0"
Benefits:
- ✅ Full control - Access all options of external crates
- ✅ No version coupling - Use any version you want
- ✅ Explicit dependencies - Clear what you're using
- ✅ Reduced bloat - Pay only for what you import
- ✅ Visible security - Validation is explicit in code
Trade-off: One extra line of code for explicit, secure integration.
Complete Integration Guide
For comprehensive examples, patterns, and best practices, see:
📚 Ecosystem Integration Guide →
This guide covers:
- Temporary directories with RAII cleanup
- Portable application paths with env overrides
- OS standard directories (cross-platform)
- Serialization/deserialization patterns
- Multi-directory application architecture
- Web API integration examples
Key Takeaways
✅ Compose, don't wrap — Use ecosystem crates directly ✅ Explicit validation — Security operations are visible ✅ Full control — No feature coupling or version constraints ✅ One extra line — Small cost for explicit security ✅ FromStr support — Boundaries deserialize automatically
The Final Complete Guarantee
By combining all stages, you achieve:
- ✅ Paths cannot escape boundaries (Stage 1)
- ✅ Paths are in the correct domain (Stage 3)
- ✅ Authorization proven by compiler (Stage 4)
- ✅ Clean virtual UX for users (Stage 5)
- ✅ Ecosystem integration with safety (Stage 6)
All enforced at compile time with zero runtime overhead.
Congratulations! 🎉
You've completed the full tutorial! You now understand:
- ✅ How
StrictPathprevents path escapes - ✅ How markers prevent domain mix-ups
- ✅ How
change_marker()encodes authorization - ✅ How
VirtualPathprovides user-friendly sandboxing - ✅ How to integrate with the Rust ecosystem
What's Next?
Explore these resources to deepen your knowledge:
- Ecosystem Integration Guide — Comprehensive integration patterns
- Real-World Examples — Copy-pasteable patterns for web servers, CLI tools, archives
- Best Practices — Decision matrices, design patterns, and guidelines
- Anti-Patterns — Common mistakes and how to fix them
- Axum Tutorial — Build a complete web service with strict-path
You're ready to build secure systems! 🚀
Quick Reference Card:
#![allow(unused)] fn main() { // Temporary directories let temp = tempfile::tempdir()?; let boundary = PathBoundary::try_new(temp.path())?; // Portable app paths let app_dir = app_path::AppPath::new("MyApp").get_app_dir(); let boundary = PathBoundary::try_new(&app_dir)?; // OS directories let config = dirs::config_dir().ok_or("No config")?; let boundary = PathBoundary::try_new_create(config.join("myapp"))?; // Deserialization (FromStr) #[derive(Deserialize)] struct Config { boundary: PathBoundary, // Automatic via FromStr user_path: String, // Manual validation } }