Skip to content

Commit fb18229

Browse files
committed
#1076 : Filesystem allow-list bypass fix
Signed-off-by: Jean-Laurent de Morlhon <jeanlaurent@morlhon.net>
1 parent 099802c commit fb18229

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

pkg/tools/builtin/filesystem.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,10 @@ func (t *FilesystemTool) isPathAllowed(path string) error {
345345
continue
346346
}
347347

348-
if strings.HasPrefix(absPath, allowedAbs) {
348+
// Check if path matches exactly or is a subdirectory (with separator check)
349+
// This prevents sibling directories with similar names from bypassing the check
350+
// (e.g., /project-secrets when /project is allowed)
351+
if absPath == allowedAbs || strings.HasPrefix(absPath, allowedAbs+string(filepath.Separator)) {
349352
return nil
350353
}
351354
}
@@ -504,7 +507,8 @@ func (t *FilesystemTool) handleAddAllowedDirectory(_ context.Context, args AddAl
504507
return &tools.ToolCallResult{Output: fmt.Sprintf("Directory %s is already in allowed directories list", absPath)}, nil
505508
}
506509
// Check if the requested path is already covered by an existing allowed directory
507-
if strings.HasPrefix(absPath, allowedAbs) {
510+
// Use proper separator check to prevent sibling directory bypass
511+
if absPath == allowedAbs || strings.HasPrefix(absPath, allowedAbs+string(filepath.Separator)) {
508512
return &tools.ToolCallResult{Output: fmt.Sprintf("Directory %s is already accessible (covered by %s)", absPath, allowedAbs)}, nil
509513
}
510514
}

pkg/tools/builtin/filesystem_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,62 @@ func TestFilesystemTool_IsPathAllowed(t *testing.T) {
5858
assert.Contains(t, err.Error(), "not within allowed directories")
5959
}
6060

61+
// TestFilesystemTool_IsPathAllowed_SiblingDirectories tests the fix for issue #1076
62+
// It verifies that sibling directories with similar names don't bypass the allow-list
63+
func TestFilesystemTool_IsPathAllowed_SiblingDirectories(t *testing.T) {
64+
t.Parallel()
65+
66+
// Create a temporary directory structure:
67+
// - tmpRoot/project (allowed)
68+
// - tmpRoot/project-secrets (should NOT be allowed)
69+
// - tmpRoot/project2 (should NOT be allowed)
70+
// - tmpRoot/projectx (should NOT be allowed)
71+
tmpRoot := t.TempDir()
72+
73+
projectDir := filepath.Join(tmpRoot, "project")
74+
projectSecretsDir := filepath.Join(tmpRoot, "project-secrets")
75+
project2Dir := filepath.Join(tmpRoot, "project2")
76+
projectXDir := filepath.Join(tmpRoot, "projectx")
77+
78+
require.NoError(t, os.Mkdir(projectDir, 0o755))
79+
require.NoError(t, os.Mkdir(projectSecretsDir, 0o755))
80+
require.NoError(t, os.Mkdir(project2Dir, 0o755))
81+
require.NoError(t, os.Mkdir(projectXDir, 0o755))
82+
83+
// Only allow the "project" directory
84+
tool := NewFilesystemTool([]string{projectDir})
85+
86+
// Test that subdirectories of allowed directory are accessible
87+
allowedSubdir := filepath.Join(projectDir, "src", "main.go")
88+
err := tool.isPathAllowed(allowedSubdir)
89+
require.NoError(t, err, "Subdirectories of allowed directory should be accessible")
90+
91+
// Test that the allowed directory itself is accessible
92+
err = tool.isPathAllowed(projectDir)
93+
require.NoError(t, err, "Allowed directory itself should be accessible")
94+
95+
// Test that sibling directories with similar names are NOT accessible
96+
siblingTests := []struct {
97+
name string
98+
path string
99+
}{
100+
{"project-secrets", filepath.Join(projectSecretsDir, "confidential.txt")},
101+
{"project2", filepath.Join(project2Dir, "file.txt")},
102+
{"projectx", filepath.Join(projectXDir, "file.txt")},
103+
{"project-secrets dir", projectSecretsDir},
104+
{"project2 dir", project2Dir},
105+
{"projectx dir", projectXDir},
106+
}
107+
108+
for _, tc := range siblingTests {
109+
t.Run(tc.name, func(t *testing.T) {
110+
err := tool.isPathAllowed(tc.path)
111+
require.Error(t, err, "Sibling directory %s should NOT be accessible", tc.name)
112+
assert.Contains(t, err.Error(), "not within allowed directories")
113+
})
114+
}
115+
}
116+
61117
func TestFilesystemTool_WriteFile(t *testing.T) {
62118
t.Parallel()
63119
tmpDir := t.TempDir()

0 commit comments

Comments
 (0)