@@ -16,12 +16,13 @@ use codex_protocol::protocol::SandboxPolicy;
1616use thiserror:: Error ;
1717use tokio:: fs;
1818use tokio:: sync:: RwLock ;
19+ use tokio:: task:: spawn_blocking;
1920
2021use crate :: bash:: parse_shell_lc_plain_commands;
2122use crate :: features:: Feature ;
2223use crate :: features:: Features ;
2324use crate :: sandboxing:: SandboxPermissions ;
24- use crate :: tools:: sandboxing:: ApprovalRequirement ;
25+ use crate :: tools:: sandboxing:: ExecApprovalRequirement ;
2526
2627const FORBIDDEN_REASON : & str = "execpolicy forbids this command" ;
2728const PROMPT_REASON : & str = "execpolicy requires approval for this command" ;
@@ -55,6 +56,9 @@ pub enum ExecPolicyUpdateError {
5556 #[ error( "failed to update execpolicy file {path}: {source}" ) ]
5657 AppendRule { path : PathBuf , source : AmendError } ,
5758
59+ #[ error( "failed to join blocking execpolicy update task: {source}" ) ]
60+ JoinBlockingTask { source : tokio:: task:: JoinError } ,
61+
5862 #[ error( "failed to update in-memory execpolicy: {source}" ) ]
5963 AddRule {
6064 #[ from]
@@ -114,41 +118,47 @@ pub(crate) async fn append_allow_prefix_rule_and_update(
114118 prefix : & [ String ] ,
115119) -> Result < ( ) , ExecPolicyUpdateError > {
116120 let policy_path = default_policy_path ( codex_home) ;
117- blocking_append_allow_prefix_rule ( & policy_path, prefix) . map_err ( |source| {
118- ExecPolicyUpdateError :: AppendRule {
119- path : policy_path,
120- source,
121- }
121+ let prefix = prefix. to_vec ( ) ;
122+ spawn_blocking ( {
123+ let policy_path = policy_path. clone ( ) ;
124+ let prefix = prefix. clone ( ) ;
125+ move || blocking_append_allow_prefix_rule ( & policy_path, & prefix)
126+ } )
127+ . await
128+ . map_err ( |source| ExecPolicyUpdateError :: JoinBlockingTask { source } ) ?
129+ . map_err ( |source| ExecPolicyUpdateError :: AppendRule {
130+ path : policy_path,
131+ source,
122132 } ) ?;
123133
124134 current_policy
125135 . write ( )
126136 . await
127- . add_prefix_rule ( prefix, Decision :: Allow ) ?;
137+ . add_prefix_rule ( & prefix, Decision :: Allow ) ?;
128138
129139 Ok ( ( ) )
130140}
131141
132142fn requirement_from_decision (
133143 decision : Decision ,
134144 approval_policy : AskForApproval ,
135- ) -> ApprovalRequirement {
145+ ) -> ExecApprovalRequirement {
136146 match decision {
137- Decision :: Forbidden => ApprovalRequirement :: Forbidden {
147+ Decision :: Forbidden => ExecApprovalRequirement :: Forbidden {
138148 reason : FORBIDDEN_REASON . to_string ( ) ,
139149 } ,
140150 Decision :: Prompt => {
141151 let reason = PROMPT_REASON . to_string ( ) ;
142152 if matches ! ( approval_policy, AskForApproval :: Never ) {
143- ApprovalRequirement :: Forbidden { reason }
153+ ExecApprovalRequirement :: Forbidden { reason }
144154 } else {
145- ApprovalRequirement :: NeedsApproval {
155+ ExecApprovalRequirement :: NeedsApproval {
146156 reason : Some ( reason) ,
147157 allow_prefix : None ,
148158 }
149159 }
150160 }
151- Decision :: Allow => ApprovalRequirement :: Skip {
161+ Decision :: Allow => ExecApprovalRequirement :: Skip {
152162 bypass_sandbox : true ,
153163 } ,
154164 }
@@ -170,17 +180,16 @@ fn allow_prefix_if_applicable(
170180 }
171181}
172182
173- pub ( crate ) async fn create_approval_requirement_for_command (
183+ pub ( crate ) async fn create_exec_approval_requirement_for_command (
174184 exec_policy : & Arc < RwLock < Policy > > ,
175185 features : & Features ,
176186 command : & [ String ] ,
177187 approval_policy : AskForApproval ,
178188 sandbox_policy : & SandboxPolicy ,
179189 sandbox_permissions : SandboxPermissions ,
180- ) -> ApprovalRequirement {
190+ ) -> ExecApprovalRequirement {
181191 let commands = parse_shell_lc_plain_commands ( command) . unwrap_or_else ( || vec ! [ command. to_vec( ) ] ) ;
182- let policy = exec_policy. read ( ) . await ;
183- let evaluation = policy. check_multiple ( commands. iter ( ) ) ;
192+ let evaluation = exec_policy. read ( ) . await . check_multiple ( commands. iter ( ) ) ;
184193
185194 match evaluation {
186195 Evaluation :: Match { decision, .. } => requirement_from_decision ( decision, approval_policy) ,
@@ -191,12 +200,12 @@ pub(crate) async fn create_approval_requirement_for_command(
191200 command,
192201 sandbox_permissions,
193202 ) {
194- ApprovalRequirement :: NeedsApproval {
203+ ExecApprovalRequirement :: NeedsApproval {
195204 reason : None ,
196205 allow_prefix : allow_prefix_if_applicable ( & commands, features) ,
197206 }
198207 } else {
199- ApprovalRequirement :: Skip {
208+ ExecApprovalRequirement :: Skip {
200209 bypass_sandbox : false ,
201210 }
202211 }
@@ -349,7 +358,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
349358 "rm -rf /tmp" . to_string( ) ,
350359 ] ;
351360
352- let requirement = create_approval_requirement_for_command (
361+ let requirement = create_exec_approval_requirement_for_command (
353362 & policy,
354363 & Features :: with_defaults ( ) ,
355364 & forbidden_script,
@@ -361,14 +370,14 @@ prefix_rule(pattern=["rm"], decision="forbidden")
361370
362371 assert_eq ! (
363372 requirement,
364- ApprovalRequirement :: Forbidden {
373+ ExecApprovalRequirement :: Forbidden {
365374 reason: FORBIDDEN_REASON . to_string( )
366375 }
367376 ) ;
368377 }
369378
370379 #[ tokio:: test]
371- async fn approval_requirement_prefers_execpolicy_match ( ) {
380+ async fn exec_approval_requirement_prefers_execpolicy_match ( ) {
372381 let policy_src = r#"prefix_rule(pattern=["rm"], decision="prompt")"# ;
373382 let mut parser = PolicyParser :: new ( ) ;
374383 parser
@@ -377,7 +386,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
377386 let policy = Arc :: new ( RwLock :: new ( parser. build ( ) ) ) ;
378387 let command = vec ! [ "rm" . to_string( ) ] ;
379388
380- let requirement = create_approval_requirement_for_command (
389+ let requirement = create_exec_approval_requirement_for_command (
381390 & policy,
382391 & Features :: with_defaults ( ) ,
383392 & command,
@@ -389,15 +398,15 @@ prefix_rule(pattern=["rm"], decision="forbidden")
389398
390399 assert_eq ! (
391400 requirement,
392- ApprovalRequirement :: NeedsApproval {
401+ ExecApprovalRequirement :: NeedsApproval {
393402 reason: Some ( PROMPT_REASON . to_string( ) ) ,
394403 allow_prefix: None ,
395404 }
396405 ) ;
397406 }
398407
399408 #[ tokio:: test]
400- async fn approval_requirement_respects_approval_policy ( ) {
409+ async fn exec_approval_requirement_respects_approval_policy ( ) {
401410 let policy_src = r#"prefix_rule(pattern=["rm"], decision="prompt")"# ;
402411 let mut parser = PolicyParser :: new ( ) ;
403412 parser
@@ -406,7 +415,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
406415 let policy = Arc :: new ( RwLock :: new ( parser. build ( ) ) ) ;
407416 let command = vec ! [ "rm" . to_string( ) ] ;
408417
409- let requirement = create_approval_requirement_for_command (
418+ let requirement = create_exec_approval_requirement_for_command (
410419 & policy,
411420 & Features :: with_defaults ( ) ,
412421 & command,
@@ -418,18 +427,18 @@ prefix_rule(pattern=["rm"], decision="forbidden")
418427
419428 assert_eq ! (
420429 requirement,
421- ApprovalRequirement :: Forbidden {
430+ ExecApprovalRequirement :: Forbidden {
422431 reason: PROMPT_REASON . to_string( )
423432 }
424433 ) ;
425434 }
426435
427436 #[ tokio:: test]
428- async fn approval_requirement_falls_back_to_heuristics ( ) {
429- let command = vec ! [ "python " . to_string( ) ] ;
437+ async fn exec_approval_requirement_falls_back_to_heuristics ( ) {
438+ let command = vec ! [ "cargo" . to_string ( ) , "build ". to_string( ) ] ;
430439
431440 let empty_policy = Arc :: new ( RwLock :: new ( Policy :: empty ( ) ) ) ;
432- let requirement = create_approval_requirement_for_command (
441+ let requirement = create_exec_approval_requirement_for_command (
433442 & empty_policy,
434443 & Features :: with_defaults ( ) ,
435444 & command,
@@ -441,7 +450,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
441450
442451 assert_eq ! (
443452 requirement,
444- ApprovalRequirement :: NeedsApproval {
453+ ExecApprovalRequirement :: NeedsApproval {
445454 reason: None ,
446455 allow_prefix: Some ( command)
447456 }
@@ -499,10 +508,10 @@ prefix_rule(pattern=["rm"], decision="forbidden")
499508
500509 #[ tokio:: test]
501510 async fn allow_prefix_is_present_for_single_command_without_policy_match ( ) {
502- let command = vec ! [ "python " . to_string( ) ] ;
511+ let command = vec ! [ "cargo" . to_string ( ) , "build ". to_string( ) ] ;
503512
504513 let empty_policy = Arc :: new ( RwLock :: new ( Policy :: empty ( ) ) ) ;
505- let requirement = create_approval_requirement_for_command (
514+ let requirement = create_exec_approval_requirement_for_command (
506515 & empty_policy,
507516 & Features :: with_defaults ( ) ,
508517 & command,
@@ -514,7 +523,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
514523
515524 assert_eq ! (
516525 requirement,
517- ApprovalRequirement :: NeedsApproval {
526+ ExecApprovalRequirement :: NeedsApproval {
518527 reason: None ,
519528 allow_prefix: Some ( command)
520529 }
@@ -523,12 +532,12 @@ prefix_rule(pattern=["rm"], decision="forbidden")
523532
524533 #[ tokio:: test]
525534 async fn allow_prefix_is_disabled_when_execpolicy_feature_disabled ( ) {
526- let command = vec ! [ "python " . to_string( ) ] ;
535+ let command = vec ! [ "cargo" . to_string ( ) , "build ". to_string( ) ] ;
527536
528537 let mut features = Features :: with_defaults ( ) ;
529538 features. disable ( Feature :: ExecPolicy ) ;
530539
531- let requirement = create_approval_requirement_for_command (
540+ let requirement = create_exec_approval_requirement_for_command (
532541 & Arc :: new ( RwLock :: new ( Policy :: empty ( ) ) ) ,
533542 & features,
534543 & command,
@@ -540,7 +549,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
540549
541550 assert_eq ! (
542551 requirement,
543- ApprovalRequirement :: NeedsApproval {
552+ ExecApprovalRequirement :: NeedsApproval {
544553 reason: None ,
545554 allow_prefix: None ,
546555 }
@@ -557,7 +566,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
557566 let policy = Arc :: new ( RwLock :: new ( parser. build ( ) ) ) ;
558567 let command = vec ! [ "rm" . to_string( ) ] ;
559568
560- let requirement = create_approval_requirement_for_command (
569+ let requirement = create_exec_approval_requirement_for_command (
561570 & policy,
562571 & Features :: with_defaults ( ) ,
563572 & command,
@@ -569,7 +578,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
569578
570579 assert_eq ! (
571580 requirement,
572- ApprovalRequirement :: NeedsApproval {
581+ ExecApprovalRequirement :: NeedsApproval {
573582 reason: Some ( PROMPT_REASON . to_string( ) ) ,
574583 allow_prefix: None ,
575584 }
@@ -581,9 +590,9 @@ prefix_rule(pattern=["rm"], decision="forbidden")
581590 let command = vec ! [
582591 "bash" . to_string( ) ,
583592 "-lc" . to_string( ) ,
584- "python && echo ok" . to_string( ) ,
593+ "cargo build && echo ok" . to_string( ) ,
585594 ] ;
586- let requirement = create_approval_requirement_for_command (
595+ let requirement = create_exec_approval_requirement_for_command (
587596 & Arc :: new ( RwLock :: new ( Policy :: empty ( ) ) ) ,
588597 & Features :: with_defaults ( ) ,
589598 & command,
@@ -595,7 +604,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
595604
596605 assert_eq ! (
597606 requirement,
598- ApprovalRequirement :: NeedsApproval {
607+ ExecApprovalRequirement :: NeedsApproval {
599608 reason: None ,
600609 allow_prefix: None ,
601610 }
0 commit comments