验收 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 方案并下单