fix(agent,git,terminal): strip AppImage paths from spawned tool env - #176
Conversation
Inside the Linux AppImage, AppRun and its GTK hook point the loader and toolkit variables into the bundle, so any host binary we spawn loads the bundle's Ubuntu 22.04 libraries — `git-remote-https` died with a symbol lookup error on newer distributions, breaking fetch and push. - `agent.rs` gains a subtract-only child-env sanitizer: a process-wide plan computed once from `$APPDIR` drops bundle entries from `PATH`-style lists (unsetting them when nothing survives, except `PATH`) and unsets the scalars the bundle owns, leaving `GDK_BACKEND`, `GTK_THEME` and `LD_PRELOAD` alone. It never clears the environment, so `SSH_AUTH_SOCK` and friends still inherit, and our own process env is untouched. - A `ChildEnv` trait applies the plan to tokio and std `Command` plus portable-pty's `CommandBuilder`, and every spawn site now calls it first: git runner (covering the credential helpers git spawns), gh and glab runners, forge reconnect, agent capture/stream and the login-shell probe, hook manager, MCP client CLI, sandbox build and container shell, fsops custom commands / terminal / open-with, and the PTY terminal. - Pure helpers are unit-tested off-Linux: path-boundary matching so a sibling mount isn't stripped by prefix, trailing-slash equivalence, all-bundle lists, host-only lists emitting no override, and `PATH` emptied rather than unset. - Adds the changelog fragment for the fixed fetch/push failure.
Deploying gitdesktop with
|
| Latest commit: |
ae716a5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ff1e6791.gitdesktop.pages.dev |
| Branch Preview URL: | https://fix-appimage-child-env-sanit.gitdesktop.pages.dev |
|
Context for reviewers — deliberate calls with their evidence, numbered for reference. (Fixes #174; sibling of #173, which fixed the app's own EGL crash — this one fixes what the AppImage does to processes it spawns.)
Posted by GitDesktop — automated agent comment, verify before acting on it. |
|
End-to-end proof from inside a real AppImage (context item 10, completed). Setup: ubuntu:24.04 container (the reporter's distro; host
Same container, same clone, same invocation — the only variable is the sanitizer. This is the live confirmation that the fix cures the reported failure where it occurs, complementing the unit tests and the earlier mechanism-level repro. (The dispatched Posted by GitDesktop — automated agent comment, verify before acting on it. |
There was a problem hiding this comment.
Pull request overview
This PR fixes Linux AppImage breakage where spawned host tools (git/gh/glab, terminal shells, hook managers, etc.) incorrectly inherit AppImage-bundled loader/toolkit environment variables, causing host binaries to load the AppImage’s Ubuntu 22.04 libraries and fail (e.g., git-remote-https: symbol lookup error).
Changes:
- Added a centralized child-environment sanitizer in
src-tauri/src/agent.rsthat strips$APPDIR-scoped paths from selected path-list env vars and unsets bundle-owned scalar env vars when they point into$APPDIR. - Applied the sanitizer at multiple backend process-spawn sites (git, forge CLIs, terminal launchers, hook manager, MCP client CLI, container shell).
- Added unit tests for the sanitizer rules and a changelog fragment describing the user-visible fix.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src-tauri/src/agent.rs | Implements the AppImage child env override plan, a shared sanitize_child_env helper, and unit tests for stripping/unsetting rules. |
| src-tauri/src/git/runner.rs | Sanitizes env before spawning git (covers git’s own spawned helpers). |
| src-tauri/src/github/runner.rs | Sanitizes env before spawning gh. |
| src-tauri/src/forge/glab.rs | Sanitizes env before spawning glab. |
| src-tauri/src/forge/session.rs | Sanitizes env before spawning reconnect child processes. |
| src-tauri/src/pty.rs | Sanitizes env for PTY-launched commands so terminal-run host tools aren’t poisoned by AppImage vars. |
| src-tauri/src/fsops.rs | Sanitizes env for launching custom commands, terminals, and “open with program” flows. |
| src-tauri/src/hooks.rs | Sanitizes env before spawning the git hook manager. |
| src-tauri/src/mcp.rs | Sanitizes env before spawning the MCP client CLI. |
| src-tauri/src/agent_sandbox.rs | Sanitizes env before spawning sandbox build and container terminal shell processes. |
| changelog.d/fixed-appimage-spawned-tools.md | Documents the AppImage spawned-tools fix for end users. |
Suppressed comments (1)
src-tauri/src/agent.rs:2694
- These comment lines are misleading:
strip_appdir_pathlistalways drops empty segments (!entry.is_empty()), even when no$APPDIRentries are removed. The reason the child keeps the original value is thatcompute_child_env_overridesemits no override whendropped == false.
// Empty segments are dropped only as a side effect of a real strip; with
// nothing bundle-derived to remove, the child inherits the value verbatim.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 GitDesktop AI review · The change adds a subtractive, per-child env sanitizer ( Correctness / coverage
Tests
Edge cases
Readability
Posted by GitDesktop — AI output, verify before acting on it. |
|
🤖 GitDesktop AI security audit · No genuinely exploitable vulnerability is introduced by these changes — the sanitizer only subtracts bundle-derived values from a fixed table of loader/toolkit vars, is applied before every site's own Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-1 dispositions for the AI review (security audit was clean; Copilot's three threads answered inline). All fixes are in the tree, riding the next push. SF1 ( SF2 (fragment over-claims) — accepted. The bullet now names the covered surfaces (git, GitHub/GitLab CLIs, agent tools, built-in terminal) instead of "other external tools", so it no longer promises the opener-plugin surfaces the fix deliberately excludes. SF3 (pin the vendored SF4 (table-drift guard) — accepted with one scope adaptation. Edge-case nit (empty Readability nits — all applied: the Constructor-pattern suggestion — deferred with a home. Reworking 18 sites into new-and-sanitize constructors late in the round trades churn for a class the drift guard and the helper's ordering contract already police; it's recorded in the backlog as a follow-up refactor. Full re-verification after the batch: 1083 tests green (13 in Posted by GitDesktop — automated agent comment, verify before acting on it. |
- `src-tauri/src/agent.rs`: a PATH whose every entry came from the bundle now falls back to `/usr/local/bin:/usr/bin:/bin` instead of being set empty — an empty PATH element means the current directory to POSIX exec, so a child could have resolved a tool out of the working tree. Also sanitize the environment of the `kill` spawned by the process-tree teardown, make `child_env_overrides` private, and add a test pinning that `portable_pty::CommandBuilder` snapshots the parent environment (otherwise the terminal's subtract-only applier would leave the bundle paths in place). - `.github/scripts/appimage-guard.sh`: fail the build when an AppRun startup hook exports an `$APPDIR`-derived variable that isn't stripped from child processes, so the guard stays the twin of `APPDIR_PATHLIST_VARS` / `APPDIR_SCALAR_VARS` as upstream's hooks change. - Changelog fragment: name the GitHub/GitLab CLIs and agent tools alongside git and the built-in terminal.
|
🤖 GitDesktop AI review · The re-review focuses on the four accepted fixes and the new guard script; the core sanitizer is unchanged and still sound. Nothing blocking — the only substantive item is a coverage gap in the newly added drift guard. Resolved since last review
Recorded decisions I'm not re-flagging: the "0 hooks passes silently" soft spot in the guard, and deferring the new-and-sanitize constructor refactor to the backlog. Correctness
Readability
Copilot's remaining open thread (gate on Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
|
Round-2 dispositions (re-review verdict was "no blocking issues"; one should-fix + two nits, all applied — in the tree, riding the final push):
Two small facts on the record for future readers: the The auto-fired Posted by GitDesktop — automated agent comment, verify before acting on it. |
|
Correction to my previous comment. I wrote that the Posted by GitDesktop — automated agent comment, verify before acting on it. |
- `.github/scripts/appimage-guard.sh`: scan `squashfs-root/AppRun` alongside the `apprun-hooks/*.sh` scripts, so a `$APPDIR`-derived export in the generated wrapper can no longer slip past the strip lists in `src-tauri/src/agent.rs`. Uses `grep -I` so a binary AppRun yields no matches instead of "Binary file matches", counts/reports "startup script(s)" now that hooks aren't the only source, and documents the shape limit (single-line `export NAME=…` only, which is what linuxdeploy emits). - `appimage-check.yml` / `release.yml`: rename the guard step to "Guard the AppImage bundle and its startup env" — it has covered both the bundled libwayland-client and the spawned-child environment since the previous change, and the old name only named the former.
|
🤖 GitDesktop AI review · Only the CI guard script and two workflow step names changed since the last round; the Rust sanitizer and all 16 spawn sites are byte-identical to what I reviewed and re-verified before. The three items from last round are all addressed, and the new guard hunks introduce no problems I can find. Resolved since last review
Spot-checked afresh this round: no production Linux spawn site remains unsanitized — Nothing further to raise — the deliberate calls on the record (zero-startup-scripts passing with a note, the deferred new-and-sanitize constructor refactor, the Verdict: no blocking issues — remaining items are non-blocking; merge when ready Posted by GitDesktop — AI output, verify before acting on it. |
Inside the Linux AppImage, AppRun and its GTK hook point the loader and toolkit variables at the bundle, so every host binary we spawn (git, gh, glab, the terminal's shell) loads the bundle's Ubuntu 22.04 libraries instead of the system's — most visibly as
git-remote-https: symbol lookup erroron fetch/push against newer distributions. This adds a single subtractive sanitizer that removes the bundle's paths from each child's environment at spawn time, and applies it at every process-spawn site in the backend.Environment sanitizer (
src-tauri/src/agent.rs)compute_child_env_overridesplus theAPPDIR_PATHLIST_VARS/APPDIR_SCALAR_VARStables:PATH-style lists (LD_LIBRARY_PATH,PATH,XDG_DATA_DIRS,GTK_PATH, the twoGST_PLUGIN_SYSTEM_PATHspellings) have their$APPDIRentries dropped in place and are unset when nothing survives; single-path vars (GSETTINGS_SCHEMA_DIR,GTK_EXE_PREFIX,GTK_DATA_PREFIX,GTK_IM_MODULE_FILE,GDK_PIXBUF_MODULE_FILE,GIO_EXTRA_MODULES) are unset only when they point into the bundle.strip_appdir_pathlist/is_under_appdirmatch on a path boundary (and normalize a trailing slash) so a sibling mount sharing our prefix isn't stripped, and an override is emitted only when a bundle entry was actually dropped — a host-only list is inherited verbatim.PATHis emptied rather than unset so a child always has one.child_env_overrides()behind aOnceLock, gated on Linux and on a non-emptyAPPDIR(which also covers--appimage-extractruns, whereAPPIMAGEis absent); off-AppImage it is empty and every applier is a no-op. Comments record the deliberate exclusions —GDK_BACKEND,GTK_THEMEandLD_PRELOADare left alone — and that the environment is never cleared wholesale, so things likeSSH_AUTH_SOCKstill inherit.ChildEnvtrait withsanitize_child_env, implemented fortokio::process::Command,std::process::Commandandportable_pty::CommandBuilder, so one helper covers every builder the app spawns through. It is called before each site's own.env()calls so explicit settings win.Spawn sites
git/runner.rs(run_git_raw_input, which also covers the credential helpers git spawns in turn),github/runner.rs(run_gh_raw,run_gh_input),forge/glab.rs(run_glab_raw,run_glab_ex) andforge/session.rs(run_reconnect_child).pty.rs(pty_open, with its comment updated to describe the subtract-then-advertise-TERMapproach) andfsops.rs(launch_custom_command, both fallback loops inlaunch_terminal_unix, andopen_with_program).agent.rs(resolve_via_login_shell,run_capture,stream_agent),agent_sandbox.rs(run_build, pluslaunch_container_shell, whose chained builder is unfolded into a localterm_cmd),hooks.rs(git_run_hook_manager) andmcp.rs(run_client_cli).Tests and changelog
child_env_testsmodule insrc-tauri/src/agent.rscovering the rules off-Linux via a fixture lookup: untouched host lists, all-bundle lists collapsing to unset, in-place removal preserving host order, trailing-slash equivalence, the prefix-vs-boundary case, degenerate/empty segments, the never-unsetPATHbehavior, scalars equal to$APPDIR, the excluded variables, and the empty-APPDIRno-op.changelog.d/fixed-appimage-spawned-tools.mddescribing the fix in user terms.Relates to #174