-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathsandbox_linux_test.go
More file actions
138 lines (111 loc) · 3.82 KB
/
Copy pathsandbox_linux_test.go
File metadata and controls
138 lines (111 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//go:build linux
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/mark3labs/mcp-go/server"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMain turns this test binary into the Landlock shim when it is re-exec'd
// with the sandbox argument, so the confinement tests can drive the real shim
// path instead of a stub.
func TestMain(m *testing.M) {
if len(os.Args) > 1 && os.Args[1] == sandboxArg {
if err := runSandboxShim(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(127)
}
}
os.Exit(m.Run())
}
func requireLandlock(t *testing.T) {
t.Helper()
data, err := os.ReadFile("/sys/kernel/security/lsm")
if err != nil || !strings.Contains(string(data), "landlock") {
t.Skip("landlock not active in this kernel")
}
}
func TestSandbox_confinesWrites(t *testing.T) {
requireLandlock(t)
ws := t.TempDir()
outside := filepath.Join(t.TempDir(), "canary")
self, err := os.Executable()
require.NoError(t, err)
inside := filepath.Join(ws, "inside")
script := fmt.Sprintf("touch %q; touch %q", inside, outside)
cmd := exec.Command(self, sandboxArg, ws, "--", "/bin/sh", "-c", script)
out, err := cmd.CombinedOutput()
require.Error(t, err, "sandboxed write outside workspace should fail; output=%s", out)
_, statIn := os.Stat(inside)
assert.NoError(t, statIn, "write inside workspace should succeed")
_, statOut := os.Stat(outside)
assert.True(t, os.IsNotExist(statOut), "write outside workspace must not land")
}
func TestSandbox_allowsWorkspaceWrites(t *testing.T) {
requireLandlock(t)
ws := t.TempDir()
self, err := os.Executable()
require.NoError(t, err)
target := filepath.Join(ws, "sub", "file")
script := fmt.Sprintf("mkdir -p %q && echo ok > %q", filepath.Join(ws, "sub"), target)
cmd := exec.Command(self, sandboxArg, ws, "--", "/bin/sh", "-c", script)
out, err := cmd.CombinedOutput()
require.NoError(t, err, "workspace write should succeed; output=%s", out)
data, readErr := os.ReadFile(target)
require.NoError(t, readErr)
assert.Equal(t, "ok\n", string(data))
}
// TestGitTools_underSandbox runs the read-only git tools with the Landlock
// sandbox enabled, proving the confinement does not break normal git execution.
// The executor re-execs the test binary as the shim (see TestMain), which
// applies Landlock and then execs git.
func TestGitTools_underSandbox(t *testing.T) {
requireLandlock(t)
ws := newTestRepo(t)
executor := newCommandExecutor(SecurityConfig{
Enabled: true,
WorkingDirectory: ws.root,
MaxExecutionTime: 30 * time.Second,
Sandbox: true,
}, zerolog.Nop())
s := server.NewMCPServer("t", "0")
newGitTools(ws, executor, false, zerolog.Nop()).register(s)
statusRes := callTool(t, s, "git_status", map[string]any{})
require.False(t, statusRes.IsError, "git_status should run under the sandbox")
diffRes := callTool(t, s, "git_diff", map[string]any{})
require.False(t, diffRes.IsError, "git_diff should run under the sandbox")
}
func TestParseSandboxArgs(t *testing.T) {
t.Parallel()
type testCase struct {
name string
args []string
wantErr bool
}
cases := []testCase{
{name: "well formed", args: []string{"self", sandboxArg, "/ws", "--", "git", "diff"}, wantErr: false},
{name: "missing separator", args: []string{"self", sandboxArg, "/ws", "git"}, wantErr: true},
{name: "empty target", args: []string{"self", sandboxArg, "/ws", "--"}, wantErr: true},
{name: "too short", args: []string{"self", sandboxArg}, wantErr: true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ws, target, err := parseSandboxArgs(tc.args)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, "/ws", ws)
assert.Equal(t, []string{"git", "diff"}, target)
})
}
}