Skip to content

Add App Sandbox support #4

@jspiro

Description

@jspiro

Motivation

App Sandbox improves security regardless of App Store plans — it limits blast radius if the app is ever compromised. Also a prerequisite for Mac App Store submission.

Sandbox Audit

The codebase is already close to sandbox-compatible. Only 3 things break:

1. com.apple.screencapture defaults read (PreferencesManager.swift:34)

Currently auto-detects the screenshot folder by reading another app's UserDefaults domain. Blocked in sandbox — can't read other apps' preferences.

Fix: Replace with a first-launch folder picker. The existing "add folder" UI (GeneralSettingsView.swift) already uses NSOpenPanel, which is sandbox-friendly.

2. Directory access needs security-scoped bookmarks

DirectoryWatcher.swift uses open(directory, O_EVTONLY) (line 21) and contentsOfDirectory(atPath:) (line 54). In sandbox, these only work on paths granted by NSOpenPanel — but that access is temporary (per-launch only).

Fix: When the user picks a folder via NSOpenPanel, persist a security-scoped bookmark. On launch, resolve bookmarks and call startAccessingSecurityScopedResource() before passing paths to DirectoryWatcher. Call stopAccessingSecurityScopedResource() on teardown.

Key files to change:

  • PreferencesManager.swift — store/resolve bookmark Data alongside path strings
  • WatcherManager.swift — manage startAccessingSecurityScopedResource() lifecycle
  • GeneralSettingsView.swift — save bookmark when user picks a folder via NSOpenPanel

3. Sparkle is incompatible with sandbox

Sparkle downloads and installs updates, which the sandbox blocks.

Fix: Wrap Sparkle in #if !APP_STORE conditional compilation flag. This lets us maintain both distribution channels:

  • Direct distribution: Sparkle updates (current behavior)
  • App Store: Apple handles updates

Files to change: Package.swift, UpdaterManager.swift, StatusBarController.swift, AppDelegate.swift

Everything else works in sandbox

  • NSPasteboard.general (clipboard) — works
  • UNUserNotificationCenter (notifications) — works
  • SMAppService.mainApp (login item) — works
  • NSWorkspace.shared.open(url) (opening System Settings) — works

Required entitlements

<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>

UX impact

Minimal. The only user-visible change is that first launch must prompt the user to pick their screenshot folder instead of auto-detecting it. The existing preferences UI already supports adding/removing folders — it just needs to persist bookmarks behind the scenes.

Implementation plan

  1. Create AutoClip.entitlements with the three entitlements above
  2. Add bookmark persistence to PreferencesManager (save Data, resolve on launch)
  3. Update WatcherManager to call startAccessingSecurityScopedResource() before creating watchers
  4. Replace com.apple.screencapture fallback with first-launch folder picker
  5. Wrap Sparkle in #if !APP_STORE
  6. Update Makefile to pass entitlements to codesign and support APP_STORE=1 flag
  7. Test: sandbox-on with folder picker, sandbox-off with current behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions