From da1770bb4425de5be5df316ca01f4611b7343de6 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 13 Jul 2026 03:17:49 +0000 Subject: [PATCH] Refuse sd.cpp install into unowned non-empty target dir When the install target already exists, is non-empty and lacks the .unsloth-studio-owned marker (a user's own stable-diffusion.cpp checkout, or unrelated files beside a custom Studio root), install() previously still extracted the release into it. Skipping the ownership marker only stopped the uninstaller from deleting the directory; extraction still merged binaries into the user's working tree and could overwrite same-named files. Fail up front with a clear message pointing the user at a fresh/empty location before any download or extraction, leaving their directory untouched. Update the ownership test suite to assert the refusal. --- studio/backend/tests/test_sd_cpp_install.py | 17 +++++++++-------- studio/install_sd_cpp_prebuilt.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/studio/backend/tests/test_sd_cpp_install.py b/studio/backend/tests/test_sd_cpp_install.py index 52ca6eb328..3a061c9fcf 100644 --- a/studio/backend/tests/test_sd_cpp_install.py +++ b/studio/backend/tests/test_sd_cpp_install.py @@ -273,11 +273,12 @@ def test_install_into_empty_dir_claims_ownership(tmp_path, monkeypatch): assert (empty / ".unsloth-studio-owned").is_file() -def test_install_into_nonempty_unowned_dir_does_not_claim_ownership(tmp_path, monkeypatch): +def test_install_into_nonempty_unowned_dir_is_refused(tmp_path, monkeypatch): # A pre-existing, non-empty directory that Studio did not create (e.g. a user's own - # stable-diffusion.cpp checkout) must NOT be marked owned: writing the marker would make the - # uninstaller recursively delete the user's directory. The install still proceeds (extracts the - # binary) but leaves the directory unowned so the uninstaller keeps it. + # stable-diffusion.cpp checkout) must NOT be extracted into. Merging the release into it would + # overwrite or mix our binaries into the user's working tree, and leaving it unowned only stops + # the uninstaller from deleting it later. install() refuses up front and leaves the dir untouched + # so the user can point us at a fresh/empty location. zb = _zip_with_sd_cli() _stub_release(monkeypatch, zip_bytes = zb, digest = "sha256:" + hashlib.sha256(zb).hexdigest()) target = tmp_path / "stable-diffusion.cpp" @@ -285,13 +286,13 @@ def test_install_into_nonempty_unowned_dir_does_not_claim_ownership(tmp_path, mo user_file = target / "USER_WORK" user_file.write_text("keep", encoding = "utf-8") - sd_cli = install(install_dir = target) + with pytest.raises(RuntimeError, match = "not a Studio-managed directory"): + install(install_dir = target) - # The user's file survives and no ownership marker was written. + # The user's directory is left exactly as it was: file intact, no marker, nothing extracted. assert user_file.read_text(encoding = "utf-8") == "keep" assert not (target / ".unsloth-studio-owned").exists() - # The binary was still installed (install is not refused). - assert sd_cli.is_file() and sd_cli.name == "sd-cli" + assert list(target.iterdir()) == [user_file] def test_reinstall_into_owned_dir_keeps_ownership(tmp_path, monkeypatch): diff --git a/studio/install_sd_cpp_prebuilt.py b/studio/install_sd_cpp_prebuilt.py index 4f99f6d364..1923cd3f8e 100644 --- a/studio/install_sd_cpp_prebuilt.py +++ b/studio/install_sd_cpp_prebuilt.py @@ -418,6 +418,18 @@ def install( _pre_existing_entries = True # Empty dir, or one we already own, may be (re)claimed; a non-empty unowned dir may not. _may_own = (not _pre_existing_entries) or marker.is_file() + # Refuse to extract into a pre-existing, non-empty directory we do not own (a user's own + # stable-diffusion.cpp checkout, or unrelated files beside a custom Studio root). Not writing + # the ownership marker only protects the uninstaller; extracting the release here would still + # merge our binaries into the user's working tree and can overwrite same-named files. Fail with + # a clear message so the user points us at a fresh/empty location instead of corrupting theirs. + if not _may_own: + raise RuntimeError( + f"sd.cpp install target already exists and is not a Studio-managed directory: {target}. " + f"Refusing to extract prebuilt binaries into it to avoid overwriting or mixing them " + f"into your files. Remove or move that directory, or install into a different, empty " + f"location (pass a different --install-dir / set the Studio sd.cpp install dir)." + ) used_repo, release, chosen = _resolve_with_fallback(accelerator, token) if release is None or not chosen: