diff --git a/crates/native/src/install.rs b/crates/native/src/install.rs
--- a/crates/native/src/install.rs
+++ b/crates/native/src/install.rs
@@ -272,6 +272,19 @@ fn selected_folder(folder: &str, writable: bool) -> Result<PathBuf, String> {
     Ok(path)
 }
 
+/// Whether this user can create files in `dir`, found by creating and removing an empty file there.
+/// The file gets a random name and is opened with `create_new` (`O_CREAT | O_EXCL`), which never
+/// opens an existing file or follows a symlink: in a folder other users can write to, like
+/// `/Applications`, nothing they put there is written through or truncated (#778).
+#[cfg(any(target_os = "macos", test))]
+fn can_create_files_in(dir: &Path) -> bool {
+    let Ok(probe) = tempfile::Builder::new().prefix(".artcraft-launcher-write-test-").tempfile_in(dir) else { return false };
+    if let Err(e) = probe.close() {
+        log::warn!("couldn't remove the write check in {} ({e})", dir.display());
+    }
+    true
+}
+
 #[cfg(any(windows, test))]
 fn msi_folder_argument(path: &Path) -> Result<String, String> {
     let value = path.to_str().ok_or("unusual characters in a path")?;
@@ -1235,9 +1248,7 @@ pub mod macos {
             return Ok(dir.to_path_buf());
         }
         let system = PathBuf::from("/Applications");
-        let probe = system.join(".artcraft-launcher-write-test");
-        if std::fs::write(&probe, b"").is_ok() {
-            let _ = std::fs::remove_file(&probe);
+        if super::can_create_files_in(&system) {
             return Ok(system);
         }
         let user = dirs::home_dir().ok_or("no home folder")?.join("Applications");
@@ -1747,6 +1758,54 @@ pub mod windows {
 mod tests {
     use super::*;
 
+    /// Every entry left in `dir`, by name.
+    fn entries(dir: &Path) -> Vec<std::ffi::OsString> {
+        std::fs::read_dir(dir).unwrap().map(|e| e.unwrap().file_name()).collect()
+    }
+
+    #[test]
+    fn write_probe_reports_a_writable_folder_and_leaves_nothing_behind() {
+        let dir = tempfile::tempdir().unwrap();
+        assert!(can_create_files_in(dir.path()));
+        assert!(entries(dir.path()).is_empty());
+        assert!(!can_create_files_in(&dir.path().join("missing")));
+    }
+
+    #[cfg(unix)]
+    #[test]
+    fn write_probe_reports_a_read_only_folder_as_not_writable() {
+        use std::os::unix::fs::PermissionsExt;
+        let dir = tempfile::tempdir().unwrap();
+        let blocked = dir.path().join("blocked");
+        std::fs::create_dir(&blocked).unwrap();
+        std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o555)).unwrap();
+        // Root ignores the mode bits, so there is nothing to check when the tests run as root.
+        let privileged = std::fs::File::create(blocked.join("root-check")).is_ok();
+        if !privileged {
+            assert!(!can_create_files_in(&blocked));
+            assert!(entries(&blocked).is_empty());
+        }
+        std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o755)).unwrap();
+    }
+
+    /// Issue #778: in a folder other users can write to (`/Applications` is writable by every
+    /// admin), a symlink planted at the probe's name must not let the probe empty its target.
+    #[cfg(unix)]
+    #[test]
+    fn write_probe_leaves_a_planted_symlink_and_its_target_untouched() {
+        let dir = tempfile::tempdir().unwrap();
+        let elsewhere = tempfile::tempdir().unwrap();
+        let victim = elsewhere.path().join("victim.txt");
+        std::fs::write(&victim, b"keep this file").unwrap();
+        let planted = dir.path().join(".artcraft-launcher-write-test");
+        std::os::unix::fs::symlink(&victim, &planted).unwrap();
+
+        assert!(can_create_files_in(dir.path()));
+        assert_eq!(std::fs::read(&victim).unwrap(), b"keep this file");
+        assert_eq!(std::fs::read_link(&planted).unwrap(), victim);
+        assert_eq!(entries(dir.path()), vec![planted.file_name().unwrap().to_owned()]);
+    }
+
     #[cfg(unix)]
     #[test]
     fn msi_install_uses_the_package_target_instead_of_a_symlinked_cache() {
