diff --git a/apps/desktop/loopx-control-plane/README.md b/apps/desktop/loopx-control-plane/README.md index 7eeb01f93b..e119d3aacb 100644 --- a/apps/desktop/loopx-control-plane/README.md +++ b/apps/desktop/loopx-control-plane/README.md @@ -77,6 +77,10 @@ it cannot depend on a macOS Documents-folder consent prompt. Ordinary `loopx doctor` retains its full operator/integration diagnostics. Terminal installation still revalidates enabled extensions by default; the App sets `LOOPX_INSTALL_REVALIDATE_EXTENSIONS=0` for its bounded bootstrap. +Runtime preparation isolates Git discovery to its verified export, so a +developer's Git environment or a checkout enclosing `TMPDIR` cannot supply +unrelated branch or dirty-state metadata. The bundled revision and digest +remain the source of the installed snapshot's identity. Failed runtime preparation remains supervised, with at most three automatic install attempts per App process and at least 30 seconds between attempts. Existing recovery controls remain available after that budget is exhausted, diff --git a/apps/desktop/loopx-control-plane/src-tauri/src/bundled_runtime.rs b/apps/desktop/loopx-control-plane/src-tauri/src/bundled_runtime.rs index 7559d828e3..baaa30bb08 100644 --- a/apps/desktop/loopx-control-plane/src-tauri/src/bundled_runtime.rs +++ b/apps/desktop/loopx-control-plane/src-tauri/src/bundled_runtime.rs @@ -189,6 +189,16 @@ fn install_snapshot(bytes: &[u8], metadata: &Value, private_root: &Path) -> Resu } command .current_dir(source.path()) + // This directory is a verified export, not the developer's checkout. + // Git must not label it using shell overrides or a repository above TMPDIR. + .env( + "GIT_CEILING_DIRECTORIES", + source.path().parent().ok_or("runtime_staging_failed")?, + ) + .env_remove("GIT_DIR") + .env_remove("GIT_WORK_TREE") + .env_remove("GIT_COMMON_DIR") + .env_remove("GIT_INDEX_FILE") .env("LOOPX_PROMOTE_DEFAULT", "1") // The archive staging directory is temporary, never a canary checkout. .env("LOOPX_INSTALL_CANARY", "0") @@ -301,6 +311,23 @@ mod tests { ) .unwrap(); assert_eq!(observed["source_revision"], metadata["source_revision"]); + let manifest: Value = serde_json::from_slice( + &fs::read( + fs::canonicalize(root.join("bin/loopx")) + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join("release.json"), + ) + .unwrap(), + ) + .unwrap(); + assert!( + manifest["source"]["git_ref"].is_null() && manifest["source"]["git_dirty"].is_null(), + "a bundled archive has no Git checkout; host repository facts are not its provenance" + ); assert_eq!( fs::read(default).unwrap(), original,