From 5631e04a12d48a1b01bffd21b8b72302c51e73a7 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 22:50:55 +0000 Subject: [PATCH] feat(io): Migrate filesystem access to pkg/io Medium abstraction (#172) * feat(io): add pkg/io with symlink-safe path validation - Add pkg/io with Medium interface for filesystem abstraction - Add pkg/io/local with sandboxed filesystem implementation - Add symlink-safe path validation to prevent bypass attacks - Add sentinel errors (ErrPathTraversal, ErrSymlinkTraversal) - Add NewSandboxed() for creating sandboxed Medium instances - Add MockMedium for testing Closes #169 Co-Authored-By: Claude Opus 4.5 * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Add missing filesystem operations to Medium interface: - Delete(path) - removes file or empty directory - DeleteAll(path) - removes path and contents recursively - Rename(old, new) - moves or renames files/directories - Exists(path) - checks if path exists - IsDir(path) - checks if path is a directory - List(path) - returns directory contents as []os.DirEntry - Stat(path) - returns file info as os.FileInfo Implements both local.Medium and MockMedium with full support. Closes #102 Co-Authored-By: Claude Opus 4.5 * fix(io): MockMedium.Read returns os.ErrNotExist for consistency Ensures os.IsNotExist(err) works with MockMedium like with real filesystem. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- pkg/io/io.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/io/io.go b/pkg/io/io.go index 7c5299e6..2b573c4d 100644 --- a/pkg/io/io.go +++ b/pkg/io/io.go @@ -1,7 +1,7 @@ package io import ( - "errors" + "os" coreerr "github.com/host-uk/core/pkg/framework/core" "github.com/host-uk/core/pkg/io/local" @@ -104,7 +104,7 @@ func NewMockMedium() *MockMedium { func (m *MockMedium) Read(path string) (string, error) { content, ok := m.Files[path] if !ok { - return "", coreerr.E("io.MockMedium.Read", "file not found: "+path, errors.New("file not found")) + return "", coreerr.E("io.MockMedium.Read", "file not found: "+path, os.ErrNotExist) } return content, nil }