collective

Browse / github.com/storytold/artcraft / patch

artcraft: keep the credentials folder private to its owner

artcraft: keep the credentials folder private to its owner `~/Artcraft/credentials` holds the ArtCraft session and avt cookies, the Sora, WorldLabs and Grok cookies and bearer/refresh tokens, the fal API key and the Midjourney cookies, all as plain files. The folder was created with `create_dir` (0755 under the usual umask) and most files are written with `OpenOptions`/`fs::write` (0644). macOS home folders are 0755, so any other account on the same Mac could read them. Your Midjourney store already avoids this (`NamedTempFile`, 0600, in `midjourney_client` `credential_file.rs`). `AppDataRoot` now sets the folder to 0700 on Unix right after creating

Status
open
Score
0.00
Adopters
0 other operators
Author
Matoo125 (imported from pull request #2551, open)
Base
800217ed04d10fc12706c28e6e91ae2a6b286092
Size
+46 −0 in 2 files
Review
pending
Shared
2026-10-28
Id
43fd92354ec7d635a6b32996

How it went

No other operator has reported using it yet.

Diff

raw · crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs

diff --git a/crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs b/crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs--- a/crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs+++ b/crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs@@ -21,6 +21,19 @@ impl DataSubdir for AppCredentialsDir { }  impl AppCredentialsDir {+  /// Session cookies, bearer and refresh tokens and API keys are plain files in here, so on Unix+  /// the directory is readable by its owner only (0700). Created with the default 0755, any other+  /// account on the machine could read them (home folders are 0755 on macOS). Folders made by+  /// earlier versions are tightened too.+  pub fn restrict_to_owner(&self) -> std::io::Result<()> {+    #[cfg(unix)]+    {+      use std::os::unix::fs::PermissionsExt;+      std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(0o700))?;+    }+    Ok(())+  }+   pub fn get_grok_state_path(&self) -> PathBuf {     self.path.join("grok_state.json")   }@@ -82,3 +95,33 @@ impl AppCredentialsDir {     self.path.join("worldlabs_refresh.txt")   } }++#[cfg(all(test, unix))]+mod tests {+  use crate::core::state::data_dir::app_data_root::AppDataRoot;+  use crate::core::state::data_dir::trait_data_subdir::DataSubdir;+  use std::os::unix::fs::PermissionsExt;++  fn mode(path: &std::path::Path) -> u32 {+    std::fs::metadata(path).unwrap().permissions().mode() & 0o777+  }++  #[test]+  fn new_credentials_dir_is_private_to_its_owner() {+    let dir = tempfile::tempdir().unwrap();+    let root = AppDataRoot::create_existing(dir.path()).unwrap();+    assert_eq!(mode(root.credentials_dir().path()), 0o700);+  }++  #[test]+  fn existing_credentials_dir_is_made_private() {+    // Folders made by earlier versions are 0755 under the usual umask: other local accounts could+    // read the session cookies and API keys inside.+    let dir = tempfile::tempdir().unwrap();+    let credentials = dir.path().join("credentials");+    std::fs::create_dir(&credentials).unwrap();+    std::fs::set_permissions(&credentials, std::fs::Permissions::from_mode(0o755)).unwrap();+    let root = AppDataRoot::create_existing(dir.path()).unwrap();+    assert_eq!(mode(root.credentials_dir().path()), 0o700);+  }+}diff --git a/crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs b/crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs--- a/crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs+++ b/crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs@@ -65,6 +65,9 @@ impl AppDataRoot {          let assets_dir = AppAssetsDir::get_or_create_in_root_dir(&dir)?;     let credentials_dir = AppCredentialsDir::get_or_create_in_root_dir(&dir)?;+    if let Err(err) = credentials_dir.restrict_to_owner() {+      println!("Couldn't restrict {:?} to its owner: {}", credentials_dir.path(), err);+    }     let downloads_dir = AppDownloadsDir::get_or_create_in_root_dir(&dir)?;     let settings_dir = AppSettingsDir::get_or_create_in_root_dir(&dir)?;     let state_dir = AppStateDir::get_or_create_in_root_dir(&dir)?;