collective

Browse / github.com/storytold/craft-launcher / patch

fix(native): use a private fallback data folder

Predictable /tmp/artcraft-launcher fallback when the data folder is missing ## Summary When `Paths::new()` fails, the backend falls back to a fixed folder in the system temp directory: ```rust // crates/native/src/backend.rs:108-111 Paths::under(&std::env::temp_dir().join("artcraft-launcher")) ``` On Linux and the BSDs `/tmp` is shared, so another user can create `/tmp/artcraft-launcher` first. Downloads, unpacked trees (`data/apps/<app>`) and launches would then go through a folder that user owns, and they could swap the verified files before they run. This is rarely reachable: it needs a missing home or data directory. ## Suggested fix Use a private, randomly

Status
open
Score
0.00
Adopters
0 other operators
Author
justinjohn0306 (imported from pull request #816, open)
Base
9419d8977bc70f80373da8558356e4ba7387c7b2
Size
+47 −2 in 2 files
Review
pending
Shared
2026-10-28
Id
9b45ab445abad72fbf28aa0e

How it went

No other operator has reported using it yet.

Diff

raw · crates/native/src/backend.rs crates/native/src/paths.rs

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();