Skip to content

Fix macOS 26 (Tahoe) support and harden XPC, config parsing, and logging - #985

Open
godlockin wants to merge 87 commits into
jordanbaird:mainfrom
godlockin:macos-26-security-hardening
Open

Fix macOS 26 (Tahoe) support and harden XPC, config parsing, and logging#985
godlockin wants to merge 87 commits into
jordanbaird:mainfrom
godlockin:macos-26-security-hardening

Conversation

@godlockin

Copy link
Copy Markdown

Summary

This PR brings the work from the macos-26 branch into main so Ice works on macOS 26 (Tahoe), and adds security hardening identified in a full audit of the merged result. Verified end-to-end on Apple Silicon (M4 Pro, macOS 26.6.2): menu bar item hiding/showing, the MenuBarItemService XPC service, and rehide timers all work.

1. macOS 26 (Tahoe) support

  • Merges the macos-26 branch (0.11.13-dev.2a) into main, picking up the menu bar item retrieval/event handling reworks that Tahoe requires.

2. Security hardening

  • Sparkle 2.9.6 — the previously resolved 2.8.0 is affected by CVE-2026-47121 (path traversal) and CVE-2026-47122 (unvalidated post-stage-1 XPC listener accepting spoofed appcast data). The pin is raised to 2.9.2+.
  • Hotkey values from stored configuration are untrusted input (any process in the user session can write to the app's defaults domain):
    • Out-of-range key codes previously reached UInt32() and trapped on every launch, a persistent crash-loop DoS that survived reinstall. They are now rejected at decode time, with UInt32(exactly:) as a second line of defense at registration.
    • System-reserved combinations are filtered at load, so a hostile defaults write can't register ⌘Tab/⌘Space as Ice hotkeys.
  • IceColor now validates that the decoded components array has at least colorSpace.numberOfComponents entries before constructing a CGColor (previously a heap out-of-bounds read).
  • MenuBarItemService XPC channel (macOS 26 path):
    • CodeSigningInfo distinguishes a verifiably team-less signature (ad-hoc builds, where same-team peer requirements can never be satisfied) from a failure to read signature information. Verification now fails closed on read failures, and the decision is logged on both sides.
    • The source PID request carries only the window ID and bounds the service needs, instead of the full WindowInfo, shrinking the decode surface.
    • The service remembers failed lookups (5 s TTL, pruned when large) and rate-limits scans of unknown windows (10/s), so a session process can't pin it in full accessibility scans.
    • The listener retries activation instead of silently dying if the mach name is momentarily taken; the app validates that a returned PID belongs to a running application before use.
    • The app now retains the activated session in shared storage — releasing an active XPCSession traps in _xpc_api_misuse.
  • Privacy/logging: item/window metadata (window titles read via kCGWindowName) is no longer logged with privacy: .public at persisted levels, closing a side channel readable by any process in the session.
  • Misc: cached item images are evicted when their items disappear; the search panel's key-down monitor is local-only while the panel is key; the unused RunLoopLocalEventMonitor (drains and replays the app event queue) was removed; event barrier continuations are guarded against double resume.

3. Building without full Xcode

  • Scripts/build-without-xcode.sh builds Ice.app using only the Command Line Tools via SwiftPM: stages the sources, strips #Preview blocks (whose macro plugin ships only with Xcode), reuses the compiled asset catalog from the official release archive pinned by SHA-256, then assembles and ad-hoc signs the bundle with the hardened runtime. Handy for contributor machines without a full Xcode install.

Fixes #709

Misc additional changes and cleanup
`RunLoopLocalEventMonitor` seems to prevent certain buttons from receiving events in macOS 26 Developer Beta 1. This might be a bug in the beta, or `RunLoopLocalEventMonitor` itself. For now, let's just move to a better mouse check implementation that doesn't use continuous event monitoring.

Note the `FIXME` (line 1057). The previous implementation had this problem too, but it was never documented.
A big part of this is hopefully a temporary measure. We need an accurate identifier for every item, and the old way just isn't cutting it right now. Items are all owned by the Control Center in macOS 26, and try as I might, I couldn't find a great way to get the _actual_ host apps for the items.

Must dig deeper into the mines...

Oh yeah, there's also a bunch of random stuff here too that I don't really want to explain. Just know that it probably all fixed something.
This is what I get for trusting Xcode to update my build settings
- Refactor screen capture
- Rework menu bar item getters
- Update immovable/non-hideable info lists
- Remove OSLog wrapper
- Minor migration rework
- Remove old entitlements file
Should also fix a crash when accessing `ControlItem.windowNumber`.
- Rework app lifecycle
- Rework how windows are initialized
- Update documentation comments
- Refactoring and cleanup
This should fix some performance issues that occur during mouse tracking operations (e.g. highlighting a button on hover).
We also store the status item and layout constraint in a separate storage class. Not sure I like this, but the idea is to convey the tightly coupled relationship between the constraint and status item, and to ensure that they are initialized (and deinitialized) at the same time.
Relying on the default behavior seemed to work fine, but is now broken in the macOS 26 Developer Beta. Probably better to handle it explicitly, even if it is just a beta bug.
jordanbaird and others added 30 commits September 3, 2025 07:08
- Documentation changes
- Misc event handling improvements
- Fix temporarily shown item interface check
- Fix broken on screen item check
- Additional minor refactoring
Revert to using a direct call to the `CGImage` initializer, rather than calling it through a static protocol method (this was originally done to suppress the deprecation warning, but it seems to cause screen capture to fail for some users).
…fier

Ad-hoc signed builds (e.g. local builds without a signing certificate)
have no team identifier, so the isFromSameTeam() requirements enforced
by both the MenuBarItemService listener and the app-side connection
reject every connection, breaking menu bar item retrieval on macOS 26.
Only enforce the requirement when our own signature has a team.
Sparkle 2.8.0 (previously resolved) is affected by CVE-2026-47121
(path traversal) and CVE-2026-47122 (unvalidated post-stage-1 XPC
listener accepting spoofed appcast data), both fixed in 2.9.2.
Stored key combinations are untrusted input, since any process in the
user session can write to the app's defaults domain:

- Reject out-of-range key codes and modifiers at decode time. A
  negative key code previously reached UInt32() and trapped on every
  launch, permanently crashing the app until the defaults were cleared.
- Convert to UInt32 with UInt32(exactly:) at registration time as a
  second line of defense.
- Skip combinations that are reserved for system use, so a hostile
  defaults write can't register system shortcuts (e.g. Command-Tab)
  as Ice hotkeys.
CGColor reads colorSpace.numberOfComponents values from the components
buffer, so a stored configuration with fewer components than the color
space requires would read out of bounds. Reject it at decode time
instead.
Security audit follow-ups for the ad-hoc (no team) build path and the
service side:

- CodeSigningInfo now distinguishes a verifiably team-less signature
  from a failure to read signature information. Peer verification is
  skipped only in the former case; read failures keep verification
  enabled (fail closed). The decision is logged on both sides.
- The source PID request now carries only the window ID and bounds the
  service needs, instead of the full WindowInfo, shrinking the decode
  surface on the service side.
- The service remembers failed lookups for 5 seconds and rate limits
  scans of unknown windows to 10/sec, so a client flooding the service
  with random window IDs can no longer pin it in full accessibility
  scans. The failed-lookup map is pruned when it grows too large.
- The listener retries activation instead of silently leaving the
  service dead if the mach name is momentarily taken.
- The app validates that a returned PID belongs to a running
  application before using it, discarding forged or stale responses.
- The app keeps a strong reference to the activated session in shared
  storage; releasing an active XPCSession traps in _xpc_api_misuse,
  which crashed the app shortly after startup.
- Item and error descriptions (which embed window titles read via
  kCGWindowName) are no longer logged with privacy: .public, so they
  are redacted in the unified log instead of being readable by any
  process in the session.
- Event barrier continuations are now resumed through a once-only
  guard, since the tap callback and the cancellation handler can race
  to resume the same continuation, which traps.
- Evict cached item images whose items no longer exist, so captures of
  hidden or removed items don't stay in memory indefinitely.
- Redact application identifiers from spacing manager logs.
- The search panel now monitors key down events locally instead of
  globally; the panel is key while shown, so a global monitor observed
  keystrokes destined for other applications for no benefit.
- Remove the unused RunLoopLocalEventMonitor, whose pattern of
  draining and replaying the app event queue is dangerous if ever
  reused.
- Require Sparkle 2.9.2+ and pin the downloaded asset archive by
  SHA-256, so a replaced upstream archive fails the build instead of
  being silently bundled.
- Sign the assembled app and XPC service with the hardened runtime,
  matching what xcodebuild produces.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Ice stops working in Mac OS 26 Tahoe

2 participants