From cdfde73da9afe94c82dbb3e563acc8257bcac638 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Mon, 18 May 2026 16:25:51 +0200 Subject: [PATCH] fix(cua-driver-rs): drop bogus window-enum fallback in screen-recording probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `screen_recording_granted()` fell back to `!all_windows().is_empty()` when `CGPreflightScreenCaptureAccess` reported false, on the theory that `CGWindowListCopyWindowInfo` returning real windows implied the grant was active. That theory is wrong: `CGWindowListCopyWindowInfo` returns window IDs and bounds for any process without requiring the Screen Recording grant — only window titles are gated. The fallback therefore returned `true` on any populated desktop regardless of grant state, which: - made `check_permissions` report a false positive after `tccutil reset ScreenCapture com.trycua.driver`, and - short-circuited the startup permissions gate's prompt (`gate.rs:213` early-returns on `status.all_granted()`) for users who had never granted Screen Recording. Drop the fallback entirely — `CGPreflightScreenCaptureAccess` is Apple's documented preflight API for this grant and is accurate on macOS 11+. The Cargo.lock churn is the v0.2.4 workspace bump catching up — `release-bump-version.yml` didn't refresh the lock when it pushed the version commit; `cargo check` on this branch did. Related to #1561 (`check_permissions` stale-state report). The AX side of that issue is a separate concern: `AXIsProcessTrusted()` is the right API, but TCC attributes shell-invoked CLI processes to the parent terminal — so a shell with AX granted will make `cua-driver check_permissions` always see `accessibility: true`, regardless of `com.trycua.driver`'s grant. That's a process-attribution question, not a probe bug, and warrants its own investigation. Co-Authored-By: Claude Opus 4.7 (1M context) --- libs/cua-driver-rs/Cargo.lock | 46 ++++++++++++++++--- .../platform-macos/src/permissions/status.rs | 30 +++++++----- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/libs/cua-driver-rs/Cargo.lock b/libs/cua-driver-rs/Cargo.lock index 6e08265679..9a829995db 100644 --- a/libs/cua-driver-rs/Cargo.lock +++ b/libs/cua-driver-rs/Cargo.lock @@ -242,12 +242,13 @@ dependencies = [ [[package]] name = "cua-driver" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", "base64", "cursor-overlay", + "flate2", "image", "libc", "mcp-server", @@ -257,17 +258,19 @@ dependencies = [ "semver", "serde", "serde_json", + "tar", "tempfile", "tokio", "tracing", "tracing-subscriber", "ureq", "uuid", + "wait-timeout", ] [[package]] name = "cursor-overlay" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "image", @@ -370,6 +373,16 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -394,7 +407,7 @@ checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "focus-monitor-win" -version = "0.2.1" +version = "0.2.4" dependencies = [ "windows", ] @@ -831,7 +844,7 @@ dependencies = [ [[package]] name = "mcp-server" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", @@ -1117,7 +1130,7 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "platform-linux" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", @@ -1136,7 +1149,7 @@ dependencies = [ [[package]] name = "platform-macos" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", @@ -1166,7 +1179,7 @@ dependencies = [ [[package]] name = "platform-windows" -version = "0.2.1" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", @@ -1672,6 +1685,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -2115,6 +2138,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/libs/cua-driver-rs/crates/platform-macos/src/permissions/status.rs b/libs/cua-driver-rs/crates/platform-macos/src/permissions/status.rs index 2b6b25c1ab..2721d17ff4 100644 --- a/libs/cua-driver-rs/crates/platform-macos/src/permissions/status.rs +++ b/libs/cua-driver-rs/crates/platform-macos/src/permissions/status.rs @@ -24,14 +24,15 @@ impl PermissionsStatus { } /// Read the live TCC status for both required grants. Cheap to call -/// repeatedly — `AXIsProcessTrusted` is a quick C function and the -/// screen recording probe falls back to the window-enumeration heuristic -/// when `CGPreflightScreenCaptureAccess` reports false. +/// repeatedly — both probes are quick C-level calls into the system +/// TCC daemon. /// /// Mirrors Swift `Permissions.currentStatus()`. Difference: Swift uses /// `SCShareableContent.excludingDesktopWindows` (ScreenCaptureKit) for /// the screen recording probe, which is unavailable from Rust without -/// large bindings — same caveat documented in `check_permissions.rs`. +/// large bindings. `CGPreflightScreenCaptureAccess` is Apple's +/// documented preflight API for the same grant and is accurate on +/// macOS 11+. pub fn current_status() -> PermissionsStatus { PermissionsStatus { accessibility: accessibility_granted(), @@ -44,18 +45,25 @@ pub fn accessibility_granted() -> bool { unsafe { crate::ax::bindings::AXIsProcessTrusted() } } -/// Best-effort screen recording probe. Uses `CGPreflightScreenCaptureAccess` -/// then falls back to the window-enumeration heuristic for parity with -/// the existing `check_permissions` tool — see that file for rationale. +/// Live Screen Recording grant state — `CGPreflightScreenCaptureAccess()`. +/// +/// This is the only probe. Earlier versions fell back to +/// `!all_windows().is_empty()` when preflight returned false, on the +/// theory that `CGWindowListCopyWindowInfo` returning real windows +/// implied the grant was active. That theory is wrong: +/// `CGWindowListCopyWindowInfo` returns window IDs and bounds for **any** +/// process without requiring the Screen Recording grant — only window +/// titles are gated. The fallback therefore returned `true` on any +/// populated desktop regardless of grant state, which (a) made +/// `check_permissions` report a false positive after `tccutil reset +/// ScreenCapture com.trycua.driver`, and (b) short-circuited the +/// startup permissions gate's prompt for users who had never granted SR. pub fn screen_recording_granted() -> bool { #[link(name = "CoreGraphics", kind = "framework")] extern "C" { fn CGPreflightScreenCaptureAccess() -> bool; } - if unsafe { CGPreflightScreenCaptureAccess() } { - return true; - } - !crate::windows::all_windows().is_empty() + unsafe { CGPreflightScreenCaptureAccess() } } /// Raise the Accessibility TCC prompt if not yet granted. No-op when