2828from __future__ import annotations
2929
3030import argparse
31+ import ast
3132import importlib
3233import json
3334import os
4344 sys .path .insert (0 , str (HERE ))
4445
4546from GameLogic import GameConfig , GameEnvironment
46- from RLInterfaces import BaseAgent
47+ from RLInterfaces import BaseAgent , RestrictedGameEnvironment
48+
49+
50+ _ALLOWED_ENV_ATTRS = frozenset ({"reset" , "step" , "action_masks" })
51+ _ALLOWED_RL_IMPORTS = {
52+ "RLInterfaces" ,
53+ "RLInterfaces.base_agent" ,
54+ }
55+
56+
57+ class SubmissionRuleError (RuntimeError ):
58+ """Raised when an agent source file uses evaluator-forbidden interfaces."""
59+
60+
61+ class _AgentRuleVisitor (ast .NodeVisitor ):
62+ def __init__ (self ):
63+ self .errors : List [str ] = []
64+ self ._env_aliases = [{"env" }]
65+
66+ @property
67+ def env_aliases (self ) -> set :
68+ return self ._env_aliases [- 1 ]
69+
70+ def _is_env_expr (self , node : ast .AST ) -> bool :
71+ if isinstance (node , ast .Name ):
72+ return node .id in self .env_aliases
73+ return (
74+ isinstance (node , ast .Attribute )
75+ and node .attr == "env"
76+ and isinstance (node .value , ast .Name )
77+ and node .value .id == "self"
78+ )
79+
80+ def _error (self , node : ast .AST , message : str ) -> None :
81+ self .errors .append (f"line { getattr (node , 'lineno' , '?' )} : { message } " )
82+
83+ def visit_ImportFrom (self , node : ast .ImportFrom ) -> None :
84+ module = node .module or ""
85+ if module .startswith ("GameLogic" ):
86+ self ._error (node , "imports from GameLogic are not allowed in submissions" )
87+ if module .startswith ("RLInterfaces" ) and module not in _ALLOWED_RL_IMPORTS :
88+ self ._error (
89+ node ,
90+ "only BaseAgent may be imported from RLInterfaces by submissions" ,
91+ )
92+ if module in _ALLOWED_RL_IMPORTS :
93+ for alias in node .names :
94+ if alias .name != "BaseAgent" :
95+ self ._error (
96+ node ,
97+ "only BaseAgent may be imported from RLInterfaces by submissions" ,
98+ )
99+ self .generic_visit (node )
100+
101+ def visit_Import (self , node : ast .Import ) -> None :
102+ for alias in node .names :
103+ if alias .name == "GameLogic" or alias .name .startswith ("GameLogic." ):
104+ self ._error (node , "imports from GameLogic are not allowed in submissions" )
105+ if alias .name == "RLInterfaces" or (
106+ alias .name .startswith ("RLInterfaces." ) and alias .name not in _ALLOWED_RL_IMPORTS
107+ ):
108+ self ._error (
109+ node ,
110+ "use 'from RLInterfaces import BaseAgent' instead of importing RLInterfaces modules" ,
111+ )
112+ self .generic_visit (node )
113+
114+ def visit_FunctionDef (self , node : ast .FunctionDef ) -> None :
115+ self ._env_aliases .append (set (self .env_aliases ))
116+ self .generic_visit (node )
117+ self ._env_aliases .pop ()
118+
119+ visit_AsyncFunctionDef = visit_FunctionDef
120+
121+ def visit_Assign (self , node : ast .Assign ) -> None :
122+ if self ._is_env_expr (node .value ):
123+ for target in node .targets :
124+ if isinstance (target , ast .Name ):
125+ self .env_aliases .add (target .id )
126+ self .generic_visit (node )
127+
128+ def visit_AnnAssign (self , node : ast .AnnAssign ) -> None :
129+ if node .value is not None and self ._is_env_expr (node .value ):
130+ if isinstance (node .target , ast .Name ):
131+ self .env_aliases .add (node .target .id )
132+ self .generic_visit (node )
133+
134+ def visit_Attribute (self , node : ast .Attribute ) -> None :
135+ if self ._is_env_expr (node .value ) and node .attr not in _ALLOWED_ENV_ATTRS :
136+ self ._error (
137+ node ,
138+ f"env.{ node .attr } is not allowed; use only reset(), step(), action_masks(), obs, and info" ,
139+ )
140+ self .generic_visit (node )
141+
142+ def visit_Call (self , node : ast .Call ) -> None :
143+ if (
144+ isinstance (node .func , ast .Name )
145+ and node .func .id == "getattr"
146+ and len (node .args ) >= 2
147+ and self ._is_env_expr (node .args [0 ])
148+ ):
149+ attr_arg = node .args [1 ]
150+ if not (
151+ isinstance (attr_arg , ast .Constant )
152+ and isinstance (attr_arg .value , str )
153+ and attr_arg .value in _ALLOWED_ENV_ATTRS
154+ ):
155+ self ._error (
156+ node ,
157+ "dynamic getattr on env is not allowed except for reset/step/action_masks" ,
158+ )
159+ self .generic_visit (node )
160+
161+
162+ def validate_agent_source (agent_file : Path ) -> None :
163+ tree = ast .parse (agent_file .read_text (encoding = "utf-8-sig" ), filename = str (agent_file ))
164+ visitor = _AgentRuleVisitor ()
165+ visitor .visit (tree )
166+ if visitor .errors :
167+ detail = "\n " .join (visitor .errors )
168+ raise SubmissionRuleError (
169+ "Submission uses forbidden PvE interfaces. "
170+ "Agents may only interact with the environment through "
171+ "reset(), step(), action_masks(), and the returned obs/info.\n "
172+ f"{ detail } "
173+ )
47174
48175
49176def load_agent (submission_dir : str , model_path : Optional [str ], env : GameEnvironment ) -> BaseAgent :
@@ -64,6 +191,7 @@ def load_agent(submission_dir: str, model_path: Optional[str], env: GameEnvironm
64191 f"{ agent_file } not found. "
65192 "Your submission must contain agent.py with class Agent(BaseAgent)."
66193 )
194+ validate_agent_source (agent_file )
67195
68196 if str (sub ) not in sys .path :
69197 sys .path .insert (0 , str (sub ))
@@ -89,17 +217,17 @@ def load_agent(submission_dir: str, model_path: Optional[str], env: GameEnvironm
89217 "Agent class must implement load(cls, path, env) classmethod "
90218 "when a model file is provided."
91219 )
92- return load_fn (model_path , env )
220+ return load_fn (model_path , RestrictedGameEnvironment ( env ) )
93221
94222 # No model file → rule-based bot or agent that doesn't need weight loading
95223 # Try classmethod load with None first, then fall back to constructor
96224 load_fn = getattr (AgentClass , "load" , None )
97225 if load_fn is not None :
98226 try :
99- return load_fn (None , env )
227+ return load_fn (None , RestrictedGameEnvironment ( env ) )
100228 except (TypeError , ValueError , NotImplementedError , FileNotFoundError ):
101229 pass
102- return AgentClass (env )
230+ return AgentClass (RestrictedGameEnvironment ( env ) )
103231
104232
105233def evaluate (
0 commit comments