Skip to content

Add Security-Scoped Bookmarks for local file access - #3

Merged
b451c merged 1 commit into
b451c:mainfrom
fxd0h:feature/security-scoped-bookmarks
Mar 25, 2026
Merged

Add Security-Scoped Bookmarks for local file access#3
b451c merged 1 commit into
b451c:mainfrom
fxd0h:feature/security-scoped-bookmarks

Conversation

@fxd0h

@fxd0h fxd0h commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Summary

QuickMD can't display local images (e.g. ![](screenshot.png)) in sandboxed builds because the app only gets read access to the file the user opens, not to sibling files in the same directory.

This PR adds a SandboxAccessManager that handles Security-Scoped Bookmarks:

  1. When a local image fails to load (sandbox blocks access), the user is prompted via NSOpenPanel to grant folder access
  2. The bookmark is persisted in UserDefaults so subsequent launches restore access silently
  3. If the user denies access, a placeholder with a "Grant Folder Access" button is shown for retry
  4. If the image file simply doesn't exist, the standard image error view is shown (not the access prompt)

Changes

File Change
SandboxAccessManager.swift [NEW] @MainActor singleton managing bookmark creation, persistence, and restoration
ImageBlockView.swift Try-first/prompt-on-failure flow with retry placeholder UI
project.pbxproj File reference for SandboxAccessManager.swift
QuickMD.entitlements com.apple.security.files.bookmarks.app-scope entitlement
CHANGELOG.md v1.4.0 entry

Design Decisions

  • On-demand prompting: The NSOpenPanel only appears when an image actually fails to load, not on every file open. This avoids unnecessary prompts for markdown files without local images.
  • @MainActor isolation: SandboxAccessManager is marked @MainActor for thread safety since ImageBlockView calls it from async contexts.
  • File existence check: Before assuming sandbox denial, we check if the file actually exists. Missing files show the standard error view, not the access prompt.
  • Retry UX: If the user cancels the prompt, a placeholder with 🔒 icon, filename, and "Grant Folder Access" button is shown. Clicking it re-triggers the prompt.

Testing

Tested with a signed build (sandbox enforced):

  • ✅ Local image that exists → sandbox blocks → prompt appears → user grants → image loads
  • ✅ Image file doesn't exist → standard error view (no access prompt)
  • ✅ Markdown without images → no prompt appears
  • ✅ Subsequent opens → bookmark restored silently, no prompt

Fully App Store compliant — no temporary sandbox exceptions needed.

@b451c
b451c merged commit fea9883 into b451c:main Mar 25, 2026
b451c added a commit that referenced this pull request Mar 25, 2026
… fix

- feat: security-scoped bookmarks for local image access (PR #3 by fxd0h)
- feat: persist Table of Contents state across launches (closes #5)
- fix: replace LazyVStack with VStack to prevent scroll freeze on
  code blocks with box-drawing Unicode characters
- chore: close #4 (Cmd+W already fixed in v1.3.2)
@b451c

b451c commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Merged in v1.3.3 — thank you for the solid contribution! The Security-Scoped Bookmarks implementation is clean and well thought out. Great work on the file existence check and retry UX.

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.

2 participants