A secure path management library for .NET to prevent directory traversal and symlink jailbreak attacks.
$ dotnet add package Owasp.Untrust.BoxedPathA secure path management library for .NET designed to prevent Directory Traversal (Path Traversal) and Symbolic Link (Symlink) Jailbreak attacks. It provides a robust "sandbox" mechanism to ensure that all file system operations remain strictly within a defined root directory.
Modern .NET applications frequently interact with the file system. However, accepting file paths from untrusted sources (user input, uploads, configuration files) introduces critical security risks:
../ sequences to access sensitive system files (e.g., ../../windows/system32/config/SAM).Standard .NET classes like System.IO.Path provide lexical manipulation but do not enforce security boundaries or handle advanced symlink resolution securely.
Owasp.Untrust.BoxedPath introduces two core concepts:
PathSandbox: Defines the secure root directory and the security policy (e.g., whether symlinks are allowed).BoxedPath: An immutable, secure wrapper around a file path. It guarantees that the path it holds has been validated to be inside its associated sandbox.All operations on BoxedPath (like Combine, GetParent) return a new, validated BoxedPath or throw a SecurityException if the operation would result in a path outside the sandbox.
ResolveAndValidatePath) that physically resolves symlinks to prevent jailbreaks.System.IO.Path and FileInfo APIs for ease of adoption.BoxedFileInfo, BoxedDirectoryInfo, and BoxedFileStream to perform I/O operations safely without exposing the raw path.Install via NuGet:
dotnet add package Owasp.Untrust.BoxedPath
Create a PathSandbox instance to define your secure root.
using Owasp.Untrust.BoxedPaths; // note plural Path_s_
// Define a sandbox rooted at "C:\Safe\Uploads"
// Default policy: DISALLOW (Symlinks are followed but must stay inside the sandbox)
PathSandbox sandbox = PathSandbox.BoxRoot(@"C:\Safe\Uploads");
Use the Of() factory method to create a BoxedPath from an untrusted string.
try
{
// User input: "user_data.txt" (Safe)
BoxedPath safePath = BoxedPath.Of(sandbox, "user_data.txt");
// User input: "../../../windows/system.ini" (Malicious)
// THROWS SecurityException immediately!
BoxedPath maliciousPath = BoxedPath.Of(sandbox, "../../../windows/system.ini");
}
catch (SecurityException ex)
{
Console.WriteLine($"Attack blocked: {ex.Message}");
}
Combine paths securely. The library ensures the result is still valid.
BoxedPath basePath = BoxedPath.Of(sandbox, "users");
// Safe combination: C:\Safe\Uploads\users\alice
BoxedPath userPath = BoxedPath.Combine(basePath, "alice");
// Malicious combination attempt
// THROWS SecurityException
BoxedPath hackAttempt = BoxedPath.Combine(basePath, "../../admin");
Crucial: Do not convert the BoxedPath to a string to use standard File methods directly, as that breaks the chain of trust. Instead, use the secure wrappers or the ValidateAndExpose() method at the very last moment.
// Check existence safely
if (BoxedFile.Exists(safePath))
{
// Read text securely
string content = BoxedFile.ReadAllText(safePath);
}
// Use FileInfo wrapper (Does NOT expose .FullName)
var fileInfo = new BoxedFileInfo(safePath);
long size = fileInfo.Length;
// Secure FileStream
using (var stream = new BoxedFileStream(safePath, FileMode.Open))
{
// ... read/write ...
}
If you must use an API that strictly requires a string path:
// ValidateAndExpose either relies on previous validation (for an absolute path) or validates the path.
// The raw physical path string is returned.
// Use this result IMMEDIATELY and do not store it.
string rawPath = safePath.ValidateAndExpose();
System.IO.File.Delete(rawPath); // Note that this is only for example purposes! Better use BoxedFile.Delete(safePath);
You can control how symbolic links are handled.
// Default: Follows symlinks, but throws if the target is outside the sandbox.
var secureSandbox = PathSandbox.BoxRoot("/data", SandboxJailbreak.DISALLOW);
// Dangerous: Allows symlinks to point anywhere (relies on OS permissions).
var looseSandbox = PathSandbox.BoxRoot("/data", SandboxJailbreak.UNCHECKED_SYMLINKS);
To prevent infinite loops or Denial of Service (DoS) via "symlink bombs," you can limit the recursion depth.
// Limit to 20 link hops (Default is 5)
var deepSandbox = PathSandbox.BoxRoot("/data", SandboxJailbreak.DISALLOW, maxLinkFollows: 20);
This project is an official OWASP contribution. Issues and Pull Requests are welcome on the GitHub Repository.
Licensed under the Apache License 2.0.