fix(install): skip and reject links when extracting .eti archives

Security audit findings EXP2-SEC-01, SEC-IPC-04 and Codex #3 (symlink
redirection through externally extracted archives).

The ordinary install path hands every root `.eti` archive to an external
`unrar x` process and promotes the resulting staging directory to
`local/` as soon as the extractor exits 0. Nothing inspected what unrar
materialised. A symbolic link (or, on Windows, a junction) inside an
archive would survive promotion and later redirect the launch-time
settings rewrite in `apply_launch_settings_once`, the uninstall path,
or any script the game ships. The archives themselves are BLAKE3
verified against the bundled catalog before they can be installed, so a
hostile link would have to be published by the catalog operator; this
is defense in depth rather than a live remote exploit.

Two independent layers now guard promotion:

- Both `unrar` invocations (Tauri sidecar and peer-cli external
  unpacker) pass `-ol-`, which makes unrar 7.x skip symbolic-link
  entries entirely. The bundled 7.10 sidecar was checked against a
  fixture archive.
- `install_inner`/`update_inner` walk the staging tree without
  following links and refuse to promote it if any entry is a symlink or
  (on Windows) carries the reparse-point attribute. The normal rollback
  then removes staging and clears the install intent.

The audit's `-sl-` suggestion does not exist in unrar; `-sl<size>` is a
size filter. Post-extraction digest verification of every extracted
file remains out of scope for the ordinary path; Stream Install already
verifies each output entry.

Test plan: `just test` (new unix test installs with a fake unpacker
that plants a symlink and asserts install fails, `local/` is absent and
the intent is cleared; the peer-cli controlled-unrar test checks the
new argument position). Manual: install a fixture game via peer-cli.

Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg
This commit is contained in:
ddidderr committed 2026-09-02 22:32:43 +02:00
1 parent 887e245638
commit 84cbabfeba
3 files changed
+99 -5

No files matched your search

@@ -3201,6 +3201,10 @@ async fn run_unrar_sidecar(
[
std::ffi::OsString::from("x"),
std::ffi::OsString::from("-p-"),
// Skip symbolic links inside the archive entirely. The install
// transaction additionally refuses to promote a tree containing
// links, so a link can never redirect later file operations.
std::ffi::OsString::from("-ol-"),
paths.archive.as_os_str().to_owned(),
std::ffi::OsString::from("-y"),
std::ffi::OsString::from("-o"),