446c2c72de
#4639 added a strict `app.fs_scope().is_allowed()` check to download_file/ upload_file. On Android that returns false for the app's own storage, so every download into the app dir (book covers, dictionaries, books, gloss packs, OPDS books in the cache dir) failed with "permission denied: path not in filesystem scope". Root cause: download_file/upload_file are plain app commands using raw tokio::fs, so Tauri does not scope file_path. The capability scope patterns that cover the app's storage ($APPDATA/Readest/**, $APPCACHE/**, **/Readest/**/*) are command-scoped and absent from the global fs_scope() FsExt exposes (it is initialized FsScope::default() and only ever gains runtime dialog/persisted-scope grants), so is_allowed() returns false for the app's own files. Interim fix mirroring dir_scanner::read_dir: keep rejecting relative and `..` paths, then accept the path if the fs scope allows it (persisted dialog grants for custom/external roots) OR it lives inside the app's own storage — matched by the `Readest` data folder or the app's bundle identifier (app.config(). identifier), which the Android sandbox (/data/user/0/<id>/…, cache dir included) and the desktop identifier dirs always carry. The `..` rejection keeps the GHSA-55vr-pvq5-6fmg hardening: foreign targets like ~/.ssh/id_rsa carry neither segment and stay blocked. Follow-up (tracked separately): replace the substring fallback with a BaseDirectory + relative path resolved via app.path(), so targets are in-scope by construction with no string markers. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>