| 10 | ) |
| 11 | |
| 12 | func TestScopedFs(t *testing.T) { |
| 13 | t.Run("path inside scope is allowed", func(t *testing.T) { |
| 14 | scope := t.TempDir() |
| 15 | if err := os.WriteFile(filepath.Join(scope, "file.txt"), []byte("x"), 0o644); err != nil { |
| 16 | t.Fatal(err) |
| 17 | } |
| 18 | fs := NewScopedFs(afero.NewOsFs(), scope) |
| 19 | |
| 20 | if _, err := fs.Stat("/file.txt"); err != nil { |
| 21 | t.Fatalf("expected in-scope file to be accessible, got %v", err) |
| 22 | } |
| 23 | }) |
| 24 | |
| 25 | t.Run("new file inside scope can be created", func(t *testing.T) { |
| 26 | scope := t.TempDir() |
| 27 | fs := NewScopedFs(afero.NewOsFs(), scope) |
| 28 | |
| 29 | f, err := fs.OpenFile("/does-not-exist-yet.txt", os.O_RDWR|os.O_CREATE, 0o644) |
| 30 | if err != nil { |
| 31 | t.Fatalf("expected to create a new in-scope file, got %v", err) |
| 32 | } |
| 33 | _ = f.Close() |
| 34 | }) |
| 35 | |
| 36 | // Regression for #5975: when the scope resolves to the filesystem root, |
| 37 | // root+separator used to be "//", which no path matched, so every write |
| 38 | // was rejected with os.ErrPermission (HTTP 403). |
| 39 | t.Run("filesystem root scope allows access", func(t *testing.T) { |
| 40 | f := filepath.Join(t.TempDir(), "file.txt") |
| 41 | if err := os.WriteFile(f, []byte("x"), 0o644); err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | fs := NewScopedFs(afero.NewOsFs(), "/") |
| 45 | |
| 46 | if _, err := fs.Stat(f); err != nil { |
| 47 | t.Fatalf("expected a path under root scope to be accessible, got %v", err) |
| 48 | } |
| 49 | }) |
| 50 | |
| 51 | t.Run("escaping symlink to a sibling is rejected", func(t *testing.T) { |
| 52 | base := t.TempDir() |
| 53 | scope := filepath.Join(base, "srv") |
| 54 | sibling := filepath.Join(base, "srvother") |
| 55 | for _, d := range []string{scope, sibling} { |
| 56 | if err := os.MkdirAll(d, 0o755); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | } |
| 60 | if err := os.WriteFile(filepath.Join(sibling, "secret.txt"), []byte("secret"), 0o644); err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | // A symlink lexically inside the scope pointing at a sibling directory |
| 64 | // must not be followed for reads or stats. |
| 65 | if err := os.Symlink(sibling, filepath.Join(scope, "escape")); err != nil { |
| 66 | t.Skipf("cannot create symlink: %v", err) |
| 67 | } |
| 68 | fs := NewScopedFs(afero.NewOsFs(), scope) |
| 69 | |