use crate::{PathBoundary, StrictPathError};
use std::path::Path;
#[test]
fn test_strict_path_core_attack_patterns() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let attack_patterns: &[&str] = &[
"../../../../etc/passwd",
"..\\..\\..\\..\\windows\\system32\\config\\sam",
"..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..",
"../../../../../../proc/self/environ",
"../../../var/log/auth.log",
"....//....//....//etc/shadow",
"..%2F..%2F..%2Fetc%2Fpasswd",
"file:///etc/passwd",
"\\\\server\\share\\sensitive.txt",
"../.env",
"../../config/database.yml",
];
for attack_input in attack_patterns {
let candidate = Path::new(attack_input);
let has_parent = candidate
.components()
.any(|c| matches!(c, std::path::Component::ParentDir));
let is_absolute = candidate.is_absolute() || attack_input.starts_with("file://");
match restriction.strict_join(attack_input) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Attack pattern '{attack_input}' escaped boundary: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
assert!(
has_parent || is_absolute,
"Unexpected rejection for benign pattern '{attack_input}'"
);
}
Err(other) => panic!("Unexpected error for '{attack_input}': {other:?}"),
}
}
}
#[test]
fn test_strict_path_null_byte_truncation_attack() {
use std::ffi::{OsStr, OsString};
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let truncation_payloads: Vec<OsString> = vec![
{
let mut s = OsString::from("../../../etc/passwd");
s.push(OsStr::new("\0"));
s.push(OsStr::new(".jpg"));
s
},
{
let mut s = OsString::from("safe_file.txt");
s.push(OsStr::new("\0"));
s.push(OsStr::new("/../../../etc/shadow"));
s
},
{
let mut s = OsString::from("uploads/avatar");
s.push(OsStr::new("\0"));
s.push(OsStr::new(".png"));
s
},
];
for attack_input in &truncation_payloads {
match restriction.strict_join(attack_input) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Null truncation attack escaped boundary: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
}
Err(other) => {
panic!("Unexpected error for null truncation payload: {other:?}");
}
}
}
}
#[test]
fn test_strict_path_empty_and_whitespace_segments() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let edge_case_inputs: &[&str] = &[
"",
" ",
" ",
"path//to//file",
"///",
"path/ /to/file",
"path/./to/./file",
"./././.",
"data/",
".",
"..",
];
for attack_input in edge_case_inputs {
match restriction.strict_join(attack_input) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Edge case '{attack_input}' escaped boundary: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
}
Err(other) => panic!("Unexpected error for '{attack_input}': {other:?}"),
}
}
}
#[test]
#[cfg(windows)]
fn test_strict_path_windows_device_paths_rejected() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let device_paths: &[&str] = &[
r"\\.\PhysicalDrive0",
r"\\.\CONIN$",
r"\\.\CONOUT$",
r"\\.\pipe\name",
r"\\.\COM1",
r"\\.\PHYSICALDRIVE0",
r"\\?\GLOBALROOT\Device\HarddiskVolume1",
r"\\?\GLOBALROOT\Device\Null",
r"\\.\Volume{GUID}",
];
for device_path in device_paths {
let result = restriction.strict_join(device_path);
match result {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Device path '{device_path}' escaped boundary: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
}
Err(other) => {
panic!("Unexpected error for device path '{device_path}': {other:?}");
}
}
}
}
#[test]
#[cfg(windows)]
fn test_strict_path_windows_reserved_names_in_subdirs() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new_create(temp.path()).unwrap();
let sub = restriction.strict_join("subdir").unwrap();
sub.create_dir_all().unwrap();
let reserved_in_subdirs: &[&str] = &[
"subdir/CON",
"subdir/NUL",
"subdir/AUX",
"subdir/PRN",
"subdir/COM1",
"subdir/LPT1",
"subdir/CON.txt",
"subdir/NUL.txt",
"subdir/COM1.log",
];
for reserved_path in reserved_in_subdirs {
match restriction.strict_join(reserved_path) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Reserved name '{reserved_path}' escaped boundary: {validated_path:?}"
);
}
Err(_) => {
}
}
}
}
#[test]
#[cfg(unix)]
fn test_strict_path_unix_backslash_literal_filename() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let backslash_paths: &[&str] = &[r"..\..\etc\passwd", r"..\secret.txt", r"data\..\config"];
for attack_input in backslash_paths {
match restriction.strict_join(attack_input) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Backslash path '{attack_input}' escaped boundary on Unix: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
}
Err(other) => panic!("Unexpected error for '{attack_input}': {other:?}"),
}
}
}
#[test]
fn test_strict_path_double_url_encoding_containment() {
let temp = tempfile::tempdir().unwrap();
let restriction: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let double_encoded_attacks: &[&str] = &[
"%252e%252e%252f%252e%252e%252fetc%252fpasswd",
"%252e%252e%255c%252e%252e%255cWindows%255cSystem32",
"%25252e%25252e%25252f",
"%252e%252e/%2e%2e/etc/passwd",
];
for attack_input in double_encoded_attacks {
match restriction.strict_join(attack_input) {
Ok(validated_path) => {
assert!(
validated_path.strictpath_starts_with(restriction.interop_path()),
"Double-encoded attack '{attack_input}' escaped boundary: {validated_path:?}"
);
}
Err(StrictPathError::PathEscapesBoundary { .. })
| Err(StrictPathError::PathResolutionError { .. }) => {
}
Err(other) => {
panic!("Unexpected error for double-encoded '{attack_input}': {other:?}");
}
}
}
}