Nullean.ScopedFileSystem 0.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Nullean.ScopedFileSystem --version 0.3.0
                    
NuGet\Install-Package Nullean.ScopedFileSystem -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nullean.ScopedFileSystem" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nullean.ScopedFileSystem" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Nullean.ScopedFileSystem" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nullean.ScopedFileSystem --version 0.3.0
                    
#r "nuget: Nullean.ScopedFileSystem, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nullean.ScopedFileSystem@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nullean.ScopedFileSystem&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Nullean.ScopedFileSystem&version=0.3.0
                    
Install as a Cake Tool

Nullean.ScopedFileSystem

A System.IO.Abstractions IFileSystem decorator that restricts file read and write operations to one or more configured scope root directories and rejects symbolic links.

What it does

ScopedFileSystem wraps any IFileSystem and enforces these rules on every file and directory operation:

Rule Detail
Scope The resolved path must be within a configured scope root or an explicitly allowed OS special folder.
No symlinks The target and all ancestor directories up to the scope root must not be symbolic links.
No hidden files Files whose name starts with . are blocked unless the name is in AllowedHiddenFileNames.
No hidden directories Directories whose name starts with . are blocked unless the name is in AllowedHiddenFolderNames. This applies to both the target directory and all ancestors up to the scope root.
Exists is safe File.Exists() and Directory.Exists() return false for out-of-scope paths instead of throwing.

Any violation throws ScopedFileSystemException (extends SecurityException).

Usage

using System.IO.Abstractions;
using Nullean.ScopedFileSystem;

// Convenience: single root, real filesystem
IFileSystem scoped = new ScopedFileSystem("/var/www/docs");

// Convenience: custom inner filesystem (e.g. MockFileSystem in tests)
IFileSystem scoped = new ScopedFileSystem(inner, "/var/www/docs");

// Full control via ScopedFileSystemOptions
IFileSystem scoped = new ScopedFileSystem(inner, new ScopedFileSystemOptions("/var/www/docs")
{
    AllowedHiddenFolderNames = [".git"],
    AllowedHiddenFileNames   = [".gitkeep"],
    AllowedSpecialFolders    = AllowedSpecialFolder.Temp | AllowedSpecialFolder.ApplicationData,
});
// OK — within scope
var content = scoped.File.ReadAllText("/var/www/docs/page.md");

// OK — write within scope
scoped.File.WriteAllText("/var/www/docs/output.md", content);

// Throws ScopedFileSystemException — outside scope
scoped.File.ReadAllText("/etc/passwd");

// Throws ScopedFileSystemException — hidden file (.env)
scoped.File.ReadAllText("/var/www/docs/.env");

// Throws ScopedFileSystemException — hidden ancestor directory (.hidden)
scoped.File.ReadAllText("/var/www/docs/.hidden/secret.txt");

// Returns false — no exception
bool exists = scoped.File.Exists("/etc/passwd");

// Throws ScopedFileSystemException — symlink
scoped.File.ReadAllText("/var/www/docs/link-to-secret");

Multiple roots

Pass multiple paths to the constructor. Roots must be fully disjoint — no root may be an ancestor of another. Violating this throws ArgumentException at construction time.

// OK — /docs and /data are siblings
new ScopedFileSystem(inner, "/docs", "/data");

// ArgumentException — /data is an ancestor of /data/sub
new ScopedFileSystem(inner, "/data", "/data/sub");

Hidden files and directories

By default, any file or directory whose name begins with . is blocked. Use ScopedFileSystemOptions to allow specific names:

var scoped = new ScopedFileSystem(new ScopedFileSystemOptions("/project")
{
    // Allow traversing .git directories (e.g. to read .git/config)
    AllowedHiddenFolderNames = [".git", ".nuget"],

    // Allow reading/writing specific hidden files
    AllowedHiddenFileNames = [".gitkeep", ".gitignore"],
});

Allowlist matching is case-sensitive on Linux and case-insensitive on Windows and macOS, matching the platform filesystem behaviour. Any IReadOnlyCollection<string> is accepted — no need to configure a comparer.

Special folders

Opt in to read and write access outside the scope roots for well-known OS directories using the AllowedSpecialFolders flags enum:

Flag macOS Windows Linux
Temp /var/folders/… / /tmp %TEMP% /tmp
ApplicationData ~/Library/Application Support %APPDATA% ~/.config
LocalApplicationData ~/Library/Application Support %LOCALAPPDATA% ~/.local/share
CommonApplicationData /Library/Application Support C:\ProgramData /usr/share
All all four combined
var scoped = new ScopedFileSystem(new ScopedFileSystemOptions("/project")
{
    AllowedSpecialFolders = AllowedSpecialFolder.Temp | AllowedSpecialFolder.ApplicationData,
});

// OK — temp is allowed
scoped.File.WriteAllText(Path.Combine(Path.GetTempPath(), "cache.bin"), data);

Paths are resolved at construction time. Access to special folders bypasses all hidden-file and hidden-directory checks.

ScopedFileSystemOptions

ScopedFileSystemOptions is the primary configuration surface. It accepts one or more scope roots either as strings or as IDirectoryInfo instances:

// String roots
var options = new ScopedFileSystemOptions("/root1", "/root2");

// IDirectoryInfo roots
var options = new ScopedFileSystemOptions(fs.DirectoryInfo.New("/root1"));
Property Type Default Description
ScopeRoots IReadOnlyList<string> (required on constructor) Scope root paths
AllowedHiddenFileNames IReadOnlyCollection<string> empty Hidden file names that are allowed
AllowedHiddenFolderNames IReadOnlyCollection<string> empty Hidden directory names that are allowed
AllowedSpecialFolders AllowedSpecialFolder None OS special folders to allow

The inner IFileSystem is passed directly to ScopedFileSystem rather than through options; overloads without an explicit inner default to new FileSystem().

Path validation

All paths are resolved via IPath.GetFullPath before any check. This means traversal sequences like .. are collapsed first, so "/docs/../etc/passwd" is correctly identified as "/etc/passwd" and rejected. Scope membership is verified by walking up the directory tree rather than string prefix matching, which prevents sibling-prefix attacks (e.g. /docs-extra is not inside /docs).

Platform behaviour

  • Case sensitivity: Path comparison is case-sensitive on Linux, case-insensitive on Windows and macOS.
  • Separator normalisation: Both / and \ separators are handled correctly on all platforms.

Projects

Project Description
src/Nullean.ScopedFileSystem Library
tests/Nullean.ScopedFileSystem.Tests xUnit test suite using MockFileSystem

Building

dotnet build
dotnet test
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0 54 4/1/2026
0.3.0 45 3/30/2026
0.2.0 290 3/30/2026