fixup! Update, bugfixes on diff and edit handling

This commit is contained in:
2026-02-22 14:03:54 +00:00
parent 6980d3b294
commit 982c2c8b44
23 changed files with 655 additions and 194 deletions
+16 -3
View File
@@ -69,13 +69,26 @@ func Load(workspaceRoot string) (*Config, error) {
cfg.WorkspaceRoot = cwd
}
// Try to load from config file in workspace root
// Try to load from config file in workspace root.
// Save WorkspaceRoot before loading config file so it cannot be overridden.
savedRoot := cfg.WorkspaceRoot
configPath := filepath.Join(cfg.WorkspaceRoot, ".mcp-filepuff.json")
if data, err := os.ReadFile(configPath); err == nil {
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
}
// Restore WorkspaceRoot — config file must not override path guards.
cfg.WorkspaceRoot = savedRoot
// Clamp size limits to prevent config file from requesting excessive memory.
const maxAllowedSize int64 = 100 * 1024 * 1024 // 100 MB
if cfg.MaxFileSize > maxAllowedSize {
cfg.MaxFileSize = maxAllowedSize
}
if cfg.MaxParseSize > maxAllowedSize {
cfg.MaxParseSize = maxAllowedSize
}
// Override from environment variables
cfg.loadFromEnv()
@@ -173,8 +186,8 @@ func (c *Config) IsPathAllowed(path string) bool {
// Check if the path is within workspace (doesn't start with ..)
// This prevents both "../" attacks and symlink bypasses
// Also reject empty relative path (which means it's the workspace root itself)
return rel != "." && !strings.HasPrefix(rel, "..")
// The workspace root itself (rel == ".") is a valid, allowed path
return !strings.HasPrefix(rel, "..")
}
// Validate validates the configuration and returns an error if invalid.
+55 -2
View File
@@ -389,8 +389,8 @@ func TestIsPathAllowedEdgeCases(t *testing.T) {
{
name: "workspace_root_itself",
path: tmpDir,
allowed: false,
desc: "workspace root itself should not be allowed",
allowed: true,
desc: "workspace root itself should be allowed",
},
{
name: "dot_relative",
@@ -546,6 +546,59 @@ func TestConfigFileLoadingErrors(t *testing.T) {
}
}
// TestIsPathAllowed_SymlinkOutsideWorkspace verifies that symlinks pointing
// outside the workspace are rejected (T-01).
func TestIsPathAllowed_SymlinkOutsideWorkspace(t *testing.T) {
// Create two separate temp dirs: one as workspace, one as outside target
workspace, err := os.MkdirTemp("", "mcp-workspace-*")
if err != nil {
t.Fatalf("failed to create workspace dir: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(workspace) })
outside, err := os.MkdirTemp("", "mcp-outside-*")
if err != nil {
t.Fatalf("failed to create outside dir: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(outside) })
// Create a file outside the workspace
outsideFile := filepath.Join(outside, "secret.txt")
if err := os.WriteFile(outsideFile, []byte("secret"), 0o600); err != nil {
t.Fatalf("failed to write outside file: %v", err)
}
// Create a symlink inside the workspace pointing outside
symlinkPath := filepath.Join(workspace, "escape-link")
if err := os.Symlink(outsideFile, symlinkPath); err != nil {
t.Skip("symlink creation not supported on this system")
}
cfg := Default()
cfg.WorkspaceRoot = workspace
// The symlink resolves to a file outside workspace — must be rejected
if cfg.IsPathAllowed(symlinkPath) {
t.Error("symlink pointing outside workspace should NOT be allowed")
}
// Direct access to the outside file should also be rejected
if cfg.IsPathAllowed(outsideFile) {
t.Error("file outside workspace should NOT be allowed")
}
// File inside workspace should still be allowed
insideFile := filepath.Join(workspace, "safe.txt")
if !cfg.IsPathAllowed(insideFile) {
t.Error("file inside workspace should be allowed")
}
// Workspace root itself should be allowed (C-08 fix)
if !cfg.IsPathAllowed(workspace) {
t.Error("workspace root itself should be allowed")
}
}
// Helper function to check if a string contains a substring.
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
+1 -1
View File
@@ -126,7 +126,7 @@ func TestIsPathAllowed_BasicCases(t *testing.T) {
{
name: "workspace root itself",
path: tmpDir,
expected: false, // Empty relative path
expected: true, // Workspace root is a valid, allowed path (needed for ast_query)
},
}