@@ -640,6 +640,43 @@ def respond_to_user_tool(
640640 return f"Message sent to user: { message } "
641641
642642
643+ def _resolve_in_workspace (agent : Any , path : str ) -> str :
644+ """Resolve a model-supplied path, refusing anything outside the workspace.
645+
646+ The file tools take ``file_path`` straight from the model, so any prompt
647+ injection reaching the autonomous loop can otherwise read, overwrite or
648+ delete anywhere the host process can. ``realpath`` is what makes the check
649+ hold: it collapses ``..`` segments and follows symlinks before the
650+ containment test, so neither can be used to step outside.
651+
652+ Args:
653+ agent: The agent instance, used for the workspace directory
654+ path: Model-supplied path, relative to the workspace or absolute
655+
656+ Returns:
657+ str: The resolved absolute path, guaranteed inside the workspace
658+
659+ Raises:
660+ ValueError: If the path resolves outside the agent workspace
661+ """
662+ workspace = os .path .realpath (agent ._get_agent_workspace_dir ())
663+ full_path = (
664+ path
665+ if path and os .path .isabs (path )
666+ else os .path .join (workspace , path or "" )
667+ )
668+ resolved = os .path .realpath (full_path )
669+
670+ if resolved != workspace and not resolved .startswith (
671+ workspace + os .sep
672+ ):
673+ raise ValueError (
674+ f"Path is outside the agent workspace and was refused: { path } "
675+ )
676+
677+ return resolved
678+
679+
643680def create_file_tool (
644681 agent : Any , file_path : str , content : str , ** kwargs
645682) -> str :
@@ -656,12 +693,7 @@ def create_file_tool(
656693 str: Path to the created file or error message
657694 """
658695 try :
659- # Resolve path - if relative, use agent workspace
660- if not os .path .isabs (file_path ):
661- workspace_dir = agent ._get_agent_workspace_dir ()
662- full_path = os .path .join (workspace_dir , file_path )
663- else :
664- full_path = file_path
696+ full_path = _resolve_in_workspace (agent , file_path )
665697
666698 # Create parent directories if they don't exist
667699 os .makedirs (os .path .dirname (full_path ), exist_ok = True )
@@ -715,12 +747,7 @@ def update_file_tool(
715747 str: Success message or error message
716748 """
717749 try :
718- # Resolve path - if relative, use agent workspace
719- if not os .path .isabs (file_path ):
720- workspace_dir = agent ._get_agent_workspace_dir ()
721- full_path = os .path .join (workspace_dir , file_path )
722- else :
723- full_path = file_path
750+ full_path = _resolve_in_workspace (agent , file_path )
724751
725752 # Check if file exists
726753 if not os .path .exists (full_path ):
@@ -769,12 +796,7 @@ def read_file_tool(agent: Any, file_path: str, **kwargs) -> str:
769796 str: File contents or error message
770797 """
771798 try :
772- # Resolve path - if relative, use agent workspace
773- if not os .path .isabs (file_path ):
774- workspace_dir = agent ._get_agent_workspace_dir ()
775- full_path = os .path .join (workspace_dir , file_path )
776- else :
777- full_path = file_path
799+ full_path = _resolve_in_workspace (agent , file_path )
778800
779801 # Check if file exists
780802 if not os .path .exists (full_path ):
@@ -819,17 +841,7 @@ def list_directory_tool(
819841 str: Formatted list of directory contents
820842 """
821843 try :
822- # Resolve path - if relative or empty, use agent workspace
823- if not directory_path or not os .path .isabs (directory_path ):
824- workspace_dir = agent ._get_agent_workspace_dir ()
825- if directory_path :
826- full_path = os .path .join (
827- workspace_dir , directory_path
828- )
829- else :
830- full_path = workspace_dir
831- else :
832- full_path = directory_path
844+ full_path = _resolve_in_workspace (agent , directory_path )
833845
834846 # Check if directory exists
835847 if not os .path .exists (full_path ):
@@ -888,12 +900,7 @@ def delete_file_tool(agent: Any, file_path: str, **kwargs) -> str:
888900 str: Success message or error message
889901 """
890902 try :
891- # Resolve path - if relative, use agent workspace
892- if not os .path .isabs (file_path ):
893- workspace_dir = agent ._get_agent_workspace_dir ()
894- full_path = os .path .join (workspace_dir , file_path )
895- else :
896- full_path = file_path
903+ full_path = _resolve_in_workspace (agent , file_path )
897904
898905 # Check if file exists
899906 if not os .path .exists (full_path ):
@@ -1129,16 +1136,7 @@ def grep_tool(
11291136 str: Matching lines or error message
11301137 """
11311138 try :
1132- # Resolve path
1133- if not path or not os .path .isabs (path ):
1134- workspace_dir = agent ._get_agent_workspace_dir ()
1135- full_path = (
1136- os .path .join (workspace_dir , path )
1137- if path
1138- else workspace_dir
1139- )
1140- else :
1141- full_path = path
1139+ full_path = _resolve_in_workspace (agent , path )
11421140
11431141 if not os .path .exists (full_path ):
11441142 return f"Error: Path does not exist: { full_path } "
0 commit comments