Skip to content

Commit 0e2e0c0

Browse files
committed
test(replay): expand egress policy coverage for path+method
1 parent a27073c commit 0e2e0c0

3 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[
2+
{
3+
"choices": [
4+
{
5+
"index": 0,
6+
"message": {
7+
"role": "assistant",
8+
"content": null,
9+
"tool_calls": [
10+
{
11+
"id": "call_1",
12+
"type": "function",
13+
"function": {
14+
"name": "a2a_send",
15+
"arguments": "{\"agent_url\":\"http://127.0.0.1/\",\"message\":\"hi\",\"allow_private\":true}"
16+
}
17+
}
18+
]
19+
},
20+
"finish_reason": "tool_calls"
21+
}
22+
]
23+
}
24+
]
25+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[
2+
{
3+
"choices": [
4+
{
5+
"index": 0,
6+
"message": {
7+
"role": "assistant",
8+
"content": null,
9+
"tool_calls": [
10+
{
11+
"id": "call_1",
12+
"type": "function",
13+
"function": {
14+
"name": "web_fetch",
15+
"arguments": "{\"url\":\"http://127.0.0.1/nope\",\"allow_private\":true,\"max_bytes\":100}"
16+
}
17+
}
18+
]
19+
},
20+
"finish_reason": "tool_calls"
21+
}
22+
]
23+
}
24+
]
25+

crates/rexos/tests/session_replay.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)