@@ -139,6 +139,11 @@ impl IpcServer {
139139
140140 let pipe_name = HSTRING :: from ( & self . socket_path ) ;
141141
142+ // Build a security descriptor that restricts pipe access to the current user only.
143+ // This prevents other users/processes from connecting to the daemon pipe.
144+ let sa = create_current_user_security_attributes ( )
145+ . map_err ( |e| format ! ( "Failed to create pipe security attributes: {e}" ) ) ?;
146+
142147 while self . running . load ( Ordering :: SeqCst ) {
143148 // Create a new named pipe instance for each client
144149 let pipe_handle = unsafe {
@@ -150,7 +155,7 @@ impl IpcServer {
150155 65536 , // out buffer
151156 65536 , // in buffer
152157 0 , // default timeout
153- None , // default security
158+ Some ( & sa ) , // restrict to current user
154159 )
155160 } ;
156161
@@ -355,6 +360,99 @@ fn get_peer_tty_id(_stream: &UnixStream) -> Option<String> {
355360 None
356361}
357362
363+ // ── Windows pipe security ───────────────────────────────────────
364+
365+ /// Create a SECURITY_ATTRIBUTES that restricts access to the current user only.
366+ /// This prevents other users from connecting to the daemon's named pipe.
367+ #[ cfg( windows) ]
368+ fn create_current_user_security_attributes ( ) -> Result < windows:: Win32 :: Security :: SECURITY_ATTRIBUTES , String > {
369+ use windows:: Win32 :: Security :: {
370+ SECURITY_ATTRIBUTES , SECURITY_DESCRIPTOR ,
371+ InitializeSecurityDescriptor , SetSecurityDescriptorDacl ,
372+ SECURITY_DESCRIPTOR_REVISION ,
373+ } ;
374+ use windows:: Win32 :: Security :: Authorization :: {
375+ SetEntriesInAclW , EXPLICIT_ACCESS_W , SET_ACCESS ,
376+ TRUSTEE_W , TRUSTEE_IS_SID , TRUSTEE_TYPE ,
377+ NO_INHERITANCE , TRUSTEE_FORM ,
378+ } ;
379+ use windows:: Win32 :: Security :: {
380+ GetTokenInformation , TokenUser , TOKEN_USER , TOKEN_QUERY ,
381+ } ;
382+ use windows:: Win32 :: System :: Threading :: { GetCurrentProcess , OpenProcessToken } ;
383+ use windows:: Win32 :: Foundation :: GENERIC_ALL ;
384+
385+ unsafe {
386+ // Get current process token
387+ let mut token = windows:: Win32 :: Foundation :: HANDLE :: default ( ) ;
388+ OpenProcessToken ( GetCurrentProcess ( ) , TOKEN_QUERY , & mut token)
389+ . map_err ( |e| format ! ( "OpenProcessToken failed: {e}" ) ) ?;
390+
391+ // Get token user (contains the SID)
392+ let mut token_info_len = 0u32 ;
393+ let _ = GetTokenInformation ( token, TokenUser , None , 0 , & mut token_info_len) ;
394+ let mut token_info = vec ! [ 0u8 ; token_info_len as usize ] ;
395+ GetTokenInformation (
396+ token,
397+ TokenUser ,
398+ Some ( token_info. as_mut_ptr ( ) as * mut _ ) ,
399+ token_info_len,
400+ & mut token_info_len,
401+ ) . map_err ( |e| format ! ( "GetTokenInformation failed: {e}" ) ) ?;
402+
403+ let token_user = & * ( token_info. as_ptr ( ) as * const TOKEN_USER ) ;
404+ let user_sid = token_user. User . Sid ;
405+
406+ // Build an ACL with a single entry: GENERIC_ALL for the current user
407+ let mut ea = EXPLICIT_ACCESS_W {
408+ grfAccessPermissions : GENERIC_ALL . 0 ,
409+ grfAccessMode : SET_ACCESS ,
410+ grfInheritance : NO_INHERITANCE ,
411+ Trustee : TRUSTEE_W {
412+ TrusteeForm : TRUSTEE_IS_SID ,
413+ TrusteeType : TRUSTEE_TYPE ( 0 ) , // TRUSTEE_IS_UNKNOWN
414+ ptstrName : windows:: core:: PWSTR ( user_sid. 0 as * mut u16 ) ,
415+ pMultipleTrustee : std:: ptr:: null_mut ( ) ,
416+ MultipleTrusteeOperation : TRUSTEE_FORM ( 0 ) ,
417+ } ,
418+ } ;
419+
420+ let mut acl = std:: ptr:: null_mut ( ) ;
421+ let err = SetEntriesInAclW ( Some ( & [ ea] ) , None , & mut acl) ;
422+ if err. 0 != 0 {
423+ return Err ( format ! ( "SetEntriesInAclW failed: error {}" , err. 0 ) ) ;
424+ }
425+
426+ // Create a security descriptor with this DACL
427+ let sd_layout = std:: alloc:: Layout :: new :: < SECURITY_DESCRIPTOR > ( ) ;
428+ let sd_ptr = std:: alloc:: alloc_zeroed ( sd_layout) as * mut SECURITY_DESCRIPTOR ;
429+ if sd_ptr. is_null ( ) {
430+ return Err ( "Failed to allocate security descriptor" . into ( ) ) ;
431+ }
432+
433+ InitializeSecurityDescriptor (
434+ sd_ptr as * mut _ ,
435+ SECURITY_DESCRIPTOR_REVISION ,
436+ ) . map_err ( |e| format ! ( "InitializeSecurityDescriptor failed: {e}" ) ) ?;
437+
438+ SetSecurityDescriptorDacl (
439+ sd_ptr as * mut _ ,
440+ true ,
441+ Some ( acl as * const _ ) ,
442+ false ,
443+ ) . map_err ( |e| format ! ( "SetSecurityDescriptorDacl failed: {e}" ) ) ?;
444+
445+ // Note: sd_ptr and acl are intentionally leaked — they must live for the
446+ // lifetime of the pipe server. The OS frees them on process exit.
447+
448+ Ok ( SECURITY_ATTRIBUTES {
449+ nLength : std:: mem:: size_of :: < SECURITY_ATTRIBUTES > ( ) as u32 ,
450+ lpSecurityDescriptor : sd_ptr as * mut _ ,
451+ bInheritHandle : false . into ( ) ,
452+ } )
453+ }
454+ }
455+
358456// ── Windows named pipe client handling ───────────────────────────
359457
360458#[ cfg( windows) ]
@@ -417,8 +515,13 @@ fn handle_windows_client(
417515
418516 let id = message. get ( "id" ) . and_then ( |v| v. as_str ( ) ) . map ( |s| s. to_string ( ) ) ;
419517
518+ // On Windows, use client-reported ttyId from the message (set by --via-daemon callers)
519+ let effective_tty_id = tty_id. clone ( ) . or_else ( || {
520+ message. get ( "ttyId" ) . and_then ( |v| v. as_str ( ) ) . map ( |s| s. to_string ( ) )
521+ } ) ;
522+
420523 let response = if let Some ( ref handler) = handler {
421- handler ( message, tty_id . clone ( ) )
524+ handler ( message, effective_tty_id )
422525 } else {
423526 serde_json:: json!( { "error" : "No handler" } )
424527 } ;
0 commit comments