fix(app): a saved file is the own user's from the start, and replaces the one chosen whole

The reading of the full reading's fixes found that writePrivately made
the file, then set it to 0600: another local user could open it in that
window and keep reading what was written after; and that where the path
was a link or another user's file, chmod failed and the secret was
written into it anyway.

On Linux and macOS it now makes a new file beside the one chosen, 0600
as it is created (O_EXCL, O_NOFOLLOW), writes through the descriptor that
made it, and renames it over the chosen path, so the old file is never
written into; a symbolic link or another user's file is refused, with
nothing written (lstat through FFI, by each system's layout). Windows
writes in place, with its folder's rights.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 16:16:00 +02:00
commit 801d0c6679
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -100,9 +100,12 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
compiler and make: sodium's build hook compiles libsodium.
- **Files** (`FileSaver`, `PlatformFileSaver`): keeping a file the app made
where the person chooses (a recovery phrase, recovery codes, an app's
exports): on the desktop the own user's alone (0600, `writePrivately`), on
iOS without the copy file_picker leaves in the temporary folder
(`forgetPickerCopy`).
exports): on Linux and macOS the own user's alone from the start
(`writePrivately`: a new file beside it, made 0600 with `O_EXCL`, written
through its descriptor, renamed over the one chosen, never written into;
a symbolic link or another user's file refused), on Windows written in
place with its folder's rights, on iOS without the copy file_picker leaves
in the temporary folder (`forgetPickerCopy`).
- **Sync** (`lib/src/sync/`): `SyncService` (once at start, shortly after
local changes, on resume and every few minutes, one sync at a time, counting
the changes not on the server yet, `PendingChangesStore`), `AppCompatibility`