@@ -196,6 +196,17 @@ impl IpcServer {
196196 std:: thread:: spawn ( move || {
197197 use windows:: Win32 :: Foundation :: HANDLE ;
198198 let pipe = HANDLE ( raw_handle as * mut _ ) ;
199+
200+ // Verify the connecting process is a trusted varlock binary
201+ if !verify_client_process ( pipe) {
202+ eprintln ! ( "Rejected connection from untrusted process" ) ;
203+ unsafe {
204+ let _ = DisconnectNamedPipe ( pipe) ;
205+ let _ = CloseHandle ( pipe) ;
206+ }
207+ return ;
208+ }
209+
199210 handle_windows_client ( pipe, handler, on_activity, running, tty_id) ;
200211 unsafe {
201212 let _ = DisconnectNamedPipe ( pipe) ;
@@ -453,6 +464,74 @@ fn create_current_user_security_attributes() -> Result<windows::Win32::Security:
453464 }
454465}
455466
467+ // ── Windows client process verification ─────────────────────────
468+
469+ /// Verify that the connecting client process is a trusted varlock binary.
470+ /// Uses GetNamedPipeClientProcessId to get the client PID, then checks
471+ /// that the process executable matches our own binary name.
472+ #[ cfg( windows) ]
473+ fn verify_client_process ( pipe : windows:: Win32 :: Foundation :: HANDLE ) -> bool {
474+ use windows:: Win32 :: System :: Pipes :: GetNamedPipeClientProcessId ;
475+ use windows:: Win32 :: System :: Threading :: {
476+ OpenProcess , QueryFullProcessImageNameW , PROCESS_QUERY_LIMITED_INFORMATION ,
477+ PROCESS_NAME_WIN32 ,
478+ } ;
479+ use windows:: Win32 :: Foundation :: CloseHandle ;
480+
481+ // Get the client's PID
482+ let mut client_pid = 0u32 ;
483+ let ok = unsafe { GetNamedPipeClientProcessId ( pipe, & mut client_pid) } ;
484+ if ok. is_err ( ) || client_pid == 0 {
485+ return false ;
486+ }
487+
488+ // Open the client process to query its image name
489+ let process = unsafe {
490+ OpenProcess ( PROCESS_QUERY_LIMITED_INFORMATION , false , client_pid)
491+ } ;
492+ let process = match process {
493+ Ok ( h) => h,
494+ Err ( _) => return false ,
495+ } ;
496+
497+ // Query the full executable path
498+ let mut buf = [ 0u16 ; 1024 ] ;
499+ let mut len = buf. len ( ) as u32 ;
500+ let ok = unsafe {
501+ QueryFullProcessImageNameW ( process, PROCESS_NAME_WIN32 , windows:: core:: PWSTR ( buf. as_mut_ptr ( ) ) , & mut len)
502+ } ;
503+ unsafe { let _ = CloseHandle ( process) ; }
504+
505+ if ok. is_err ( ) || len == 0 {
506+ return false ;
507+ }
508+
509+ let client_path = String :: from_utf16_lossy ( & buf[ ..len as usize ] ) ;
510+
511+ // Extract the filename from the full path
512+ let client_filename = client_path
513+ . rsplit ( '\\' )
514+ . next ( )
515+ . unwrap_or ( "" )
516+ . to_lowercase ( ) ;
517+
518+ // Allow connections from varlock binaries and Node.js (for native Windows daemon client)
519+ let allowed = [
520+ "varlock-local-encrypt.exe" ,
521+ "varlock.exe" ,
522+ "node.exe" , // Node.js daemon client on native Windows
523+ "bun.exe" , // Bun runtime on native Windows
524+ ] ;
525+
526+ let is_trusted = allowed. iter ( ) . any ( |name| client_filename == * name) ;
527+ if !is_trusted {
528+ eprintln ! (
529+ "Untrusted client process: PID={client_pid}, path={client_path}"
530+ ) ;
531+ }
532+ is_trusted
533+ }
534+
456535// ── Windows named pipe client handling ───────────────────────────
457536
458537#[ cfg( windows) ]
0 commit comments