A macOS file-reading feature developed on a remote Mac works in a test directory, but users get an “access denied” error. Often, the test file happened to be inside the app’s own container, or the test checked access immediately after file selection but not after the app quit. To validate this feature, use a file that the user explicitly selects outside the container, and test whether the app can still read it after a relaunch.
Define the test boundary first
App Sandbox restricts an app’s access to files outside its container. Granting access through the system file picker does not mean the app can read the file indefinitely using its original path. If the product needs to remember the file, the app must save a security-scoped bookmark, resolve it on the next launch, and activate access while it reads or writes the file.
Prepare a plain-text file with recognizable contents outside the app container. Avoid directories protected by additional system privacy permissions so that a separate authorization mechanism does not complicate this test. Also prepare a file inside the container as a control: reading it proves only that the basic file-handling code works, not that external file access works.
The acceptance criterion is not “no error after the file picker closes.” It is “the app can still read the specified file within the granted scope after a relaunch, and offers a way to recover when authorization is unavailable.”
Inspect the delivered app’s entitlements
Inspect the actual .app being delivered, not just the entitlement file edited in the project. A build configuration or signing discrepancy can change the declarations in the final artifact. Replace APP below with the actual path to this build’s output:
APP="$PWD/build/Reader.app"
codesign --verify --strict --verbose=2 "$APP"
codesign -d --entitlements :- "$APP"
Confirm that sandboxing is enabled. For a read-only import, check the entitlement for reading user-selected files; consider read-write access only if the app genuinely needs to modify the original file. If access must persist across launches, also check the declaration for app-scoped bookmarks. Do not grant access to unrelated directories just to make the test pass.
A signature check tells you what the app declares, not whether the user selected a file or whether a bookmark can be restored. Both require running the app.
Save bookmarks and pair access with cleanup
After NSOpenPanel returns the URL selected by the user, create a security-scoped bookmark and store the resulting Data in the app’s persistent storage. Do not save only url.path: a path records the location but contains none of the information needed to regain access.
Keep bookmark resolution, access activation, and cleanup in the same read function so that error paths cannot skip cleanup:
enum FileAccessError: Error {
case staleBookmark
case accessDenied
}
func readSelectedFile(bookmark: Data) throws -> Data {
var stale = false
let url = try URL(
resolvingBookmarkData: bookmark,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &stale
)
guard !stale else { throw FileAccessError.staleBookmark }
guard url.startAccessingSecurityScopedResource() else {
throw FileAccessError.accessDenied
}
defer { url.stopAccessingSecurityScopedResource() }
return try Data(contentsOf: url)
}
Use the security-scoped option when creating the bookmark, too, and persist the returned data properly. This example stops reading if the bookmark is stale; the product UI should let the user select the file again and create a new bookmark after authorization is renewed. Do not omit the check of startAccessingSecurityScopedResource()’s result, and do not defer stopAccessingSecurityScopedResource() until the app exits.
Run the relaunch regression test in a graphical session
You can inspect an app’s signature from the command line on a cloud Mac, but testing the NSOpenPanel interaction requires a working macOS graphical session. Do not assume that because an SSH task runs, the file picker can appear. For automated tests, likewise confirm that the test process runs in an environment with a graphical session.
Use the same test file for the following sequence, recording the expected and actual result at each step:
- On first launch, try to read the external file directly from a previously stored path without opening the file picker. Confirm that the app cannot bypass the expected authorization using the path alone.
- Select the file explicitly in the picker, read it and verify its contents, then save the bookmark.
- Quit the app completely and relaunch the same delivered build. Resolve the bookmark, then read and verify the contents again.
- Move the test file away and retry. The app should show an understandable error and a way to select the file again, rather than misreporting a missing file as a signing failure.
- Test the error path with corrupted bookmark data. The app should stop reading and ask the user to select the file again, without repeatedly opening dialogs or retrying indefinitely.
Step three is the easiest to miss: reading only within one process lifetime can hide a bookmark that was never persisted. If the test also requires writing changes back to the file, repeat this sequence separately with read-write access; a read-only test is not a substitute.
Diagnose failures from the evidence
If access fails immediately after selection, first check whether the URL returned by the picker is used correctly and whether the entitlements match the read-write requirements. If access works immediately but fails after a relaunch, check first whether the bookmark was persisted and whether the relaunched app still has the same identity and signing configuration. If only certain directories fail, rule out their additional privacy permissions before loosening sandbox access.
At a minimum, retain the delivered app version, final entitlements, category of directory containing the test file, results before and after selection, result after relaunch, and error type in the regression record. Log only the necessary states and errors; do not output raw bookmark data or file contents. Then, when a later build fails, the team can distinguish an entitlement change from a bookmark lifecycle problem or a test environment without a graphical session—instead of having to guess again based on “it opens on my machine.”
Frequently asked questions
Why save a bookmark after the user selects a file?
If the app must reopen that file after relaunch, save a security-scoped bookmark. Resolve it later, start access before reading, and stop access when the operation finishes.
Can I complete this regression test using SSH alone?
No. SSH is sufficient to inspect signing and entitlements, but the file picker and interactive access checks require a usable macOS graphical session.
Bring a cloud Mac into your workflow
VPSPush offers remotely accessible, dedicated physical Macs. Choose a location and rental term, then review the access details and final price in your order.