@@ -702,3 +702,147 @@ async fn replay_fixture_blocks_egress_policy_rule_mismatches() {
702702
703703 server. abort ( ) ;
704704}
705+
706+ #[ tokio:: test]
707+ #[ serial]
708+ async fn replay_fixture_blocks_egress_policy_path_prefix_mismatches ( ) {
709+ let fixture = support:: openai_compat_fixture:: load_json_array ( include_str ! (
710+ "fixtures/replay/session_egress_policy_path_block.json"
711+ ) ) ;
712+ let server = support:: openai_compat_fixture:: FixtureServer :: spawn ( fixture) . await ;
713+
714+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
715+ let security = rexos:: security:: SecurityConfig {
716+ egress : EgressConfig {
717+ rules : vec ! [ EgressRule {
718+ tool: "web_fetch" . to_string( ) ,
719+ host: "127.0.0.1" . to_string( ) ,
720+ path_prefix: "/ok" . to_string( ) ,
721+ methods: vec![ "GET" . to_string( ) ] ,
722+ } ] ,
723+ } ,
724+ ..Default :: default ( )
725+ } ;
726+
727+ let ( agent, _paths, workspace_root) = fixture_agent ( & tmp, server. base_url . clone ( ) , security) ;
728+
729+ let session_id = "s-replay-egress-path" ;
730+ agent
731+ . set_session_allowed_tools ( session_id, vec ! [ "web_fetch" . to_string( ) ] )
732+ . unwrap ( ) ;
733+
734+ let err = agent
735+ . run_session (
736+ workspace_root,
737+ session_id,
738+ None ,
739+ "fetch disallowed path" ,
740+ TaskKind :: Coding ,
741+ )
742+ . await
743+ . unwrap_err ( ) ;
744+ let err_text = err. to_string ( ) ;
745+ assert ! (
746+ err_text. contains( "egress path not allowed" ) ,
747+ "expected egress path block, got: {err_text}"
748+ ) ;
749+ assert ! (
750+ err_text. contains( "web_fetch" ) ,
751+ "expected tool name in error, got: {err_text}"
752+ ) ;
753+
754+ let requests = server. requests . lock ( ) . unwrap ( ) . clone ( ) ;
755+ assert_eq ! ( requests. len( ) , 1 , "expected one chat completions call" ) ;
756+ assert_eq ! (
757+ compact_request( & requests[ 0 ] ) ,
758+ json!( {
759+ "model" : "fixture-model" ,
760+ "temperature" : 0.0 ,
761+ "tools" : [ {
762+ "name" : "web_fetch" ,
763+ "type" : "function" ,
764+ "param_type" : "object" ,
765+ "required" : [ "url" ] ,
766+ "properties" : [ "allow_private" , "max_bytes" , "timeout_ms" , "url" ] ,
767+ "additional_properties" : false ,
768+ } ] ,
769+ "message_roles" : [ "user" ] ,
770+ "assistant_tool_calls" : [ ] ,
771+ "tool_messages" : [ ] ,
772+ } )
773+ ) ;
774+
775+ server. abort ( ) ;
776+ }
777+
778+ #[ tokio:: test]
779+ #[ serial]
780+ async fn replay_fixture_blocks_egress_policy_method_mismatches ( ) {
781+ let fixture = support:: openai_compat_fixture:: load_json_array ( include_str ! (
782+ "fixtures/replay/session_egress_policy_method_block.json"
783+ ) ) ;
784+ let server = support:: openai_compat_fixture:: FixtureServer :: spawn ( fixture) . await ;
785+
786+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
787+ let security = rexos:: security:: SecurityConfig {
788+ egress : EgressConfig {
789+ rules : vec ! [ EgressRule {
790+ tool: "a2a_send" . to_string( ) ,
791+ host: "127.0.0.1" . to_string( ) ,
792+ path_prefix: "/" . to_string( ) ,
793+ methods: vec![ "GET" . to_string( ) ] ,
794+ } ] ,
795+ } ,
796+ ..Default :: default ( )
797+ } ;
798+
799+ let ( agent, _paths, workspace_root) = fixture_agent ( & tmp, server. base_url . clone ( ) , security) ;
800+
801+ let session_id = "s-replay-egress-method" ;
802+ agent
803+ . set_session_allowed_tools ( session_id, vec ! [ "a2a_send" . to_string( ) ] )
804+ . unwrap ( ) ;
805+
806+ let err = agent
807+ . run_session (
808+ workspace_root,
809+ session_id,
810+ None ,
811+ "send a2a disallowed method" ,
812+ TaskKind :: Coding ,
813+ )
814+ . await
815+ . unwrap_err ( ) ;
816+ let err_text = err. to_string ( ) ;
817+ assert ! (
818+ err_text. contains( "egress method not allowed" ) ,
819+ "expected egress method block, got: {err_text}"
820+ ) ;
821+ assert ! (
822+ err_text. contains( "a2a_send" ) ,
823+ "expected tool name in error, got: {err_text}"
824+ ) ;
825+
826+ let requests = server. requests . lock ( ) . unwrap ( ) . clone ( ) ;
827+ assert_eq ! ( requests. len( ) , 1 , "expected one chat completions call" ) ;
828+ assert_eq ! (
829+ compact_request( & requests[ 0 ] ) ,
830+ json!( {
831+ "model" : "fixture-model" ,
832+ "temperature" : 0.0 ,
833+ "tools" : [ {
834+ "name" : "a2a_send" ,
835+ "type" : "function" ,
836+ "param_type" : "object" ,
837+ "required" : [ "message" ] ,
838+ "properties" : [ "agent_url" , "allow_private" , "message" , "session_id" , "url" ] ,
839+ "additional_properties" : false ,
840+ } ] ,
841+ "message_roles" : [ "user" ] ,
842+ "assistant_tool_calls" : [ ] ,
843+ "tool_messages" : [ ] ,
844+ } )
845+ ) ;
846+
847+ server. abort ( ) ;
848+ }
0 commit comments