fix: open host URLs with a clean environment (triple-c#34)

On Linux the app ships as a single AppImage, and the AppImage environment
leaks into everything it spawns. linuxdeploy's AppRun, linuxdeploy-plugin-gtk
and our own wayland fallback hook all export LD_LIBRARY_PATH, GTK_PATH,
GIO_MODULE_DIR and friends pointing inside $APPDIR, and main.rs sets
WEBKIT_DISABLE_DMABUF_RENDERER process-wide for the webview. A browser that
is already running shrugs this off, because xdg-open just hands the URL to
the existing process. A cold-launched one inherits the lot and dies before
painting -- with xdg-open still exiting 0, which is why this looked like the
button doing nothing at all.

`url_open` captures a pristine snapshot of the environment in main() before
any mutation runs, then hands children a repaired copy: a saved original is
restored where one exists, otherwise the process-start value is restored
where we changed it, otherwise only the colon-separated entries that live
under $APPDIR are dropped and the user's own are kept. Outside an AppImage
it is a no-op.

The command re-validates the URL in Rust rather than trusting the frontend,
because the URL originates in an untrusted container: http/https only, no
embedded credentials, no control characters or whitespace, length capped,
ASCII asserted before it reaches execvp, and error messages never echo the
input. Spawning is Command with explicit args and never a shell, trying
xdg-open then gio open.

No portal. org.freedesktop.portal.OpenURI would pull in a D-Bus client stack
for one call on the one platform where we ship self-contained, and it only
helps where a portal is running -- the same case where xdg-open already
works once the environment is clean. `gio open` as a second candidate
recovers most of the missing-MIME-association case for free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 10:08:20 -07:00
co-authored by Claude Opus 5
parent bf8094dbc4
commit 9297020688
5 changed files with 722 additions and 0 deletions
+4
View File
@@ -39,6 +39,10 @@ local-ip-address = "0.6"
argon2 = "0.5"
aes-gcm = "0.10"
zeroize = "1"
# WHATWG URL parsing for `url_open`'s re-validation of URLs arriving from the
# container. Already in the tree transitively (reqwest), and the point of
# using it rather than hand-rolling is parity with the frontend's `new URL()`.
url = "2"
[dev-dependencies]
# `test-util` (not part of tokio's `full`) lets the auto-start retry tests run