阅读器在多会话朗读、加密 EPUB 资源、并发打开同一本书和多 UserDefaults 容器等场景下,存在旧异步结果覆盖新状态、锁屏控制串会话、大型明文资源被误拒绝、解压半成品被复用及标注跨容器混用的风险;同时高亮、书签、搜索和设置面板的空状态与自动化可访问性入口不完整。\n\n本次为朗读会话引入代次和当前 utterance 校验,远程控制改为仅响应当前会话并按自身 token 清理;加密资源 provider 先判定是否实际返回解密数据,明文大资源继续流式读取;解压缓存串行化以避免并发复用未完成目录。书签与高亮迁移至受保护文件,按 UserDefaults 容器隔离命名空间,保留标准容器旧文件兼容并确保新副本成功落盘后才删除历史数据。\n\n同步调整缓存淘汰、FoundationModels 弱链接、搜索/标注/设置面板交互与 UI 测试,并更新 Pod 生成配置及 API、风险文档。已执行 git diff --check 和 ReadViewDemo Debug Simulator 构建,结果为 BUILD SUCCEEDED。
9.6 KiB
9.6 KiB
代码库风险与关注点
分析日期: 2026-05-21 状态复核: 2026-07-30(逐条对照源码,结论见下表;正文段落保留原始分析,勿当作现状)
| # | 项目 | 现状 |
|---|---|---|
| 1 | User Script Sandboxing 被关 | 部分处理:已移除 Podfile 的全局强制覆盖;但显式打成 YES 会让 CocoaPods 的 [CP] Embed Pods Frameworks rsync 阶段被沙箱拒绝而构建失败(已实测)。要真正开启需先改掉 use_frameworks! 动态框架集成方式。 |
| 2 | 外链未做 allowlist | 已修:RDEPUBReaderController+ExternalLinks.swift 有 scheme allowlist + 二次确认弹窗 + delegate 否决。 |
| 3 | Zip Slip 未加固 | 已修:RDEPUBParser+Archive.swift 的 validatedExtractionDestination 做 standardized 前缀校验并拒绝 ../绝对路径。 |
| 4 | 高亮/选中文本明文写 UserDefaults | 已修:新增 RDEPUBProtectedContentStore,高亮与书签改写 Application Support 下带 file protection 的文件、排除备份,首次读取自动迁移并删除明文;另加 clearReadingData(for:) / clearAllReadingContent()。 |
| 5 | Scheme handler 整文件读内存 | 原判断有误:>512KB 早已走 respondWithStreaming 64KB 分块。本次补上 maximumInMemoryResourceBytes(默认 64MB)给走不了流式的加密 provider 分支兜底。 |
| 6 | 分页 WKWebView 可能加载外部资源 | 未复核。 |
| 7 | 解压缓存无限累积 | 已修:pruneExtractionCache(keeping:) 先按 extractionCacheMaximumAge(30 天)再按 extractionCacheMaximumTotalBytes(512MB)做 LRU 淘汰;复用时 touch mtime;另有 clearExtractionCache()。 |
| 8+ | 其余性能/可维护性项 | 未复核。 |
高风险:安全与隐私
1) CocoaPods 构建关闭了 User Script Sandboxing
- 问题:
post_installhook 将 Pods 以及 用户工程的ENABLE_USER_SCRIPT_SANDBOXING强制设置为NO。 - 影响: 削弱对内嵌 Web 内容的纵深防御;增加
WKWebView相关功能的风险面,并可能不符合组织/审核(含 App Store)安全基线。 - 建议缓解:
- 移除全局覆盖,保持系统默认的 sandboxing。
- 如确有依赖需要 workaround,仅对具体 Pod target 做最小范围的设置,并记录原因。
- 在 CI 中增加校验:出现
ENABLE_USER_SCRIPT_SANDBOXING=NO时告警/失败。
2) 外链打开未做 allowlist(scheme/host)
- 问题: EPUB 内容中的外部 URL 通过
UIApplication.shared.open(...)打开时,未对 scheme/host 做校验,也未强制用户二次确认。 - 影响: 恶意 EPUB 可能触发钓鱼、隐私泄露,或打开意外的 URL scheme(包含跳转到其他 App 的 deep link)。
- 建议缓解:
- 强制 allowlist(默认仅允许
https;如需mailto/tel等应在明确 UI 提示后允许)。 - 弹出确认对话框,展示目标 host。
- 对 bridge 消息与导航行为中的非 HTTP(S) scheme 做拒绝/过滤。
- 强制 allowlist(默认仅允许
3) EPUB 解压未显式做 Zip Slip(路径穿越)加固
- 问题: 解压逻辑构造
destinationURL = extractionURL.appendingPathComponent(entry.path)并解压 entry,但未显式验证标准化后的目标路径是否仍在extractionURL之内。 - 影响: 构造的 EPUB 可能尝试通过
../...路径穿越覆盖目标目录外的文件(实际危害与 ZIPFoundation 行为及权限有关,但建议显式防护)。 - 建议缓解:
- 解压前计算
standardizedDestination = destinationURL.standardizedFileURL,并确保其前缀位于extractionURL.standardizedFileURL.path + "/"之下。 - 拒绝包含
..、绝对路径、或异常路径分隔符形式的 entry。 - 考虑先解压到新的随机 UUID 目录,仅将校验通过的内容移动到最终目录。
- 解压前计算
4) 选中文本/高亮内容明文写入 UserDefaults
- 问题: 选中文本与高亮 payload 以 JSON 编码写入
UserDefaults(包含“新”持久化与 legacy 路径)。 - 影响: 可能将书籍敏感内容(高亮、选择文本)存入默认不加密的持久化位置,可能进入备份;同时数据量可能无限增长(性能与隐私风险)。
- 建议缓解:
- 仅持久化稳定标识/范围(例如 CFI / rangeInfo),运行时再派生文本。
- 如必须持久化文本,存入加密存储(例如 Keychain 或使用
NSFileProtectionComplete的加密文件)并设置大小上限。 - 增加数据保留策略,并提供 “Clear reading data” API。
性能与稳定性风险
5) Scheme handler 将整文件一次性读入内存(无流式)
- 问题:
WKURLSchemeHandler使用Data(contentsOf:)读取资源并一次性返回。 - 影响: EPUB 内的大图/字体/媒体资源可能导致内存峰值、加载变慢,甚至在内存压力下被系统终止。
- 建议缓解:
- 尽可能改为基于
InputStream的流式传输,并使用增量didReceive(...)。 - 对单个资源增加大小上限,超过则优雅失败并提示。
- 尽可能改为基于
6) 隐藏的分页 WKWebView 可能加载外部资源
- 问题:
RDEPUBPaginator使用loadFileURL(...allowingReadAccessTo:),但未看到对navigationAction的 allowlist(例如仅允许file://或自定义ss-reader://)。 - 影响: 分页测量阶段可能产生意外网络请求,引发隐私泄露与不可控性能波动。
- 建议缓解:
- 在 paginator 场景加入导航策略:默认仅允许
file://(以及必要的自定义 scheme),对http(s)直接取消。 - 视需求考虑内容拦截规则或禁用分页阶段的网络加载。
- 在 paginator 场景加入导航策略:默认仅允许
7) EPUB 解压缓存目录可能无限累积
- 问题: 解压路径基于文件名/大小/mtime 的确定性签名;若目录已存在则直接复用,且无清理策略。
- 影响: 书籍数量多或频繁更新时磁盘占用持续增长,造成存储压力与潜在卡顿。
- 建议缓解:
- 为
ssreaderview-epub缓存目录实现淘汰策略(LRU/按时间)。 - 提供显式清理 API,并在低存储信号时触发清理。
- 为
可维护性与技术债
8) DEBUG 下日志与 Web Inspector 可能泄露内容
- 问题: Debug 辅助会打印导航/消息体;selection payload 可能包含用户选中文本;且 DEBUG 下可能默认开启 inspectable。
- 影响: 在 debug 构建(含内部测试/QA)中,日志可能包含敏感内容并被导出/分享。
- 建议缓解:
- 默认对消息体做脱敏(仅记录类型/大小),需要时再显式 opt-in 输出内容。
- 将
isInspectable受控于 app 级 debug 开关,而不是在 DEBUG 下默认启用。
构建与依赖脆弱性
9) 仓库中包含 vendored 的 Pods/ 目录
- 问题: 仓库根目录有
Pods/,示例工程内也有ReadViewDemo/Pods/。 - 影响: diff 体积大、clone 慢、易产生 merge 冲突,也容易出现依赖陈旧导致的构建不一致。
- 建议缓解:
- 通常建议不要提交 Pods,仅提交
Podfile.lock,在 CI 中执行pod install。 - 如确有 vendoring 政策,需文档化并提供同步/校验工具,避免漂移。
- 通常建议不要提交 Pods,仅提交
10) Podfile 与 podspec 的部署版本不一致
- 问题:
Podfile设为platform :ios, '15.6',而 podspec 声明s.platform = :ios, "15.0"。 - 影响: 对外承诺与本地构建基线不一致,容易造成集成方预期偏差。
- 建议缓解:
- 对齐
Podfile、*.podspec、Xcode project 的 deployment target。 - 在 CI 中增加漂移检查,防止版本被无意改动。
- 对齐
11) podspec 将 Swift 版本钉死为 5.10
- 问题:
s.swift_versions = ["5.10"]将 pod 与特定 Swift 版本强绑定。 - 影响: 旧工具链的集成方无法使用;未来升级可能出现“突然不兼容”,尤其当依赖未同步时。
- 建议缓解:
- 如兼容性允许,可声明更多支持版本(或范围),并在 CI 中覆盖多个 Xcode/Swift 版本构建验证。
- 在文档中明确最低支持的 Xcode/Swift 版本。
不清晰/容易踩坑的契约
12) 默认的持久化协议实现可能静默丢失数据
- 问题:
RDEPUBReaderPersistence通过 protocol extension 提供了 bookmarks/settings 的默认 no-op 实现。 - 影响: 集成方可能误以为这些能力默认会持久化,但实际数据会被静默丢弃,且不易排查。
- 建议缓解:
- 将关键方法改为必须实现(移除 no-op 默认实现);或在未实现且功能启用时输出日志/assert。
- 提供并文档化 “最小持久化” 与 “完整持久化” 的参考实现。
Evidence(仓库路径)
- Build flags:
Podfile - Pod metadata/toolchain:
RDEpubReaderView.podspec - Archive extraction:
Sources/RDEpubReaderView/EPUBCore/RDEPUBParser+Archive.swift - Resource path validation(post-extraction):
Sources/RDEpubReaderView/EPUBCore/RDEPUBParser+Resources.swift - Scheme handler reads full file bytes:
Sources/RDEpubReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift - WebView bridge + message handling:
Sources/RDEpubReaderView/EPUBCore/RDEPUBWebView+JavaScriptBridge.swift - External link opening:
Sources/RDEpubReaderView/EPUBUI/RDEPUBReaderController.swift - UserDefaults persistence implementation:
Sources/RDEpubReaderView/EPUBUI/RDEPUBReaderPersistence.swift - Hidden paginator web view:
Sources/RDEpubReaderView/EPUBCore/RDEPUBPaginator.swift - Debug logging:
Sources/RDEpubReaderView/EPUBCore/RDEPUBWebViewDebug.swift - Vendored dependencies:
Pods/、ReadViewDemo/Pods/
风险与关注点审计:2026-05-21