Documentation
¶
Overview ¶
Package allowedpaths implements a filesystem sandbox that restricts access to a set of allowed directories using os.Root (Go 1.24+).
Index ¶
- Constants
- func CollectDirEntries(readBatch func(n int) ([]fs.DirEntry, error), batchSize, offset, maxRead int) ([]fs.DirEntry, bool, error)
- func FileIdentity(_ string, info fs.FileInfo, _ *Sandbox) (uint64, uint64, bool)
- func IsDevNull(path string) bool
- func IsErrIsDirectory(err error) bool
- func PortableErrMsg(err error) string
- func PortablePathError(err error) error
- func WithContextClose(ctx context.Context, f io.ReadWriteCloser) io.ReadWriteCloser
- type Sandbox
- func (s *Sandbox) Access(path string, cwd string, mode uint32) error
- func (s *Sandbox) Close() error
- func (s *Sandbox) IsDirEmpty(path string, cwd string) (bool, error)
- func (s *Sandbox) Lstat(path string, cwd string) (fs.FileInfo, error)
- func (s *Sandbox) Open(path string, cwd string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
- func (s *Sandbox) OpenDir(path string, cwd string) (fs.ReadDirFile, error)
- func (s *Sandbox) ReadDir(path string, cwd string) ([]fs.DirEntry, error)
- func (s *Sandbox) ReadDirForGlob(path string, cwd string) ([]fs.DirEntry, error)
- func (s *Sandbox) ReadDirLimited(path string, cwd string, offset, maxRead int) ([]fs.DirEntry, bool, error)
- func (s *Sandbox) Stat(path string, cwd string) (fs.FileInfo, error)
Constants ¶
const MaxGlobEntries = 10_000
MaxGlobEntries is the maximum number of directory entries read per single glob expansion step. ReadDirForGlob returns an error for directories that exceed this limit to prevent memory exhaustion during pattern matching.
Variables ¶
This section is empty.
Functions ¶
func CollectDirEntries ¶
func CollectDirEntries(readBatch func(n int) ([]fs.DirEntry, error), batchSize, offset, maxRead int) ([]fs.DirEntry, bool, error)
CollectDirEntries reads directory entries in batches using readBatch, skipping the first offset entries and collecting up to maxRead entries. Returns (entries, truncated, lastErr). Entries are sorted by name.
NOTE: We intentionally truncate before reading all entries. For directories larger than maxRead, the returned entries are sorted within the read window but may not be the globally-smallest names. Reading all entries to get globally-correct sorting would defeat the DoS protection — a directory with millions of files would OOM or stall. The truncation warning communicates that output is incomplete.
func FileIdentity ¶
FileIdentity extracts canonical file identity (dev+inode) from FileInfo. On Unix, this is extracted directly from Stat_t via info.Sys().
func IsErrIsDirectory ¶
IsErrIsDirectory reports whether err is an "is a directory" error.
func PortableErrMsg ¶
PortableErrMsg returns a POSIX-style error message for the given error, normalizing platform-specific syscall messages to consistent strings. This ensures shell error output is identical across Linux, macOS, and Windows.
func PortablePathError ¶
PortablePathError returns a *os.PathError with a normalized error message. If the error is not a *os.PathError, it is returned as-is. Only the Err field is normalized; the Path and Op fields are preserved as-is.
func WithContextClose ¶ added in v0.0.8
func WithContextClose(ctx context.Context, f io.ReadWriteCloser) io.ReadWriteCloser
WithContextClose wraps f so that f.Close() is guaranteed to be called when ctx is done, in addition to any explicit Close calls made by the caller. The underlying file is closed at most once.
A background goroutine waits for either ctx cancellation or an explicit Close call. If the context expires first the file is closed immediately and the goroutine exits. If Close is called first the goroutine is signalled to stop and exits without touching the file again.
Types ¶
type Sandbox ¶
type Sandbox struct {
// contains filtered or unexported fields
}
Sandbox restricts filesystem access to a set of allowed directories. The restriction is enforced using os.Root (Go 1.24+), which uses openat syscalls for atomic path validation — immune to symlink and ".." traversal attacks.
func New ¶
New creates a sandbox from an allowlist of directory paths. Paths that do not exist or cannot be opened are silently skipped — the sandbox operates with whatever paths are available at construction time.
Diagnostic messages about skipped paths are collected into warnings. The caller is responsible for writing them to the appropriate output stream.
func (*Sandbox) Access ¶
Access checks whether the resolved path is accessible with the given mode. All operations go through os.Root to stay within the sandbox. Mode: 0x04 = read, 0x02 = write, 0x01 = execute.
On Unix, read permission for regular files is verified by attempting to open through os.Root with O_NONBLOCK (fd-relative openat, respects POSIX ACLs, never blocks on FIFOs). Metadata is obtained from the opened fd via fstat to eliminate TOCTOU between open and stat. For special files where open fails (e.g. sockets), and for write and execute checks, mode-bit inspection is used on the fd-relative Stat result. On Windows, the same OpenFile approach is used for read checks; write and execute checks are not performed.
All operations are fd-relative through os.Root — no filesystem path is re-resolved through the mutable namespace after initial validation.
func (*Sandbox) Close ¶
Close releases all os.Root file descriptors. It is safe to call multiple times.
func (*Sandbox) IsDirEmpty ¶
IsDirEmpty checks whether a directory is empty by reading at most one entry. More efficient than reading all entries when only emptiness needs to be determined.
func (*Sandbox) Lstat ¶
Lstat implements the restricted lstat policy. Like stat, it uses a metadata-only call, but does not follow symbolic links — the returned FileInfo describes the link itself rather than its target.
func (*Sandbox) Open ¶
func (s *Sandbox) Open(path string, cwd string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
Open implements the restricted file-open policy. The file is opened through os.Root for atomic path validation. Only read-only access is permitted; any write flags are rejected as a defense-in-depth measure.
func (*Sandbox) OpenDir ¶
OpenDir opens a directory within the sandbox for incremental reading via ReadDir(n). The caller must close the returned handle when done. Returns fs.ReadDirFile to expose only read-only directory methods.
func (*Sandbox) ReadDirForGlob ¶
ReadDirForGlob reads directory entries for glob expansion, capped at MaxGlobEntries. The underlying ReadDir call is limited to MaxGlobEntries+1 so the kernel never materialises more entries than needed. If the directory exceeds the limit an error is returned before any pattern matching or sorting can occur, making the failure explicit rather than silently returning a partial listing that could miss valid matches.
func (*Sandbox) ReadDirLimited ¶
func (s *Sandbox) ReadDirLimited(path string, cwd string, offset, maxRead int) ([]fs.DirEntry, bool, error)
ReadDirLimited reads directory entries, skipping the first offset entries and returning up to maxRead entries sorted by name within the read window. Returns (entries, truncated, error). When truncated is true, the directory contained more entries beyond the returned set.
The offset skips raw directory entries during reading (before sorting). This means offset does NOT correspond to positions in a sorted listing — pages may overlap or miss entries. This is an acceptable tradeoff to achieve O(n) memory regardless of offset value, where n = min(maxRead, entries).