From 92f34cb64db01f8e2c5475cd677333ae4e096c62 Mon Sep 17 00:00:00 2001 From: marcusH Date: Mon, 20 Jul 2026 22:17:21 +0200 Subject: [PATCH] macOS: API-Key nicht mehr ueber die Kommandozeile an security add-generic-password bekam den Key als -w-Argument; damit stand er fuer die Dauer des Aufrufs in der Prozessliste und war per ps fuer jeden Nutzer des Rechners lesbar. Jetzt kommt -w ohne Wert, security liest das Secret ueber stdin (zweimal, Eingabe und Bestaetigung). Die Eingabeaufforderungen landen auf stderr und werden aus der Fehlermeldung geschnitten. --- src-tauri/src/platform/macos.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src-tauri/src/platform/macos.rs b/src-tauri/src/platform/macos.rs index 07e3d79..0cd7270 100644 --- a/src-tauri/src/platform/macos.rs +++ b/src-tauri/src/platform/macos.rs @@ -1,8 +1,9 @@ //! macOS: Aktivierung/Fokus über AppKit (objc2), Dock-Icons pro //! Terminal-Prozess, Keychain über das security-CLI, nativer Tray. +use std::io::Write; use std::path::Path; -use std::process::Command; +use std::process::{Command, Stdio}; use crate::domain::credentials::{ApikeyStore, APIKEY_SERVICE}; use crate::domain::paths::Paths; @@ -98,14 +99,32 @@ pub(crate) struct KeychainStore; impl ApikeyStore for KeychainStore { fn set(&self, pool: &str, key: &str) -> Result<(), String> { - let out = Command::new("security") - .args(["add-generic-password", "-U", "-s", APIKEY_SERVICE, "-a", pool, "-w", key]) - .output() + // -w ohne Wert: security liest das Secret über stdin, zweimal (Eingabe und + // Bestätigung). Als Argument übergeben stünde der Key für die Dauer des + // Aufrufs in der Prozessliste und wäre für jeden `ps` lesbar. + let mut child = Command::new("security") + .args(["add-generic-password", "-U", "-s", APIKEY_SERVICE, "-a", pool, "-w"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() .map_err(|e| e.to_string())?; + child + .stdin + .take() + .ok_or("security: kein stdin")? + .write_all(format!("{key}\n{key}\n").as_bytes()) + .map_err(|e| e.to_string())?; + let out = child.wait_with_output().map_err(|e| e.to_string())?; if out.status.success() { Ok(()) } else { - Err(String::from_utf8_lossy(&out.stderr).into_owned()) + // Die Eingabeaufforderungen landen auf stderr und stehen sonst vor der + // eigentlichen Fehlermeldung. + let err = String::from_utf8_lossy(&out.stderr) + .replace("password data for new item: ", "") + .replace("retype password for new item: ", ""); + Err(err.trim().to_string()) } }