From 150722ac13075122cd3f14090f25d1adc0aeecf1 Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 6 Aug 2026 14:56:38 +0100 Subject: [PATCH] fix(fs): pass absolute paths through Fs.path unprefixed on an unrestricted medium MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fs.path bolted "/" onto every path before cleaning. On an unrestricted ("/"-rooted) medium that prefix was a no-op for POSIX absolute paths ("//x" cleans to "/x") and corrupted Windows drive-letter ones: filepath.Clean(`/C:\models\x`) is `\C:\models\x`, a path no Win32 open resolves. Every List/Read walk over an absolute path on Windows failed on it — go-inference's Discover yielded nothing and model/pack printed the mangled path verbatim in CI (dAppCore/go-inference run 31101973970): CreateFile \C:\Users\RUNNER~1\...\pack-roundtrip-good-3323670468\src: The filename, directory name, or volume label syntax is incorrect. The unrestricted branch now cleans the path as it stands. The sandboxed branch keeps its prefix — there it IS the contract (absolute paths map under root). Relative-path handling is untouched. PathIsAbs recognises the drive-letter form on every platform, so the regression test pins the contract on POSIX runners too. Co-Authored-By: Virgil --- fs.go | 16 +++++++++++----- fs_internal_test.go | 9 +++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/fs.go b/fs.go index f4e7932d..ecbebf04 100644 --- a/fs.go +++ b/fs.go @@ -129,15 +129,21 @@ func (m *Fs) path(p string) string { return PathJoin(cwd, p) } + // An unrestricted medium passes an absolute path through as it + // stands — only a relative one was rewritten above. No "/" prefix + // here: prefixing was a no-op for a POSIX absolute path ("//x" + // cleans to "/x") but corrupted a Windows drive-letter one — + // filepath.Clean(`/C:\models`) is `\C:\models`, a path no Win32 + // open resolves, which silently emptied every List/Read walk on + // an absolute path there. + if root == "/" { + return CleanPath(p, string(PathSeparator)) + } + // Use a leading slash to resolve all .. and . internally // before joining with the root. This is a standard way to sandbox paths. clean := CleanPath("/"+p, string(PathSeparator)) - // If root is "/", allow absolute paths through - if root == "/" { - return clean - } - // Strip leading "/" so Join works correctly with root return PathJoin(root, clean[1:]) } diff --git a/fs_internal_test.go b/fs_internal_test.go index 9d35d1fd..8ce9cf93 100644 --- a/fs_internal_test.go +++ b/fs_internal_test.go @@ -20,6 +20,15 @@ func TestFs_Fs_path_Ugly(t *T) { fsys := (&Fs{}).New("/") AssertEqual(t, PathJoin(cwd.Value.(string), "relative.txt"), fsys.path("relative.txt")) + + // An absolute path on an unrestricted medium comes back as itself — + // no "/" prefix bolted on first. The prefix was invisible on POSIX + // ("//x" cleans to "/x") and turned a Windows drive-letter path into + // `\C:\models`, which no Win32 open resolves. PathIsAbs recognises + // the drive-letter form on every platform, so this pins the contract + // everywhere, not just on a windows runner. + AssertEqual(t, `C:\models\gemma`, fsys.path(`C:\models\gemma`)) + AssertEqual(t, CleanPath("/tmp//x/../y", string(PathSeparator)), fsys.path("/tmp//x/../y")) } func TestFs_Fs_validatePath_Good(t *T) { root := t.TempDir()