Files
punktfunk/docs-site/content/docs
enricobuehler 6dd4add11b
ci / bun-nix (pull_request) Successful in 20s
android / android (pull_request) Canceled after 1m22s
ci / rust-arm64 (pull_request) Successful in 1m17s
ci / rust (pull_request) Canceled after 1m22s
ci / web (pull_request) Canceled after 1m18s
ci / docs-site (pull_request) Canceled after 1m18s
fix(library): Steam's art lives in Program Files, which was never an allowed art root
A field report: the Steam plugin installed, the grid stayed empty, and the
only clue was one warn per sync — `art.hero: local art must be an image file
… inside an allowed art root`.

Two defects, both here.

The art roots defaulted to the users base (`C:\Users`, from `%PUBLIC%`'s
parent). That covers the launchers that install per-user, but not Steam,
which installs to `C:\Program Files (x86)\Steam` and keeps both the things
the plugin publishes there — `appcache\librarycache\<appid>\<hash>\` and each
account's `userdata\<id>\config\grid\`. So every cover was out of root. It is
a v0.28.0 regression: the built-in scanner the plugin replaced served covers
through the legacy `steam:` art-proxy branch, which never passed through the
H-2 confinement, so deleting the scanner routed that art through a gate it
had never been measured against. `art_roots()` now also carries every Steam
install it can find, from the three Program Files vars and from HKLM
`Valve\Steam\InstallPath` so a Steam on another drive counts too. POSIX needs
no equivalent — native and Flatpak Steam are both already under `$HOME`.

The confinement is not weakened. It exists to stop the host (SYSTEM) reading
what the plugin lane (LocalService) cannot reach itself; the Steam directory
is readable by LocalService already, so nothing there is reachable *because*
the host is privileged, and the extension, regular-file, magic-byte and
config-dir gates still apply on top. Tested: `config.vdf` is not servable
from an art root, nor is a non-image wearing `.png`.

Second, and the reason this cost a whole library rather than a thumbnail: the
provider reconcile validated art per entry and 400'd the WHOLE payload on the
first bad value. A path mismatch therefore deleted every game from that
store, and the plugin — which only ever sees `HostRequestError` — could not
say which. A reconcile now strips unservable local art and syncs the rest,
logging one aggregated warn with the count, an example path and the env var.
The invariant the 400 held is unchanged: no unservable path is persisted. The
operator's own single-entry writes keep the hard 400, because there the path
was typed by hand and silence would be the wrong answer.

Verified on Linux (.25: 493/493, clippy clean) and Windows (.133: 12/12 art
tests, clippy clean). The new Windows test is hermetic — it repoints
`%ProgramFiles(x86)%` at a synthetic Steam tree rather than asserting over
whatever Steam the box happens to have, since the vacuous version of that
test is what would have let this ship. Confirmed non-vacuous by disabling the
fix: it fails on "the DEFAULT art roots must include it".
2026-08-14 08:38:43 +02:00
..