Tar Extract Path Guard

Validate archive entry names before extraction, blocking absolute paths, parent traversal, and anything outside the destination.

RustSecurity Boundaries

Code

snippet
use std::path::{Component, Path, PathBuf};

fn safe_entry_path(destination: &Path, entry_name: &str) -> Result<PathBuf, String> {
    let entry = Path::new(entry_name);
    if entry.is_absolute() {
        return Err(format!("absolute entry rejected: {entry_name}"));
    }

    let mut output = destination.to_path_buf();
    let mut depth = 0usize;
    for component in entry.components() {
        match component {
            Component::Normal(part) => {
                output.push(part);
                depth += 1;
            }
            Component::CurDir => {}
            Component::ParentDir => {
                if depth == 0 {
                    return Err(format!("entry escapes destination: {entry_name}"));
                }
                output.pop();
                depth -= 1;
            }
            _ => return Err(format!("unsupported component in {entry_name}")),
        }
    }

    if !output.starts_with(destination) {
        return Err(format!("resolved entry escapes destination: {entry_name}"));
    }
    Ok(output)
}

fn main() {
    let destination = Path::new("/var/lib/app/uploads");
    let entries = [
        "reports/2026.csv",
        "docs/../notes.txt",
        "../etc/cron.d/job",
        "/etc/passwd",
        "a/../../b",
    ];
    for entry in entries {
        match safe_entry_path(destination, entry) {
            Ok(path) => println!("ok   {entry:<22} => {}", path.display()),
            Err(error) => println!("skip {entry:<22} => {error}"),
        }
    }
}