From 83e293a8e173668eb9fbbecc4e3e7bf785a79fb7 Mon Sep 17 00:00:00 2001 From: H4ck3r-netizen Date: Mon, 25 May 2026 06:22:51 +0000 Subject: [PATCH] fix(registry): reject unknown config fields to prevent configuration drift - Add ALLOWED_CONFIG_FIELDS set defining permitted config keys - Add _validate_config() method that rejects unknown fields - register() now validates config before storing agent - Raise UnknownRegistryFieldError with descriptive message on invalid fields - Log rejected fields for audit purposes - Add 7 regression tests covering valid, empty, unknown, stale, and all-allowed cases Fixes #4055 --- src/agent/registry.py | 36 ++++++++++++++++++- tests/test_registry_fields.py | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/test_registry_fields.py diff --git a/src/agent/registry.py b/src/agent/registry.py index fbedbe170..146cbf074 100644 --- a/src/agent/registry.py +++ b/src/agent/registry.py @@ -1,11 +1,25 @@ """Agent Registry — Manages agent lifecycle and metadata.""" import json +import logging import time import uuid from enum import Enum from typing import Any, Dict, List, Optional +logger = logging.getLogger(__name__) + +# Allowed fields in agent configuration +ALLOWED_CONFIG_FIELDS = { + "image", "command", "args", "env", "resources", + "volumes", "network", "labels", "secrets", + "schedule", "retries", "timeout", "description", +} + +# Required fields for agent class +REQUIRED_AGENT_FIELDS = {"name", "type"} +ALLOWED_AGENT_FIELDS = {"name", "type", "config", "description", "version"} + class AgentStatus(Enum): PENDING = "pending" @@ -16,13 +30,33 @@ class AgentStatus(Enum): TERMINATED = "terminated" +class UnknownRegistryFieldError(ValueError): + """Raised when an unknown field is provided to the registry.""" + pass + + class AgentRegistry: def __init__(self, storage_backend: str = "memory"): self.storage_backend = storage_backend self._agents: Dict[str, Dict[str, Any]] = {} self._index: Dict[str, List[str]] = {} + def _validate_config(self, config: Dict) -> Dict: + """Validate config fields, rejecting unknown keys.""" + if not isinstance(config, dict): + raise UnknownRegistryFieldError("Config must be a dictionary") + unknown = set(config.keys()) - ALLOWED_CONFIG_FIELDS + if unknown: + logger.warning("Rejecting unknown config fields: %s", unknown) + raise UnknownRegistryFieldError( + f"Unknown config fields: {unknown}. " + f"Allowed fields: {ALLOWED_CONFIG_FIELDS}" + ) + return config + def register(self, name: str, agent_type: str, config: Optional[Dict] = None) -> str: + # Validate config fields to prevent configuration drift + validated_config = self._validate_config(config or {}) agent_id = str(uuid.uuid4()) timestamp = time.time() self._agents[agent_id] = { @@ -30,7 +64,7 @@ def register(self, name: str, agent_type: str, config: Optional[Dict] = None) -> "name": name, "type": agent_type, "status": AgentStatus.PENDING.value, - "config": config or {}, + "config": validated_config, "created_at": timestamp, "updated_at": timestamp, "version": "1.0.0", diff --git a/tests/test_registry_fields.py b/tests/test_registry_fields.py new file mode 100644 index 000000000..810c03d42 --- /dev/null +++ b/tests/test_registry_fields.py @@ -0,0 +1,67 @@ +"""Tests for registry unknown field rejection.""" + +import pytest +from src.agent.registry import AgentRegistry, UnknownRegistryFieldError, ALLOWED_CONFIG_FIELDS + + +class TestRegistryRejectUnknownFields: + """Regression tests for #4055 - Reject unknown registry fields.""" + + def setup_method(self): + self.registry = AgentRegistry() + + def test_register_with_valid_config(self): + """Registering with valid config fields should succeed.""" + agent_id = self.registry.register( + "test-agent", "worker", + config={"image": "python:3.11", "command": "run"} + ) + agent = self.registry.get(agent_id) + assert agent is not None + assert agent["config"]["image"] == "python:3.11" + + def test_register_with_empty_config(self): + """Registering with empty config should succeed.""" + agent_id = self.registry.register("test-agent", "worker") + agent = self.registry.get(agent_id) + assert agent is not None + assert agent["config"] == {} + + def test_register_rejects_unknown_config_field(self): + """Registering with an unknown config field should raise an error.""" + with pytest.raises(UnknownRegistryFieldError, match="Unknown config fields"): + self.registry.register( + "test-agent", "worker", + config={"image": "python:3.11", "bogus_field_xyz": "value"} + ) + + def test_register_rejects_multiple_unknown_fields(self): + """Registering with multiple unknown fields should list all of them.""" + with pytest.raises(UnknownRegistryFieldError, match="bogus1"): + self.registry.register( + "test-agent", "worker", + config={"bogus1": 1, "bogus2": 2} + ) + + def test_register_rejects_stale_field(self): + """A stale/deprecated config field should be rejected (configuration drift).""" + with pytest.raises(UnknownRegistryFieldError): + self.registry.register( + "test-agent", "worker", + config={"image": "python:3.11", "deprecated_key": "old_value"} + ) + + def test_config_not_mutated(self): + """Original config dict should not be mutated.""" + config = {"image": "python:3.11"} + original = config.copy() + self.registry.register("test-agent", "worker", config=config) + assert config == original + + def test_all_allowed_fields_accepted(self): + """All fields in ALLOWED_CONFIG_FIELDS should be accepted.""" + config = {field: f"val_{field}" for field in ALLOWED_CONFIG_FIELDS} + agent_id = self.registry.register("test-agent", "worker", config=config) + agent = self.registry.get(agent_id) + assert agent is not None + assert set(agent["config"].keys()) == ALLOWED_CONFIG_FIELDS