1- use std:: collections:: BTreeMap ;
2- use std:: path:: Path ;
3- use std:: path:: PathBuf ;
4- use std:: time:: Duration ;
1+ use std:: {
2+ collections:: BTreeMap ,
3+ env,
4+ ffi:: { OsStr , OsString } ,
5+ fs,
6+ path:: { Path , PathBuf } ,
7+ time:: Duration ,
8+ } ;
59
610use firma_runtime_state:: RuntimeLayout ;
711use firma_secret_provider:: IntegrationSpec ;
@@ -24,6 +28,10 @@ const DEFAULT_SIDECAR_STARTUP_TIMEOUT_SECS: u64 = 10;
2428#[ doc( hidden) ]
2529pub mod vscode;
2630
31+ /// Secret-mediation shim injection (Unix/bwrap). Wires the broker + shim mounts
32+ /// into a launch when the profile lists `secret_providers`.
33+ mod secret_shims;
34+
2735/// Lib-level input for [`execute_run`]. The CLI layer (in the `firma`
2836/// host crate) builds this from its `clap`-derived args struct.
2937#[ derive( Debug , Clone ) ]
@@ -226,8 +234,14 @@ pub fn execute_run(args: &RunInput, hooks: &LaunchHooks<'_>) -> Result<i32, RunE
226234 if let CapabilitySource :: File { ref path } = profile. capability . source {
227235 flags. capability_seed_path = Some ( path. clone ( ) ) ;
228236 }
229- let firma_exe = std :: env:: current_exe ( )
237+ let firma_exe = env:: current_exe ( )
230238 . map_err ( |e| RunError :: Internal ( format ! ( "resolve current_exe: {e}" ) ) ) ?;
239+ // Start the secret services before the Sidecar so its gateway address
240+ // is available while the Sidecar config is synthesized.
241+ let gateway_binding = secret_shims:: pre_bind_gateway ( handle_ref, & profile) ?;
242+ if let Some ( binding) = & gateway_binding {
243+ flags. secret_gateway_addr = Some ( binding. addr . clone ( ) ) ;
244+ }
231245 let mut prompt = crate :: authority:: StdAuthorityPrompt ;
232246 let authority = crate :: routing:: resolve_authority (
233247 ResolveAuthorityRequest {
@@ -320,6 +334,14 @@ pub fn execute_run(args: &RunInput, hooks: &LaunchHooks<'_>) -> Result<i32, RunE
320334 . ok_or_else ( || RunError :: Internal ( "sandbox handle missing" . to_string ( ) ) ) ?;
321335 vscode:: ensure_vscode_state_mount ( handle_mut, & state_dir) ;
322336 }
337+ secret_shims:: prepare (
338+ & mut handle,
339+ & profile,
340+ & mut env,
341+ & firma_exe,
342+ env:: var_os ( "PATH" ) . as_deref ( ) ,
343+ gateway_binding. as_ref ( ) ,
344+ ) ?;
323345 let launch = LaunchSpec {
324346 executable,
325347 args : launch_args,
@@ -386,7 +408,7 @@ fn log_run_start(identity: &RunIdentity, profile: &ResolvedProfile) {
386408}
387409
388410fn resolve_working_dir ( ) -> Result < PathBuf , RunError > {
389- std :: env:: current_dir ( )
411+ env:: current_dir ( )
390412 . map_err ( |error| RunError :: Internal ( format ! ( "failed to read current directory: {error}" ) ) )
391413}
392414
@@ -404,7 +426,7 @@ fn combine_run_and_teardown_results(
404426}
405427
406428fn ensure_required_session_identity ( ) -> Result < ( ) , RunError > {
407- let require = std :: env:: var ( "FIRMA_RUN_REQUIRE_SESSION_ID" )
429+ let require = env:: var ( "FIRMA_RUN_REQUIRE_SESSION_ID" )
408430 . ok ( )
409431 . is_some_and ( |v| {
410432 let v = v. trim ( ) . to_ascii_lowercase ( ) ;
@@ -413,7 +435,7 @@ fn ensure_required_session_identity() -> Result<(), RunError> {
413435 if !require {
414436 return Ok ( ( ) ) ;
415437 }
416- let has_session = std :: env:: var ( "FIRMA_RUN_SESSION_ID" )
438+ let has_session = env:: var ( "FIRMA_RUN_SESSION_ID" )
417439 . ok ( )
418440 . is_some_and ( |v| !v. trim ( ) . is_empty ( ) ) ;
419441 if has_session {
@@ -429,9 +451,9 @@ fn maybe_apply_executable_policy(
429451 executable : & str ,
430452 args : Vec < String > ,
431453) -> Vec < String > {
432- let executable = std :: path :: Path :: new ( executable)
454+ let executable = Path :: new ( executable)
433455 . file_name ( )
434- . and_then ( std :: ffi :: OsStr :: to_str)
456+ . and_then ( OsStr :: to_str)
435457 . unwrap_or_default ( )
436458 . to_string ( ) ;
437459 let Some ( policy) = profile. executable_policies . get ( & executable) else {
@@ -507,6 +529,75 @@ fn config_item_matches_key(item: &str, key: &str) -> bool {
507529 item. split_once ( '=' ) . is_some_and ( |( k, _) | k. trim ( ) == key)
508530}
509531
532+ fn resolve_host_executable (
533+ executable : & str ,
534+ host_path : Option < & OsStr > ,
535+ ) -> Result < PathBuf , RunError > {
536+ let candidate = PathBuf :: from ( executable) ;
537+ if candidate
538+ . parent ( )
539+ . is_some_and ( |parent| !parent. as_os_str ( ) . is_empty ( ) )
540+ {
541+ return require_file ( candidate, executable) ;
542+ }
543+
544+ let path_value = host_path
545+ . map ( OsString :: from)
546+ . or_else ( || env:: var_os ( "PATH" ) )
547+ . ok_or_else ( || {
548+ RunError :: ConfigValidation ( format ! (
549+ "cannot resolve executable '{executable}' because host PATH is not set"
550+ ) )
551+ } ) ?;
552+ for dir in env:: split_paths ( & path_value) {
553+ for candidate in executable_search_candidates ( & dir, executable) {
554+ if candidate. is_file ( ) {
555+ return Ok ( candidate) ;
556+ }
557+ }
558+ }
559+ Err ( RunError :: ConfigValidation ( format ! (
560+ "cannot resolve executable '{executable}' on host PATH"
561+ ) ) )
562+ }
563+
564+ fn require_file ( candidate : PathBuf , executable : & str ) -> Result < PathBuf , RunError > {
565+ if candidate. is_file ( ) {
566+ Ok ( candidate)
567+ } else {
568+ Err ( RunError :: ConfigValidation ( format ! (
569+ "cannot resolve executable '{executable}' at {}" ,
570+ candidate. display( )
571+ ) ) )
572+ }
573+ }
574+
575+ #[ cfg( windows) ]
576+ fn executable_search_candidates ( dir : & Path , executable : & str ) -> Vec < PathBuf > {
577+ let direct = dir. join ( executable) ;
578+ if Path :: new ( executable) . extension ( ) . is_some ( ) {
579+ return vec ! [ direct] ;
580+ }
581+
582+ let mut candidates = vec ! [ direct] ;
583+ let path_ext = env:: var_os ( "PATHEXT" ) . map_or_else (
584+ || ".COM;.EXE;.BAT;.CMD" . to_string ( ) ,
585+ |value| value. to_string_lossy ( ) . into_owned ( ) ,
586+ ) ;
587+ candidates. extend (
588+ path_ext
589+ . split ( ';' )
590+ . filter ( |extension| !extension. is_empty ( ) )
591+ . map ( |extension| dir. join ( format ! ( "{executable}{extension}" ) ) ) ,
592+ ) ;
593+ candidates
594+ }
595+
596+ #[ cfg( not( windows) ) ]
597+ fn executable_search_candidates ( dir : & Path , executable : & str ) -> Vec < PathBuf > {
598+ vec ! [ dir. join( executable) ]
599+ }
600+
510601/// Resolve `executable` to its canonical UTF-8 path, enforce the configured
511602/// allowlist policy, and return the canonical string.
512603///
@@ -517,7 +608,7 @@ fn resolve_governed_executable(
517608 mediator : & crate :: config:: CommandMediatorConfig ,
518609 executable : & str ,
519610) -> Result < String , RunError > {
520- let canonical_path = std :: fs:: canonicalize ( executable) . map_err ( |error| {
611+ let canonical_path = fs:: canonicalize ( executable) . map_err ( |error| {
521612 RunError :: Governance ( format ! (
522613 "executable '{executable}' could not be resolved (fail-closed): {error}"
523614 ) )
@@ -566,7 +657,7 @@ fn build_execution_env(
566657 let mut env = BTreeMap :: new ( ) ;
567658
568659 for key in & profile. env_passthrough {
569- if let Ok ( value) = std :: env:: var ( key) {
660+ if let Ok ( value) = env:: var ( key) {
570661 env. insert ( key. clone ( ) , value) ;
571662 }
572663 }
@@ -640,9 +731,9 @@ fn maybe_apply_claude_settings(
640731 return Ok ( args) ;
641732 }
642733
643- let executable = std :: path :: Path :: new ( executable)
734+ let executable = Path :: new ( executable)
644735 . file_name ( )
645- . and_then ( std :: ffi :: OsStr :: to_str)
736+ . and_then ( OsStr :: to_str)
646737 . unwrap_or_default ( ) ;
647738 if executable != "claude" {
648739 return Ok ( args) ;
@@ -664,7 +755,7 @@ fn maybe_apply_claude_settings(
664755 "failed to serialize Claude settings payload: {error}"
665756 ) )
666757 } ) ?;
667- std :: fs:: write ( & settings_path, serialized) . map_err ( |error| {
758+ fs:: write ( & settings_path, serialized) . map_err ( |error| {
668759 RunError :: Internal ( format ! (
669760 "failed to write Claude settings file {}: {error}" ,
670761 settings_path. display( )
@@ -703,14 +794,14 @@ fn build_appended_ca_bundle(firma_ca_path: &Path) -> Option<PathBuf> {
703794/// paths and concatenates the first existing one with the firma CA.
704795fn build_appended_ca_bundle_with_roots ( firma_ca_path : & Path , roots : & [ PathBuf ] ) -> Option < PathBuf > {
705796 let system_roots = roots. iter ( ) . find ( |p| p. is_file ( ) ) ?;
706- let mut bundle = match std :: fs:: read ( system_roots) {
797+ let mut bundle = match fs:: read ( system_roots) {
707798 Ok ( bytes) => bytes,
708799 Err ( error) => {
709800 tracing:: warn!( %error, path = %system_roots. display( ) , "failed to read system CA bundle; using sole firma-ca" ) ;
710801 return None ;
711802 }
712803 } ;
713- let firma_ca = match std :: fs:: read ( firma_ca_path) {
804+ let firma_ca = match fs:: read ( firma_ca_path) {
714805 Ok ( bytes) => bytes,
715806 Err ( error) => {
716807 tracing:: warn!( %error, path = %firma_ca_path. display( ) , "failed to read firma-ca; using sole firma-ca" ) ;
@@ -722,7 +813,7 @@ fn build_appended_ca_bundle_with_roots(firma_ca_path: &Path, roots: &[PathBuf])
722813 }
723814 bundle. extend_from_slice ( & firma_ca) ;
724815 let bundle_path = firma_ca_path. with_file_name ( "firma-ca-bundle.crt" ) ;
725- if let Err ( error) = std :: fs:: write ( & bundle_path, & bundle) {
816+ if let Err ( error) = fs:: write ( & bundle_path, & bundle) {
726817 tracing:: warn!( %error, path = %bundle_path. display( ) , "failed to write combined CA bundle; using sole firma-ca" ) ;
727818 return None ;
728819 }
@@ -761,7 +852,7 @@ fn resolve_sidecar_ca_cert_path(network_overrides: &BTreeMap<String, String>) ->
761852 }
762853 }
763854
764- if let Ok ( explicit) = std :: env:: var ( "FIRMA_SIDECAR_CA_CERT_PATH" )
855+ if let Ok ( explicit) = env:: var ( "FIRMA_SIDECAR_CA_CERT_PATH" )
765856 && !explicit. trim ( ) . is_empty ( )
766857 {
767858 let path = PathBuf :: from ( explicit) ;
@@ -770,7 +861,7 @@ fn resolve_sidecar_ca_cert_path(network_overrides: &BTreeMap<String, String>) ->
770861 }
771862 }
772863
773- if let Ok ( ca_dir) = std :: env:: var ( "FIRMA_SIDECAR_CA_DIR" )
864+ if let Ok ( ca_dir) = env:: var ( "FIRMA_SIDECAR_CA_DIR" )
774865 && !ca_dir. trim ( ) . is_empty ( )
775866 {
776867 let path = PathBuf :: from ( ca_dir) . join ( "firma-ca.crt" ) ;
@@ -779,7 +870,7 @@ fn resolve_sidecar_ca_cert_path(network_overrides: &BTreeMap<String, String>) ->
779870 }
780871 }
781872
782- let cwd_candidate = std :: env:: current_dir ( )
873+ let cwd_candidate = env:: current_dir ( )
783874 . ok ( )
784875 . map ( |cwd| cwd. join ( "firma-ca" ) . join ( "firma-ca.crt" ) ) ;
785876 let default_candidates = [
@@ -810,7 +901,7 @@ fn print_effective_config(
810901 agent_id : & identity. agent_id ,
811902 execution_profile : & identity. execution_profile ,
812903 profile,
813- working_dir : std :: env:: current_dir ( ) . map_err ( |error| {
904+ working_dir : env:: current_dir ( ) . map_err ( |error| {
814905 RunError :: Internal ( format ! (
815906 "failed to resolve working dir for snapshot: {error}"
816907 ) )
@@ -827,7 +918,7 @@ fn print_effective_config(
827918mod tests {
828919 use std:: collections:: { BTreeMap , BTreeSet } ;
829920 use std:: fs;
830- use std:: path:: PathBuf ;
921+ use std:: path:: { Path , PathBuf } ;
831922 use std:: time:: Duration ;
832923
833924 use firma_config_loader:: CONFIG_FILE_NAME ;
@@ -857,15 +948,15 @@ mod tests {
857948 fn appended_ca_bundle_concatenates_system_roots_and_firma_ca ( ) {
858949 let dir = tempfile:: tempdir ( ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
859950 let system = dir. path ( ) . join ( "system-roots.pem" ) ;
860- std :: fs:: write ( & system, b"-----SYSTEM ROOT-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
951+ fs:: write ( & system, b"-----SYSTEM ROOT-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
861952 let firma_ca = dir. path ( ) . join ( "firma-ca.crt" ) ;
862- std :: fs:: write ( & firma_ca, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
953+ fs:: write ( & firma_ca, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
863954
864955 let bundle =
865956 super :: build_appended_ca_bundle_with_roots ( & firma_ca, std:: slice:: from_ref ( & system) )
866957 . unwrap_or_else ( || panic ! ( "bundle should be built" ) ) ;
867958 assert_eq ! ( bundle, dir. path( ) . join( "firma-ca-bundle.crt" ) ) ;
868- let body = std :: fs:: read_to_string ( & bundle) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
959+ let body = fs:: read_to_string ( & bundle) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
869960 assert ! ( body. contains( "SYSTEM ROOT" ) ) ;
870961 assert ! ( body. contains( "FIRMA CA" ) ) ;
871962 }
@@ -874,7 +965,7 @@ mod tests {
874965 fn appended_ca_bundle_falls_back_when_no_system_roots ( ) {
875966 let dir = tempfile:: tempdir ( ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
876967 let firma_ca = dir. path ( ) . join ( "firma-ca.crt" ) ;
877- std :: fs:: write ( & firma_ca, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
968+ fs:: write ( & firma_ca, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
878969 let missing = dir. path ( ) . join ( "does-not-exist.pem" ) ;
879970 assert ! ( super :: build_appended_ca_bundle_with_roots( & firma_ca, & [ missing] ) . is_none( ) ) ;
880971 }
@@ -1074,7 +1165,7 @@ mod tests {
10741165 fn ca_trust_mode_selects_appended_bundle_at_injection_site ( ) {
10751166 let dir = tempfile:: tempdir ( ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
10761167 let ca_cert = dir. path ( ) . join ( "firma-ca.crt" ) ;
1077- std :: fs:: write ( & ca_cert, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
1168+ fs:: write ( & ca_cert, b"-----FIRMA CA-----\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
10781169
10791170 let make_profile = |mode : crate :: config:: CaTrustMode | ResolvedProfile {
10801171 id : "copilot" . to_string ( ) ,
@@ -1151,15 +1242,15 @@ mod tests {
11511242 let bundle_path = ca_cert. with_file_name ( "firma-ca-bundle.crt" ) ;
11521243 let system_roots_present = super :: SYSTEM_CA_BUNDLE_CANDIDATES
11531244 . iter ( )
1154- . any ( |p| std :: path :: Path :: new ( p) . is_file ( ) ) ;
1245+ . any ( |p| Path :: new ( p) . is_file ( ) ) ;
11551246 if system_roots_present {
11561247 // A system bundle exists: AppendSystemRoots must point at the
11571248 // generated sibling bundle, and it must contain the firma CA.
11581249 assert_eq ! (
11591250 append_env. get( "SSL_CERT_FILE" ) ,
11601251 Some ( & bundle_path. display( ) . to_string( ) )
11611252 ) ;
1162- let body = std :: fs:: read_to_string ( & bundle_path) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
1253+ let body = fs:: read_to_string ( & bundle_path) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
11631254 assert ! ( body. contains( "FIRMA CA" ) ) ;
11641255 assert_ne ! (
11651256 append_env. get( "SSL_CERT_FILE" ) ,
@@ -1722,7 +1813,7 @@ mod tests {
17221813 let real_code = host_bin. join ( "code" ) ;
17231814 fs:: write ( & real_code, "#!/bin/sh\n exit 0\n " ) . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
17241815
1725- let resolved = super :: vscode :: resolve_host_executable ( "code" , Some ( host_bin. as_os_str ( ) ) )
1816+ let resolved = super :: resolve_host_executable ( "code" , Some ( host_bin. as_os_str ( ) ) )
17261817 . unwrap_or_else ( |e| panic ! ( "{e}" ) ) ;
17271818 assert_eq ! ( resolved, real_code) ;
17281819 }
0 commit comments