A virtual jailed shell environment for Go apps backed by a virtual filesystem.
xeiaso.net/blog/2026/dancing-mad-sandboxing/
shell
agents
terrible-idea
sandbox
1package unixmeta
2
3import (
4 "os"
5 "testing"
6 "time"
7)
8
9func TestModeRoundTrip(t *testing.T) {
10 t.Parallel()
11
12 for _, tt := range []struct {
13 name string
14 mode os.FileMode
15 }{
16 {name: "regular file rw-r--r--", mode: 0o644},
17 {name: "regular file rwxr-xr-x", mode: 0o755},
18 {name: "directory", mode: os.ModeDir | 0o755},
19 {name: "symlink", mode: os.ModeSymlink | 0o777},
20 {name: "block device", mode: os.ModeDevice | 0o660},
21 {name: "char device", mode: os.ModeDevice | os.ModeCharDevice | 0o620},
22 {name: "fifo", mode: os.ModeNamedPipe | 0o644},
23 {name: "socket", mode: os.ModeSocket | 0o755},
24 {name: "setuid", mode: os.ModeSetuid | 0o755},
25 {name: "setgid", mode: os.ModeSetgid | 0o755},
26 {name: "sticky dir", mode: os.ModeDir | os.ModeSticky | 0o777},
27 } {
28 t.Run(tt.name, func(t *testing.T) {
29 t.Parallel()
30 got := GoFileMode(PosixMode(tt.mode))
31 if got != tt.mode {
32 t.Logf("want: %v (%#o)", tt.mode, uint32(tt.mode))
33 t.Logf("got: %v (%#o)", got, uint32(got))
34 t.Error("mode did not survive round trip")
35 }
36 })
37 }
38}
39
40func TestPosixModeKnownValue(t *testing.T) {
41 t.Parallel()
42
43 // 0o100644 == 33188: regular file with rw-r--r--, per the reference doc.
44 if got := PosixMode(0o644); got != 0o100644 {
45 t.Errorf("PosixMode(0o644) = %#o, want %#o", got, 0o100644)
46 }
47}
48
49func TestEncodeDecodeRoundTrip(t *testing.T) {
50 t.Parallel()
51
52 mtime := time.Unix(1716700000, 0)
53
54 for _, tt := range []struct {
55 name string
56 in Attrs
57 }{
58 {
59 name: "regular file",
60 in: Attrs{UID: 1000, GID: 1000, Mode: 0o644, Mtime: mtime},
61 },
62 {
63 name: "directory",
64 in: Attrs{UID: 0, GID: 0, Mode: os.ModeDir | 0o755, Mtime: mtime},
65 },
66 {
67 name: "char device with rdev",
68 in: Attrs{UID: 0, GID: 5, Mode: os.ModeDevice | os.ModeCharDevice | 0o620, Rdev: 1280, Mtime: mtime},
69 },
70 {
71 name: "symlink with awkward target",
72 in: Attrs{UID: 1000, GID: 1000, Mode: os.ModeSymlink | 0o777, Mtime: mtime, SymlinkTarget: "/etc/has spaces/and%percent"},
73 },
74 } {
75 t.Run(tt.name, func(t *testing.T) {
76 t.Parallel()
77 got := Decode(Encode(tt.in), Attrs{})
78 if got.UID != tt.in.UID || got.GID != tt.in.GID {
79 t.Errorf("uid/gid: want %d/%d, got %d/%d", tt.in.UID, tt.in.GID, got.UID, got.GID)
80 }
81 if got.Mode != tt.in.Mode {
82 t.Errorf("mode: want %v, got %v", tt.in.Mode, got.Mode)
83 }
84 if !got.Mtime.Equal(tt.in.Mtime) {
85 t.Errorf("mtime: want %v, got %v", tt.in.Mtime, got.Mtime)
86 }
87 if got.SymlinkTarget != tt.in.SymlinkTarget {
88 t.Errorf("symlink target: want %q, got %q", tt.in.SymlinkTarget, got.SymlinkTarget)
89 }
90 if tt.in.Rdev != 0 && got.Rdev != tt.in.Rdev {
91 t.Errorf("rdev: want %d, got %d", tt.in.Rdev, got.Rdev)
92 }
93 })
94 }
95}
96
97func TestDecodeMissingKeysKeepDefaults(t *testing.T) {
98 t.Parallel()
99
100 defaults := Attrs{UID: 501, GID: 20, Mode: 0o644, Mtime: time.Unix(42, 0)}
101 got := Decode(map[string]string{}, defaults)
102 if got != defaults {
103 t.Logf("want: %+v", defaults)
104 t.Logf("got: %+v", got)
105 t.Error("empty metadata should leave defaults untouched")
106 }
107}
108
109func TestDecodeMalformedTreatedAsMissing(t *testing.T) {
110 t.Parallel()
111
112 defaults := Attrs{UID: 501, GID: 20, Mode: 0o644, Mtime: time.Unix(42, 0)}
113 meta := map[string]string{
114 "uid": "not-a-number",
115 "gid": "99",
116 "mode": "garbage",
117 "mtime": "also-bad",
118 }
119 got := Decode(meta, defaults)
120 if got.UID != defaults.UID {
121 t.Errorf("malformed uid should fall back to default: want %d, got %d", defaults.UID, got.UID)
122 }
123 if got.GID != 99 {
124 t.Errorf("valid gid should be parsed: want 99, got %d", got.GID)
125 }
126 if got.Mode != defaults.Mode {
127 t.Errorf("malformed mode should fall back to default: want %v, got %v", defaults.Mode, got.Mode)
128 }
129 if !got.Mtime.Equal(defaults.Mtime) {
130 t.Errorf("malformed mtime should fall back to default: want %v, got %v", defaults.Mtime, got.Mtime)
131 }
132}