Safedisk API

The piko.sh/piko/wdk/safedisk package provides a Factory that creates Sandbox instances rooted at validated absolute paths. A sandbox uses Go 1.24's os.Root (Linux: openat2(..., RESOLVE_BENEATH)) to confine reads and writes to a directory. Path traversal, symlink escape, and time-of-check-to-time-of-use (TOCTOU) attempts fail at the syscall layer instead of in user code. Use it for any code path that accepts untrusted file paths (uploads, browser-test fixtures, project-relative reads).

Factory

type Factory interface {
    Create(purpose, path string, mode Mode) (Sandbox, error)
    MustCreate(purpose, path string, mode Mode) Sandbox
    IsPathAllowed(path string) bool
    AllowedPaths() []string
}
MethodPurpose
Create(purpose, path, mode)Create a sandbox at path. Returns an error if path is not within the factory's AllowedPaths. purpose is descriptive text used in error messages and logs.
MustCreate(purpose, path, mode)Same as Create but panics on error. Use only at startup when the caller cannot recover from failure.
IsPathAllowed(path)Reports whether Create would accept a path without actually creating a sandbox.
AllowedPaths()Returns the configured allowlist (empty when the factory allows all paths).

Constructors

func NewFactory(config FactoryConfig) (Factory, error)
func NewCLIFactory(cwd string) (Factory, error)
func Create(purpose, path string, mode Mode) (Sandbox, error)
FunctionPurpose
NewFactory(config)Build a factory from FactoryConfig.
NewCLIFactory(cwd)Convenience constructor for CLI tools. Allows the working directory and validates absolute paths.
Create(...)Top-level convenience that uses the global factory. Initialise it via NewFactory (or NewCLIFactory) at startup.

FactoryConfig

type FactoryConfig struct {
    CWD          string   // Current working directory; always allowed. Auto-detected when empty.
    AllowedPaths []string // Absolute paths that may be sandboxed. Empty list allows all paths.
    Enabled      bool     // false means no-op sandbox (no real isolation, useful for tests).
}

Modes

type Mode uint8

Mode distinguishes read-only from read-write sandboxes. Mode.String() returns the human-readable form. The canonical check on a sandbox is IsReadOnly().

Sandbox interface

Sandbox exposes a sandboxed os-shaped surface. The sandbox root resolves every relative path. The kernel rejects absolute paths and paths that escape (..).

Read

MethodPurpose
Open(name) (FileHandle, error)Open a file for reading.
ReadFile(name) ([]byte, error)Read the entire file.
ReadFileLimit(name, maxBytes) ([]byte, int64, error)Stat-then-read with a size cap. Returns (data, statSize, error). Refuses to allocate when the file exceeds maxBytes so a malformed or attacker-influenced file cannot dominate memory.
Stat(name) (fs.FileInfo, error)Follows symlinks.
Lstat(name) (fs.FileInfo, error)Does NOT follow symlinks.
ReadDir(name) ([]fs.DirEntry, error)Directory entries.
WalkDir(root, walkFunc) errorRecursive walk under root.

Write

All write methods return an error when the caller opens the sandbox in read-only mode.

MethodPurpose
Create(name) (FileHandle, error)Create or truncate a file for writing.
OpenFile(name, flag, perm) (FileHandle, error)Most flexible open (mirrors os.OpenFile).
WriteFile(name, data, perm) errorCreate or truncate, write, close.
WriteFileAtomic(name, data, perm) errorWrite to a temp file in the same directory, then rename - survives a crash mid-write.
Mkdir(name, perm) errorCreate one directory.
MkdirAll(path, perm) errorCreate the full path tree.
Remove(name) errorDelete a file or empty directory.
RemoveAll(path) errorDelete recursively.
Rename(oldpath, newpath) errorBoth paths must be inside the same sandbox.
Chmod(name, mode) errorChange permissions.
CreateTemp(dir, pattern) (FileHandle, error)Like os.CreateTemp but inside the sandbox.
MkdirTemp(dir, pattern) (string, error)Like os.MkdirTemp but inside the sandbox.

Inspect

MethodPurpose
Root() stringAbsolute path of the sandbox root.
Mode() ModeThe configured Mode.
IsReadOnly() boolConvenience check.
Close() errorRelease the underlying os.Root handle.
RelPath(path) stringConvert an absolute path (or one prefixed with the sandbox folder name) to a sandbox-relative path.

FileHandle

A FileHandle exposes the standard io.Reader/io.Writer/io.Closer surface plus stat, sync, truncate, and seek. The concrete implementation is *File, returned by Open, Create, OpenFile, and CreateTemp.

MethodPurpose
Name() stringSandbox-relative name.
AbsolutePath() stringAbsolute path on disk.
Read, ReadAt, Write, WriteAt, WriteString, SeekStandard byte-level operations.
Sync() errorFlush to disk.
Truncate(size) errorTruncate to length.
Stat() (fs.FileInfo, error)File info.
Chmod(mode) errorChange permissions.
ReadDir(n) ([]fs.DirEntry, error)Directory entries (when the handle is a directory).
Fd() uintptrUnderlying file descriptor.
Close() errorRelease the handle.

Errors

The package exposes typed sentinel errors for the common rejection cases (empty path, path escapes the sandbox root, file exceeds ReadFileLimit, sandbox closed). Use errors.Is to match.

Mock

safedisk ships a mock implementation suitable for tests that do not want to touch disk. See mock.go for constructors. The contract matches the Factory interface so test code is identical.

See also