feat(security): add config validation for safety-critical parameters

Add Validate() method to Config that checks temperature range [0,2],
max_tokens > 0, max_tool_iterations > 0, gateway port 1-65535, and
non-negative security policy values. Called automatically by LoadConfig.
This commit is contained in:
Paul De Velder 2026-02-23 15:23:17 +01:00
parent 4dbf3e5fcb
commit 12ba462ae5
2 changed files with 157 additions and 0 deletions

View file

@ -531,9 +531,55 @@ func LoadConfig(path string) (*Config, error) {
return nil, err
}
// Validate configuration values
if err := cfg.Validate(); err != nil {
return nil, err
}
return cfg, nil
}
// Validate checks configuration values for correctness.
func (c *Config) Validate() error {
// Temperature: [0.0, 2.0] if set
if c.Agents.Defaults.Temperature != nil {
t := *c.Agents.Defaults.Temperature
if t < 0.0 || t > 2.0 {
return fmt.Errorf("agents.defaults.temperature must be between 0.0 and 2.0, got %v", t)
}
}
// MaxTokens > 0
if c.Agents.Defaults.MaxTokens <= 0 {
return fmt.Errorf("agents.defaults.max_tokens must be > 0, got %d", c.Agents.Defaults.MaxTokens)
}
// MaxToolIterations > 0
if c.Agents.Defaults.MaxToolIterations <= 0 {
return fmt.Errorf("agents.defaults.max_tool_iterations must be > 0, got %d", c.Agents.Defaults.MaxToolIterations)
}
// Gateway port: 1-65535
if c.Gateway.Port < 1 || c.Gateway.Port > 65535 {
return fmt.Errorf("gateway.port must be between 1 and 65535, got %d", c.Gateway.Port)
}
// Security: non-negative values
if c.Tools.Security.DefaultMaxArgSize < 0 {
return fmt.Errorf("tools.security.default_max_arg_size must be >= 0, got %d", c.Tools.Security.DefaultMaxArgSize)
}
for name, policy := range c.Tools.Security.ToolPolicies {
if policy.MaxCallsPerMin < 0 {
return fmt.Errorf("tools.security.tool_policies[%s].max_calls_per_min must be >= 0, got %d", name, policy.MaxCallsPerMin)
}
if policy.MaxArgSize < 0 {
return fmt.Errorf("tools.security.tool_policies[%s].max_arg_size must be >= 0, got %d", name, policy.MaxArgSize)
}
}
return nil
}
func SaveConfig(path string, cfg *Config) error {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {

View file

@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
@ -392,3 +393,113 @@ func TestLoadConfig_OpenAIWebSearchCanBeDisabled(t *testing.T) {
t.Fatal("OpenAI codex web search should be false when disabled in config file")
}
}
// --- Config Validate() tests ---
func TestValidate_DefaultConfig(t *testing.T) {
cfg := DefaultConfig()
if err := cfg.Validate(); err != nil {
t.Fatalf("DefaultConfig should be valid, got: %v", err)
}
}
func TestValidate_TemperatureTooHigh(t *testing.T) {
cfg := DefaultConfig()
temp := 3.0
cfg.Agents.Defaults.Temperature = &temp
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for temperature > 2.0")
}
if !strings.Contains(err.Error(), "temperature") {
t.Errorf("Error should mention temperature, got: %v", err)
}
}
func TestValidate_TemperatureNegative(t *testing.T) {
cfg := DefaultConfig()
temp := -0.1
cfg.Agents.Defaults.Temperature = &temp
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for negative temperature")
}
}
func TestValidate_TemperatureValid(t *testing.T) {
cfg := DefaultConfig()
temp := 0.7
cfg.Agents.Defaults.Temperature = &temp
if err := cfg.Validate(); err != nil {
t.Fatalf("Temperature 0.7 should be valid, got: %v", err)
}
}
func TestValidate_MaxTokensZero(t *testing.T) {
cfg := DefaultConfig()
cfg.Agents.Defaults.MaxTokens = 0
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for MaxTokens = 0")
}
if !strings.Contains(err.Error(), "max_tokens") {
t.Errorf("Error should mention max_tokens, got: %v", err)
}
}
func TestValidate_MaxTokensNegative(t *testing.T) {
cfg := DefaultConfig()
cfg.Agents.Defaults.MaxTokens = -1
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for negative MaxTokens")
}
}
func TestValidate_MaxToolIterationsZero(t *testing.T) {
cfg := DefaultConfig()
cfg.Agents.Defaults.MaxToolIterations = 0
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for MaxToolIterations = 0")
}
}
func TestValidate_GatewayPortInvalid(t *testing.T) {
cfg := DefaultConfig()
cfg.Gateway.Port = 0
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for port = 0")
}
if !strings.Contains(err.Error(), "port") {
t.Errorf("Error should mention port, got: %v", err)
}
}
func TestValidate_GatewayPortTooHigh(t *testing.T) {
cfg := DefaultConfig()
cfg.Gateway.Port = 70000
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for port > 65535")
}
}
func TestValidate_SecurityMaxArgSizeNegative(t *testing.T) {
cfg := DefaultConfig()
cfg.Tools.Security.DefaultMaxArgSize = -1
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for negative DefaultMaxArgSize")
}
}
func TestValidate_SecurityMaxCallsPerMinNegative(t *testing.T) {
cfg := DefaultConfig()
cfg.Tools.Security.ToolPolicies["exec"] = ToolPolicyConfig{MaxCallsPerMin: -1}
err := cfg.Validate()
if err == nil {
t.Fatal("Expected error for negative MaxCallsPerMin")
}
}