From e15374bdd3c1e0df4b93e93658faf56f5b749f3e Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Tue, 9 Jun 2026 20:00:50 -0500 Subject: [PATCH] fix(shell): delay KubeconfigGuard disarm until after PTY session starts Add `path_str()` to `KubeconfigGuard` so the path can be passed to `SessionParams` without consuming the guard. Both `start_pty_exec_session` and `start_pty_attach_session` now hold the guard live until `start_exec/attach_session` returns `Ok`, then disarm it. Previously `disarm()` was called before the session-start call, meaning a kubeconfig temp file would leak if PTY spawn or session registration failed after the guard was consumed. --- src-tauri/src/commands/shell.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/commands/shell.rs b/src-tauri/src/commands/shell.rs index 044aa95a..8cccd06e 100644 --- a/src-tauri/src/commands/shell.rs +++ b/src-tauri/src/commands/shell.rs @@ -27,6 +27,15 @@ impl KubeconfigGuard { Self { path: Some(path) } } + /// Return the path as a string without transferring ownership. + fn path_str(&self) -> String { + self.path + .as_ref() + .expect("KubeconfigGuard path already taken") + .to_string_lossy() + .into_owned() + } + /// Transfer ownership: caller is now responsible for the file. /// Returns the path string for use with the PTY session. fn disarm(mut self) -> String { @@ -344,8 +353,9 @@ pub async fn start_pty_exec_session( let kubectl_path = crate::shell::kubectl::locate_kubectl().map_err(|e| format!("kubectl not found: {e}"))?; - // Transfer ownership: PTY session now owns the temp file's lifetime. - let kubeconfig_path = kubeconfig_guard.map(|g| g.disarm()); + // Obtain path string without disarming; the guard remains active so the + // file is cleaned up if session start fails below. + let kubeconfig_path = kubeconfig_guard.as_ref().map(|g| g.path_str()); // Start session let params = crate::shell::session::SessionParams { @@ -363,6 +373,13 @@ pub async fn start_pty_exec_session( .await .map_err(|e| format!("Failed to start exec session: {e}"))?; + // Session started — disarm the guard so the file outlives this function. + // The PTY process needs the kubeconfig for the full session duration; + // temp dir is OS-cleaned on reboot. + if let Some(g) = kubeconfig_guard { + g.disarm(); + } + Ok(session_id) } @@ -406,8 +423,9 @@ pub async fn start_pty_attach_session( let kubectl_path = crate::shell::kubectl::locate_kubectl().map_err(|e| format!("kubectl not found: {e}"))?; - // Transfer ownership: PTY session now owns the temp file's lifetime. - let kubeconfig_path = kubeconfig_guard.map(|g| g.disarm()); + // Obtain path string without disarming; the guard remains active so the + // file is cleaned up if session start fails below. + let kubeconfig_path = kubeconfig_guard.as_ref().map(|g| g.path_str()); // Start session let params = crate::shell::session::SessionParams { @@ -425,6 +443,11 @@ pub async fn start_pty_attach_session( .await .map_err(|e| format!("Failed to start attach session: {e}"))?; + // Session started — disarm the guard so the file outlives this function. + if let Some(g) = kubeconfig_guard { + g.disarm(); + } + Ok(session_id) }