Files
filepuff-mcp/internal/config/security_test.go
T

142 lines
3.1 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
)
// TestIsPathAllowed_SymlinkSecurity tests the symlink security fix.
func TestIsPathAllowed_SymlinkSecurity(t *testing.T) {
// Create a temporary workspace
tmpDir := t.TempDir()
workspace := filepath.Join(tmpDir, "workspace")
outside := filepath.Join(tmpDir, "outside")
if err := os.MkdirAll(workspace, 0700); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(outside, 0700); err != nil {
t.Fatal(err)
}
// Create a file outside the workspace
outsideFile := filepath.Join(outside, "secret.txt")
if err := os.WriteFile(outsideFile, []byte("secret data"), 0600); err != nil {
t.Fatal(err)
}
cfg := &Config{
WorkspaceRoot: workspace,
}
tests := []struct {
setup func() string
name string
expected bool
}{
{
name: "regular file inside workspace",
setup: func() string {
return filepath.Join(workspace, "file.txt")
},
expected: true,
},
{
name: "file with parent directory traversal",
setup: func() string {
return filepath.Join(workspace, "../outside/secret.txt")
},
expected: false,
},
{
name: "symlink pointing outside workspace",
setup: func() string {
symlink := filepath.Join(workspace, "link.txt")
_ = os.Symlink(outsideFile, symlink)
return symlink
},
expected: false,
},
{
name: "symlink pointing inside workspace",
setup: func() string {
inside := filepath.Join(workspace, "inside.txt")
_ = os.WriteFile(inside, []byte("ok"), 0600)
symlink := filepath.Join(workspace, "link_inside.txt")
_ = os.Symlink(inside, symlink)
return symlink
},
expected: true,
},
{
name: "dotfile inside workspace",
setup: func() string {
return filepath.Join(workspace, ".gitignore")
},
expected: true,
},
{
name: "hidden directory inside workspace",
setup: func() string {
return filepath.Join(workspace, ".git/config")
},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
path := tt.setup()
result := cfg.IsPathAllowed(path)
if result != tt.expected {
t.Errorf("IsPathAllowed(%q) = %v, want %v", path, result, tt.expected)
}
})
}
}
// TestIsPathAllowed_BasicCases tests basic path validation.
func TestIsPathAllowed_BasicCases(t *testing.T) {
tmpDir := t.TempDir()
cfg := &Config{
WorkspaceRoot: tmpDir,
}
tests := []struct {
name string
path string
expected bool
}{
{
name: "path inside workspace",
path: filepath.Join(tmpDir, "file.txt"),
expected: true,
},
{
name: "path outside workspace",
path: "/etc/passwd",
expected: false,
},
{
name: "parent directory reference",
path: filepath.Join(tmpDir, "../../../etc/passwd"),
expected: false,
},
{
name: "workspace root itself",
path: tmpDir,
expected: true, // Workspace root is a valid, allowed path (needed for ast_query)
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := cfg.IsPathAllowed(tt.path)
if result != tt.expected {
t.Errorf("IsPathAllowed(%q) = %v, want %v", tt.path, result, tt.expected)
}
})
}
}