From f75008de07d2b4b81f6329702719f02eca66a02b Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Thu, 20 Aug 2026 12:10:44 +0100 Subject: [PATCH] archive: fix zip subdirectory root matching sibling directories GHSA-66hp-wgxq-6f5q When a zip archive was mounted at a subdirectory root, readZip used a bare strings.HasPrefix to decide which entries fell inside the root. This matched on a raw string prefix rather than a path boundary, so mounting root "foo" also exposed sibling entries such as "foobar/..." with their names left uncorrected. Require a path boundary when filtering by root. --- backend/archive/zip/zip.go | 6 ++++-- backend/archive/zip/zip_internal_test.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/archive/zip/zip.go b/backend/archive/zip/zip.go index ec9ba77d7..6af2d21d7 100644 --- a/backend/archive/zip/zip.go +++ b/backend/archive/zip/zip.go @@ -146,8 +146,10 @@ func (f *Fs) readZip() (singleObject bool, err error) { } remote = path.Join(f.prefix, remote) if f.root != "" { - // Ignore all files outside the root - if !strings.HasPrefix(remote, f.root) { + // Ignore all files outside the root, requiring a path + // boundary so that root "foo" does not also match a + // sibling entry such as "foobar" + if remote != f.root && !strings.HasPrefix(remote, f.root+"/") { continue } if remote == f.root { diff --git a/backend/archive/zip/zip_internal_test.go b/backend/archive/zip/zip_internal_test.go index f0bb3bb03..5b7a0252d 100644 --- a/backend/archive/zip/zip_internal_test.go +++ b/backend/archive/zip/zip_internal_test.go @@ -83,3 +83,24 @@ func TestReadZipSlip(t *testing.T) { } assert.Equal(t, []string{"good.txt"}, remotes) } + +// Mounting with a non-empty root must only expose entries within that +// root directory, not sibling directories that merely share a name +// prefix (root "foo" must not match "foobar"). +func TestReadZipRootBoundary(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + name := writeZip(t, dir, "test.zip", + "foo/a.txt", + "foobar/b.txt", + ) + + localFs, err := cache.Get(ctx, dir) + require.NoError(t, err) + + f, err := New(ctx, localFs, name, "", "foo") + require.NoError(t, err) + + remotes := allRemotes(t, f) + assert.Equal(t, []string{"a.txt"}, remotes) +}