diff --git a/internal/config/config.go b/internal/config/config.go index a5963ed..311a69b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -84,14 +84,24 @@ func Load(workspaceRoot string) (*Config, error) { // Restore WorkspaceRoot — config file must not override path guards. cfg.WorkspaceRoot = savedRoot - // Clamp size limits to prevent config file from requesting excessive memory. + // Clamp size/count limits so the config file cannot request excessive memory. const maxAllowedSize int64 = 100 * 1024 * 1024 // 100 MB + const maxAllowedSearchResults = 100000 if cfg.MaxFileSize > maxAllowedSize { cfg.MaxFileSize = maxAllowedSize } if cfg.MaxParseSize > maxAllowedSize { cfg.MaxParseSize = maxAllowedSize } + if cfg.MaxEditSize > maxAllowedSize { + cfg.MaxEditSize = maxAllowedSize + } + if cfg.MaxSearchResults > maxAllowedSearchResults { + cfg.MaxSearchResults = maxAllowedSearchResults + } + if cfg.MaxSearchResults < 0 { + cfg.MaxSearchResults = 0 // negative would re-enable unbounded search + } // Override from environment variables cfg.loadFromEnv() diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 6abd86b..c006509 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -183,6 +183,35 @@ func TestLoadWithConfigFile(t *testing.T) { } } +// A config file requesting excessive size/count limits must be clamped so it +// cannot force the server into unbounded memory use. +func TestLoadClampsExcessiveLimits(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, ".mcp-filepuff.json") + configContent := `{ + "max_file_size": 999999999999, + "max_parse_size": 999999999999, + "max_edit_size": 999999999999, + "max_search_results": 999999999 + }` + if err := os.WriteFile(configPath, []byte(configContent), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + + cfg, err := Load(tmpDir) + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + const maxSize int64 = 100 * 1024 * 1024 + if cfg.MaxFileSize > maxSize || cfg.MaxParseSize > maxSize || cfg.MaxEditSize > maxSize { + t.Errorf("size limits not clamped: file=%d parse=%d edit=%d", cfg.MaxFileSize, cfg.MaxParseSize, cfg.MaxEditSize) + } + if cfg.MaxSearchResults > 100000 { + t.Errorf("MaxSearchResults not clamped: %d", cfg.MaxSearchResults) + } +} + // TestValidate tests the Validate method with various inputs. func TestValidate(t *testing.T) { tests := []struct {