From 89921275b8982327cb2f7ea24494b23b9e1d4966 Mon Sep 17 00:00:00 2001 From: maxsoch Date: Mon, 6 Jul 2026 22:41:42 +0200 Subject: [PATCH] show the triggering brew command in the sudo password dialog The askpass script is now rewritten before each brew invocation with the current command baked into the dialog text ("...to run: brew upgrade tailscale-app") instead of a generic message written once at launch. The command text is whitelisted to characters that cannot break out of the shell/AppleScript quoting it is embedded in. Co-Authored-By: Claude Fable 5 --- BrewBar/BrewBarApp.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index 2f76d0c..c43e4e0 100644 --- a/BrewBar/BrewBarApp.swift +++ b/BrewBar/BrewBarApp.swift @@ -145,22 +145,29 @@ class AppDelegate: NSObject, NSApplicationDelegate { return "brew" } - /// Written once per launch; sudo runs it to show a GUI password dialog - /// whenever brew needs admin rights (no terminal is attached to Process). - static let askpassURL: URL = { + /// Rewritten before each brew command so the sudo password dialog can say + /// which command needs it. Sudo runs this script to show a GUI password + /// prompt whenever brew needs admin rights (no terminal is attached to + /// Process). If two brew commands overlap, the later one's text wins — + /// harmless, since prompts realistically only appear during upgrades. + static func writeAskpassScript(for command: String) -> URL { let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("BrewBar", isDirectory: true) try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + // the text lands inside shell single quotes AND an AppleScript string; + // whitelist characters that cannot break out of either + let safeCommand = command.filter { $0.isLetter || $0.isNumber || " ._@+=:/-".contains($0) } + let url = dir.appendingPathComponent("askpass.sh") let script = """ #!/bin/zsh - osascript -e 'display dialog "BrewBar needs your administrator password to continue:" default answer "" with hidden answer with title "BrewBar" with icon caution buttons {"Cancel", "OK"} default button "OK"' -e 'text returned of result' + osascript -e 'display dialog "BrewBar needs your administrator password to run:\\n\\nbrew \(safeCommand)" default answer "" with hidden answer with title "BrewBar" with icon caution buttons {"Cancel", "OK"} default button "OK"' -e 'text returned of result' """ try? script.write(to: url, atomically: true, encoding: .utf8) try? FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: url.path) return url - }() + } func runBrew(_ command: String, completion: @escaping (String) -> Void = { _ in }) { DispatchQueue.global().async { @@ -177,7 +184,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { process.arguments = arguments var environment = ProcessInfo.processInfo.environment - environment["SUDO_ASKPASS"] = Self.askpassURL.path + environment["SUDO_ASKPASS"] = Self.writeAskpassScript(for: cleaned).path process.environment = environment let pipe = Pipe()