@@ -275,6 +275,86 @@ fn untrusted_channel_context_denies_every_tool_and_private_context() {
275275 assert_eq ! ( params[ "_private_context" ] , false ) ;
276276}
277277
278+ #[ test]
279+ fn untrusted_ceiling_defaults_match_the_unconfigured_behaviour ( ) {
280+ let mut configured = serde_json:: json!( { } ) ;
281+ let mut unconfigured = serde_json:: json!( { } ) ;
282+
283+ apply_untrusted_channel_context_with (
284+ & mut configured,
285+ UntrustedAudience :: default ( ) ,
286+ UntrustedTools :: default ( ) ,
287+ ) ;
288+ apply_untrusted_channel_context ( & mut unconfigured) ;
289+
290+ assert_eq ! (
291+ configured, unconfigured,
292+ "defaults must not change behaviour for an unconfigured account"
293+ ) ;
294+ }
295+
296+ /// The most permissive ceiling leaves the params carrying no tool policy at
297+ /// all, so nothing on the request side can deny `exec`.
298+ ///
299+ /// That is why the `/sh` guard in `dispatch_to_chat` stays tied to
300+ /// `trusted_channel_turn` and not to this ceiling. `run_explicit_shell_command`
301+ /// takes `exec` straight from the request registry, before the
302+ /// `[tools.policy]` layers are consulted, so a guard that widened with the
303+ /// ceiling would hand out an `exec` that no `deny` can take back.
304+ #[ test]
305+ fn the_lifted_ceiling_cannot_deny_exec_on_the_request ( ) {
306+ let mut params = serde_json:: json!( { } ) ;
307+
308+ apply_untrusted_channel_context_with (
309+ & mut params,
310+ UntrustedAudience :: Trusted ,
311+ UntrustedTools :: Policy ,
312+ ) ;
313+
314+ assert ! ( params. get( "_tool_policy" ) . is_none( ) ) ;
315+ assert ! ( params. get( "_tool_audience" ) . is_none( ) ) ;
316+ assert_eq ! ( params[ "_private_context" ] , false ) ;
317+ }
318+
319+ #[ test]
320+ fn each_axis_is_lifted_on_its_own ( ) {
321+ let mut both = serde_json:: json!( { "_private_context" : true } ) ;
322+ apply_untrusted_channel_context_with (
323+ & mut both,
324+ UntrustedAudience :: Trusted ,
325+ UntrustedTools :: Policy ,
326+ ) ;
327+ assert ! ( both. get( "_tool_audience" ) . is_none( ) ) ;
328+ assert ! ( both. get( "_tool_policy" ) . is_none( ) ) ;
329+ assert_eq ! (
330+ both[ "_private_context" ] , false ,
331+ "owner-private context is never configurable for a channel turn"
332+ ) ;
333+
334+ let mut audience_only = serde_json:: json!( { } ) ;
335+ apply_untrusted_channel_context_with (
336+ & mut audience_only,
337+ UntrustedAudience :: Trusted ,
338+ UntrustedTools :: default ( ) ,
339+ ) ;
340+ assert_eq ! (
341+ audience_only[ "_tool_policy" ] [ "deny" ] ,
342+ serde_json:: json!( [ "*" ] ) ,
343+ "lifting the audience alone must still deny every tool by name"
344+ ) ;
345+
346+ let mut tools_only = serde_json:: json!( { } ) ;
347+ apply_untrusted_channel_context_with (
348+ & mut tools_only,
349+ UntrustedAudience :: default ( ) ,
350+ UntrustedTools :: Policy ,
351+ ) ;
352+ assert_eq ! (
353+ tools_only[ "_tool_audience" ] , "public" ,
354+ "dropping the name policy alone must still hold the audience ceiling"
355+ ) ;
356+ }
357+
278358#[ test]
279359fn public_audience_tools_require_explicit_registration ( ) {
280360 const REGISTRATION : & str = include_str ! ( "../server/prepare_core/post_state.rs" ) ;
0 commit comments