diff --git a/crates/native/src/backend.rs b/crates/native/src/backend.rs
--- a/crates/native/src/backend.rs
+++ b/crates/native/src/backend.rs
@@ -106,8 +106,11 @@ impl NativeBackend {
     pub fn new(waker: impl Fn() + Send + Sync + 'static) -> Self {
         let waker: Waker = Arc::new(waker);
         let paths = Paths::new().unwrap_or_else(|e| {
-            log::error!("{e}; keeping files in the temporary folder");
-            Paths::under(&std::env::temp_dir().join("artcraft-launcher"))
+            log::error!("{e}; trying a private temporary folder");
+            Paths::private_temp().unwrap_or_else(|e| {
+                log::error!("{e}; filesystem operations will be unavailable");
+                Paths::unavailable()
+            })
         });
         let http = match Http::new() {
             Ok(h) => Some(h),
diff --git a/crates/native/src/paths.rs b/crates/native/src/paths.rs
--- a/crates/native/src/paths.rs
+++ b/crates/native/src/paths.rs
@@ -29,6 +29,24 @@ impl Paths {
         Self { data: root.join("data"), cache: root.join("cache") }
     }
 
+    pub(crate) fn private_temp() -> Result<Self, String> {
+        // shortcut: fallback installs aren't rediscovered after restart; add a persistent per-user root if this becomes common.
+        let dir = tempfile::Builder::new()
+            .prefix("artcraft-launcher-")
+            .tempdir()
+            .map_err(|e| format!("couldn't create a private temporary folder ({e})"))?;
+        Ok(Self::under(&dir.keep()))
+    }
+
+    pub(crate) fn unavailable() -> Self {
+        // These device paths cannot contain writable child directories, so failures stay safe.
+        #[cfg(unix)]
+        let root = std::path::Path::new("/dev/null");
+        #[cfg(windows)]
+        let root = std::path::Path::new(r"\\.\NUL");
+        Self::under(root)
+    }
+
     pub fn downloads(&self) -> PathBuf {
         self.cache.join("downloads")
     }
@@ -110,6 +128,30 @@ mod tests {
         assert_eq!(std::fs::read(&path).unwrap(), b"new manifest");
     }
 
+    #[test]
+    fn private_temporary_paths_are_unique_and_private() {
+        let first = Paths::private_temp().unwrap();
+        let second = Paths::private_temp().unwrap();
+        let first_root = first.data.parent().unwrap();
+        let second_root = second.data.parent().unwrap();
+        assert_ne!(first_root, second_root);
+        assert!(first_root.file_name().unwrap().to_string_lossy().starts_with("artcraft-launcher-"));
+        #[cfg(unix)]
+        {
+            use std::os::unix::fs::PermissionsExt;
+            assert_eq!(std::fs::metadata(first_root).unwrap().permissions().mode() & 0o777, 0o700);
+        }
+        std::fs::remove_dir_all(first_root).unwrap();
+        std::fs::remove_dir_all(second_root).unwrap();
+    }
+
+    #[cfg(unix)]
+    #[test]
+    fn unavailable_paths_cannot_create_writable_folders() {
+        let paths = Paths::unavailable();
+        assert!(std::fs::create_dir_all(paths.data).is_err());
+    }
+
     #[test]
     fn atomic_write_replaces_a_complete_file_and_cleans_up_on_failure() {
         let dir = tempfile::tempdir().unwrap();
