From 6dafcbfd9525efb834eb83c637f3b3ee2640f2b7 Mon Sep 17 00:00:00 2001 From: muhtasham Date: Mon, 22 Jun 2026 16:24:44 -0700 Subject: [PATCH] Harden CybORG agent validation --- codeclash/arenas/cyborg/cyborg.py | 14 +++++++ tests/arenas/test_cyborg.py | 61 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/codeclash/arenas/cyborg/cyborg.py b/codeclash/arenas/cyborg/cyborg.py index e3ab83a..d33a457 100644 --- a/codeclash/arenas/cyborg/cyborg.py +++ b/codeclash/arenas/cyborg/cyborg.py @@ -63,6 +63,20 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]: "assert hasattr(module, 'MyAgent'), 'MyAgent class not found'\n" "from CybORG.Agents import BaseAgent\n" "assert issubclass(module.MyAgent, BaseAgent), 'MyAgent must inherit from a CybORG BaseAgent class'\n" + "def make_agent(agent_class, agent_name):\n" + " try:\n" + " return agent_class(name=agent_name)\n" + " except TypeError:\n" + " try:\n" + " return agent_class(agent_name)\n" + " except TypeError:\n" + " try:\n" + " return agent_class()\n" + " except TypeError as final_error:\n" + " raise TypeError(\n" + " 'MyAgent could not be constructed with the CybORG runtime constructor fallbacks'\n" + " ) from final_error\n" + "make_agent(module.MyAgent, 'validation-agent')\n" "PY" ) if import_check["returncode"] != 0: diff --git a/tests/arenas/test_cyborg.py b/tests/arenas/test_cyborg.py index 7464b4c..fca086e 100644 --- a/tests/arenas/test_cyborg.py +++ b/tests/arenas/test_cyborg.py @@ -66,6 +66,67 @@ def test_import_failure(self, mock_player_factory): assert valid is False assert "Could not import" in error + def test_validation_instantiates_agent_with_runtime_fallbacks(self, mock_player_factory): + arena = CybORGArena.__new__(CybORGArena) + arena.submission = "cyborg_agent.py" + player = mock_player_factory( + name="Alice", + files={"cyborg_agent.py": "from CybORG.Agents import RandomAgent\nclass MyAgent(RandomAgent):\n pass\n"}, + command_outputs={ + "test -f cyborg_agent.py && echo exists": {"output": "exists\n", "returncode": 0}, + "cat cyborg_agent.py": { + "output": "from CybORG.Agents import RandomAgent\nclass MyAgent(RandomAgent):\n pass\n", + "returncode": 0, + }, + "python -m py_compile cyborg_agent.py": {"output": "", "returncode": 0}, + }, + ) + + valid, error = arena.validate_code(player) + + import_command = player.environment._executed_commands[-1] + assert valid is True + assert error is None + assert "make_agent(module.MyAgent, 'validation-agent')" in import_command + + def test_validation_rejects_agent_constructor_failure(self, mock_player_factory): + arena = CybORGArena.__new__(CybORGArena) + arena.submission = "cyborg_agent.py" + player = mock_player_factory( + name="Alice", + files={ + "cyborg_agent.py": ( + "from CybORG.Agents import RandomAgent\n" + "class MyAgent(RandomAgent):\n" + " def __init__(self, seed=None):\n" + " super().__init__(seed=seed)\n" + ) + }, + command_outputs={ + "test -f cyborg_agent.py && echo exists": {"output": "exists\n", "returncode": 0}, + "cat cyborg_agent.py": { + "output": ( + "from CybORG.Agents import RandomAgent\n" + "class MyAgent(RandomAgent):\n" + " def __init__(self, seed=None):\n" + " super().__init__(seed=seed)\n" + ), + "returncode": 0, + }, + "python -m py_compile cyborg_agent.py": {"output": "", "returncode": 0}, + "python - <<'PY'": { + "output": "TypeError: RandomAgent.__init__() got an unexpected keyword argument 'seed'", + "returncode": 1, + }, + }, + ) + + valid, error = arena.validate_code(player) + + assert valid is False + assert "Could not import `MyAgent`" in error + assert "unexpected keyword argument 'seed'" in error + class TestCybORGResults: def test_parse_winner(self, tmp_log_dir):