驗收 macOS 應用程式的沙盒檔案存取與安全範圍書籤

Security ·約 6 分鐘閱讀

驗收 macOS 應用程式的沙盒檔案存取與安全範圍書籤

在遠端 Mac 上開發的 macOS 檔案讀取功能,於測試目錄中運作正常,交付使用者後卻出現「無權存取」。常見原因是測試檔案剛好位於應用程式自己的容器內,或只驗證選取檔案當下能否讀取,沒有確認應用程式結束後能否再次開啟。驗收這類功能時,測試對象應是由使用者主動選取、位於容器外的檔案,並將驗收延伸至應用程式重新啟動之後。

先釐清測試邊界

App Sandbox 會限制應用程式存取容器外的檔案。使用者透過系統檔案選取視窗授予存取權,不代表應用程式往後能永久憑原始路徑讀取該檔案。如果產品需要記住這個檔案,應用程式還必須儲存安全範圍書籤;下次啟動時解析書籤,並在實際讀寫期間啟用存取權。

準備一份內容容易辨識的一般文字檔,放在應用程式容器外。不要選擇受額外系統隱私權限保護的目錄,以免將另一套授權機制混入這次測試。另備一份容器內的檔案作為對照:能讀取它,只能證明基本檔案處理程式碼可用,不能證明外部檔案的存取流程正確。

驗收標準不是「選取視窗關閉後沒有報錯」,而是「應用程式重新啟動後,仍能在授權範圍內讀取指定檔案,並在授權無法使用時提供可供使用者復原的操作」。

檢查交付應用程式的權限宣告

先檢查實際交付的 .app,不要只看專案中編輯過的權限檔案。如果建置設定或簽署過程有落差,最終成品的宣告可能不同。將下方的 APP 改成本次建置成品的實際路徑:

APP="$PWD/build/Reader.app"
codesign --verify --strict --verbose=2 "$APP"
codesign -d --entitlements :- "$APP"

確認沙盒權限是否啟用。若功能只是以唯讀方式匯入檔案,應檢查使用者所選檔案的讀取權限;確實需要修改原始檔案時,才考慮讀寫權限。若流程需要在應用程式重新啟動後保留存取權,還須檢查應用程式範圍書籤的權限宣告。不要為了讓測試通過,就擴大對無關目錄的存取權限。

簽署檢查只能回答「宣告了什麼」,無法回答「使用者是否完成選取」或「書籤能否恢復存取權」。後兩者必須實際執行應用程式才能驗證。

儲存書籤,並成對管理存取的啟用與結束

NSOpenPanel 傳回使用者所選的 URL 後,建立安全範圍書籤,並將取得的 Data 存入應用程式自身的持久儲存空間。不要只儲存 url.path:路徑只記錄位置,不包含日後重新取得存取權所需的資訊。

讀取時,將解析書籤、啟用存取權及結束存取寫在同一個函式內,以免發生錯誤時漏掉清理步驟:

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)
}

建立書籤時也必須使用安全範圍選項,並妥善儲存傳回的資料。此範例遇到過期書籤便會停止讀取;產品介面應提供重新選取檔案的途徑,在取得新授權後重新建立書籤。不能省略對 startAccessingSecurityScopedResource() 成功與否的檢查,也不能等到應用程式結束時才呼叫 stopAccessingSecurityScopedResource()。

在圖形工作階段中執行重啟回歸測試

雲端 Mac 可以透過命令列檢查簽署,但要驗收 NSOpenPanel 的互動流程,必須有可用的 macOS 圖形工作階段。不要把 SSH 工作能夠執行,誤認為檔案選取視窗也一定能正常顯示。自動化測試同樣要確認測試程序執行於具備圖形工作階段的環境。

使用同一份測試檔案依序執行以下步驟,並記錄每一步的預期結果與實際結果:

  1. 首次啟動應用程式,不開啟選取視窗,嘗試憑預先儲存的路徑直接讀取外部檔案;確認應用程式無法只靠路徑繞過預期的授權流程。
  2. 在視窗中主動選取該檔案,讀取並核對內容,同時儲存書籤。
  3. 完全結束應用程式,再啟動同一份交付成品;解析書籤,重新讀取並核對內容。
  4. 移走測試檔案後再試;應顯示容易理解的錯誤與重新選取的入口,而不是將檔案不存在誤報為簽署故障。
  5. 使用損壞的書籤資料測試錯誤處理分支;應用程式應停止讀取並要求重新選取,不應反覆彈出視窗或無限重試。

第三步最容易遺漏:只在單次程序執行期間讀取,可能掩蓋書籤未寫入持久儲存空間的問題。如果測試要求將資料寫回檔案,還應另以讀寫權限重複這組操作,不能用唯讀測試替代。

依據證據定位失敗原因

「選取後立即失敗」時,先檢查是否正確使用選取視窗傳回的 URL,以及權限宣告是否符合讀寫需求。「立即成功,重新啟動後失敗」時,優先確認書籤是否確實持久儲存,以及重新啟動的是否仍是相同的應用程式身分與簽署設定。「只有某些目錄失敗」時,先排除目錄本身受到額外隱私權限保護的可能,不要直接放寬沙盒權限。

回歸測試紀錄至少應保留交付應用程式版本、最終權限宣告、測試檔案所在的目錄類型、選取前後的結果、重新啟動後的結果,以及錯誤類型。日誌只記錄必要的狀態與錯誤,不輸出書籤原始資料或檔案內容。如此一來,下次建置失敗時,團隊便能判斷問題出在權限宣告變更、書籤生命週期,還是測試環境缺乏圖形工作階段,不必再憑「在我的電腦上可以開啟」從頭猜測。

常見問題

使用者選取檔案後,為何還要儲存書籤?

應用程式若需要在重新啟動後繼續存取該檔案,就應儲存安全範圍書籤;再次讀取前須解析書籤並啟動存取,完成後結束存取。

只靠 SSH 就能完成整套驗收嗎?

不能。簽章與權限宣告可以從命令列檢查,但檔案選取視窗及互動式存取測試需要可用的 macOS 圖形工作階段。

讓雲端 Mac 融入你的工作流程

VPSPush 提供可遠端連線的專屬實體 Mac。選擇節點與租用期間後,請在訂單中確認連線資訊與總金額。

查看雲端 Mac 方案並下單