collective

Browse / github.com/storytold/artcraft / patch

Remove tracking and harden desktop security settings

Remove tracking and harden desktop security settings Tracking removed: - Microsoft Clarity session recording (index.html loader + CSP) - Google Analytics gtag snippet; gtag helpers are now no-ops (gtagLogin previously sent the user's session token as GA user_id) - PostHog init and provider; feature-flag hook no longer phones home - storyteller.ai activity heartbeat thread is no longer started Hardening: - Stop logging full Grok/WorldLabs session cookies to artcraft_debug.log - Asset protocol scope narrowed from "**" (whole disk) to user content folders, with the credentials folder explicitly denied - Drop unpinned unpkg.com and tracker domains

Status
open
Score
0.00
Adopters
0 other operators
Author
GeekShopAdmin (imported from pull request #2497, open)
Base
7a2935cf9e3922e90e4ed5f3556b1cb107013148
Size
+87 −110 in 15 files
Review
pending
Shared
2026-10-28
Id
cafab25be4b2d1f677e37788

How it went

No other operator has reported using it yet.

Diff

raw · crates/desktop/artcraft/src/core/lifecycle/startup/tasks/spawn_storyteller_threads.rs crates/desktop/artcraft/src/core/state/data_dir/app_credentials_dir.rs crates/desktop/artcraft/src/core/state/data_dir/app_data_root.rs crates/desktop/artcraft/src/services/grok/windows/grok_login_window/grok_login_window_thread.rs crates/desktop/artcraft/src/services/worldlabs/windows/worldlabs_login_window/worldlabs_login_window_thread.rs crates/desktop/artcraft/tauri-dev-hot-reload.conf.json crates/desktop/artcraft/tauri-mac.conf.json crates/desktop/artcraft/tauri.conf.json frontend/apps/artcraft/app/index.html frontend/apps/artcraft/app/src/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx frontend/apps/artcraft/app/src/index.tsx frontend/apps/artcraft/app/src/root.tsx frontend/libs/analytics/google-analytics/src/lib/gtag-event.ts frontend/libs/analytics/google-analytics/src/lib/gtag-login.ts frontend/libs/analytics/google-analytics/src/lib/gtag-logout.ts

diff --git a/crates/desktop/artcraft/src/core/lifecycle/startup/tasks/spawn_storyteller_threads.rs b/crates/desktop/artcraft/src/core/lifecycle/startup/tasks/spawn_storyteller_threads.rs--- a/crates/desktop/artcraft/src/core/lifecycle/startup/tasks/spawn_storyteller_threads.rs+++ b/crates/desktop/artcraft/src/core/lifecycle/startup/tasks/spawn_storyteller_threads.rs@@ -3,7 +3,6 @@ use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo; use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; use crate::core::state::task_database::TaskDatabase; use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;-use crate::services::storyteller::threads::storyteller_activity_thread::storyteller_activity_thread; use crate::services::storyteller::threads::storyteller_task_polling_thread::storyteller_task_polling_thread::storyteller_task_polling_thread; use errors::AnyhowResult; use tauri::{AppHandle, Manager};@@ -24,12 +23,9 @@ pub fn spawn_storyteller_threads(     storyteller_creds_manager.clone(),   )); -  tauri::async_runtime::spawn(storyteller_activity_thread(-    app_env_configs.clone(),-    artcraft_platform_info.clone(),-    artcraft_usage_tracker.clone(),-    storyteller_creds_manager.clone(),-  ));-  +  // Usage-telemetry heartbeat (storyteller_activity_thread) intentionally not started:+  // it only reported session length, OS/app version and generation counts to storyteller.ai.+  let _ = (artcraft_platform_info, artcraft_usage_tracker);+   Ok(()) }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,18 @@ impl DataSubdir for AppCredentialsDir { }  impl AppCredentialsDir {+  /// Restrict the credentials directory to the current user (0700) on Unix.+  /// A 0700 directory blocks other local users from every file inside it.+  /// On Windows the directory inherits the user profile's owner-only ACL, so this is a no-op.+  pub fn restrict_permissions(&self) -> anyhow::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")   }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,7 @@ impl AppDataRoot {          let assets_dir = AppAssetsDir::get_or_create_in_root_dir(&dir)?;     let credentials_dir = AppCredentialsDir::get_or_create_in_root_dir(&dir)?;+    credentials_dir.restrict_permissions()?;     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)?;diff --git a/crates/desktop/artcraft/src/services/grok/windows/grok_login_window/grok_login_window_thread.rs b/crates/desktop/artcraft/src/services/grok/windows/grok_login_window/grok_login_window_thread.rs--- a/crates/desktop/artcraft/src/services/grok/windows/grok_login_window/grok_login_window_thread.rs+++ b/crates/desktop/artcraft/src/services/grok/windows/grok_login_window/grok_login_window_thread.rs@@ -137,7 +137,8 @@ async fn check_login_window(    info!("Heuristic count is ({}); we're likely done.", heuristic_count); -  info!("Current cookies (len {}): {:?}", cookie_store.len(), cookie_store.to_cookie_string());+  // Never log cookie values: they are full session credentials.+  info!("Captured {} cookies from login window.", cookie_store.len());    //let response = get_user_info(GetUserInfoRequest {   //  hostname: MidjourneyHostname::Standard,diff --git a/crates/desktop/artcraft/src/services/worldlabs/windows/worldlabs_login_window/worldlabs_login_window_thread.rs b/crates/desktop/artcraft/src/services/worldlabs/windows/worldlabs_login_window/worldlabs_login_window_thread.rs--- a/crates/desktop/artcraft/src/services/worldlabs/windows/worldlabs_login_window/worldlabs_login_window_thread.rs+++ b/crates/desktop/artcraft/src/services/worldlabs/windows/worldlabs_login_window/worldlabs_login_window_thread.rs@@ -98,7 +98,8 @@ async fn check_login_window(    let cookie_store = worldlabs_login_webview_extract_cookies(webview_window)?; -  info!("Current cookies (len {}): {:?}", cookie_store.len(), cookie_store.to_cookie_string());+  // Never log cookie values: they are full session credentials.+  info!("Captured {} cookies from login window.", cookie_store.len());    worldlabs_creds_manager.replace_cookie_store(cookie_store)?;   diff --git a/crates/desktop/artcraft/tauri-dev-hot-reload.conf.json b/crates/desktop/artcraft/tauri-dev-hot-reload.conf.json--- a/crates/desktop/artcraft/tauri-dev-hot-reload.conf.json+++ b/crates/desktop/artcraft/tauri-dev-hot-reload.conf.json@@ -7,12 +7,25 @@   },   "app": {     "withGlobalTauri": true,-    "windows": [-    ],+    "windows": [],     "security": {       "assetProtocol": {         "enable": true,-        "scope": ["**"]+        "scope": {+          "allow": [+            "$HOME/Artcraft/**",+            "$DESKTOP/**",+            "$DOCUMENT/**",+            "$DOWNLOAD/**",+            "$PICTURE/**",+            "$VIDEO/**",+            "$AUDIO/**"+          ],+          "deny": [+            "$HOME/Artcraft/credentials",+            "$HOME/Artcraft/credentials/**"+          ]+        }       },       "csp": {         "default-src": [@@ -23,8 +36,6 @@           "data:",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -36,13 +47,10 @@           "blob:",           "data:",           "http://ipc.localhost",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",           "https://asset.localhost",           "https://unpkg.com/detect-gpu@5.0.70/",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -67,12 +75,8 @@           "asset:",           "blob:",           "data:",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://*.unpkg.com",-          "https://clarity.ms",-          "https://unpkg.com",           "ipc://localhost",           "tauri:",           "tauri://localhost"diff --git a/crates/desktop/artcraft/tauri-mac.conf.json b/crates/desktop/artcraft/tauri-mac.conf.json--- a/crates/desktop/artcraft/tauri-mac.conf.json+++ b/crates/desktop/artcraft/tauri-mac.conf.json@@ -11,12 +11,25 @@   },   "app": {     "withGlobalTauri": true,-    "windows": [-    ],+    "windows": [],     "security": {       "assetProtocol": {         "enable": true,-        "scope": ["**"]+        "scope": {+          "allow": [+            "$HOME/Artcraft/**",+            "$DESKTOP/**",+            "$DOCUMENT/**",+            "$DOWNLOAD/**",+            "$PICTURE/**",+            "$VIDEO/**",+            "$AUDIO/**"+          ],+          "deny": [+            "$HOME/Artcraft/credentials",+            "$HOME/Artcraft/credentials/**"+          ]+        }       },       "csp": {         "default-src": [@@ -27,8 +40,6 @@           "data:",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -40,13 +51,10 @@           "blob:",           "data:",           "http://ipc.localhost",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",           "https://asset.localhost",           "https://unpkg.com/detect-gpu@5.0.70/",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -71,12 +79,8 @@           "asset:",           "blob:",           "data:",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://*.unpkg.com",-          "https://clarity.ms",-          "https://unpkg.com",           "ipc://localhost",           "tauri:",           "tauri://localhost"diff --git a/crates/desktop/artcraft/tauri.conf.json b/crates/desktop/artcraft/tauri.conf.json--- a/crates/desktop/artcraft/tauri.conf.json+++ b/crates/desktop/artcraft/tauri.conf.json@@ -15,7 +15,21 @@     "security": {       "assetProtocol": {         "enable": true,-        "scope": ["**"]+        "scope": {+          "allow": [+            "$HOME/Artcraft/**",+            "$DESKTOP/**",+            "$DOCUMENT/**",+            "$DOWNLOAD/**",+            "$PICTURE/**",+            "$VIDEO/**",+            "$AUDIO/**"+          ],+          "deny": [+            "$HOME/Artcraft/credentials",+            "$HOME/Artcraft/credentials/**"+          ]+        }       },       "csp": {         "default-src": [@@ -26,8 +40,6 @@           "data:",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -39,13 +51,10 @@           "blob:",           "data:",           "http://ipc.localhost",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",           "https://asset.localhost",           "https://unpkg.com/detect-gpu@5.0.70/",-          "https://www.google-analytics.com",-          "https://www.googletagmanager.com",           "ipc://localhost",           "tauri://localhost"         ],@@ -70,12 +79,8 @@           "asset:",           "blob:",           "data:",-          "https://*.clarity.ms",           "https://*.fakeyou.com",           "https://*.storyteller.ai",-          "https://*.unpkg.com",-          "https://clarity.ms",-          "https://unpkg.com",           "ipc://localhost",           "tauri:",           "tauri://localhost"diff --git a/frontend/apps/artcraft/app/index.html b/frontend/apps/artcraft/app/index.html--- a/frontend/apps/artcraft/app/index.html+++ b/frontend/apps/artcraft/app/index.html@@ -6,26 +6,7 @@   <meta charset="utf-8" />   <meta name="viewport" content="width=device-width, initial-scale=1" /> -  <!-- Google tag (gtag.js) -->-  <script async src="https://www.googletagmanager.com/gtag/js?id=G-Y4Y8KMLPQ7"></script>-  <script>-    window.dataLayer = window.dataLayer || [];-    function gtag() {dataLayer.push(arguments);}-    gtag('js', new Date());-    gtag('config', 'G-Y4Y8KMLPQ7');-  </script>--  <!---->-  <!-- <script crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js"></script> -->--  <!-- Microsoft Clarity -->-  <script type="text/javascript">-    (function (c, l, a, r, i, t, y) {-      c[a] = c[a] || function () {(c[a].q = c[a].q || []).push(arguments)};-      t = l.createElement(r); t.async = 1; t.src = "https://www.clarity.ms/tag/" + i;-      y = l.getElementsByTagName(r)[0]; y.parentNode.insertBefore(t, y);-    })(window, document, "clarity", "script", "sixp9uu263");-  </script>+  <!-- Google Analytics and Microsoft Clarity removed (privacy). -->    <base href="/" />   <link rel="icon" type="image/svg+xml" href="/artcraft-icon.svg" />diff --git a/frontend/apps/artcraft/app/src/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx b/frontend/apps/artcraft/app/src/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx--- a/frontend/apps/artcraft/app/src/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx+++ b/frontend/apps/artcraft/app/src/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx@@ -1,14 +1,15 @@-import { useFeatureFlagEnabled } from "posthog-js/react"; import { FeatureFlags } from "~/enums/FeatureFlags"; import environmentVariables from "~/Classes/EnvironmentVariables"; -export const usePosthogFeatureFlag = (flag: FeatureFlags): boolean => {-  const enabled = useFeatureFlagEnabled(flag) ?? false;+// PostHog removed (privacy). Remote feature flags are no longer fetched;+// every flag reads as "off" except in development builds, as before.+// eslint-disable-next-line @typescript-eslint/no-unused-vars+export const usePosthogFeatureFlag = (_flag: FeatureFlags): boolean => {   if (     environmentVariables.values.DEPLOY_CONTEXT &&     environmentVariables.values.DEPLOY_CONTEXT === "DEVELOPMENT"   ) {     return true;   }-  return enabled;+  return false; };diff --git a/frontend/apps/artcraft/app/src/index.tsx b/frontend/apps/artcraft/app/src/index.tsx--- a/frontend/apps/artcraft/app/src/index.tsx+++ b/frontend/apps/artcraft/app/src/index.tsx@@ -10,7 +10,6 @@ import "./styles/base.css"; import EnvironmentVariables from "~/Classes/EnvironmentVariables"; import { pageHeight, pageWidth, persistLogin } from "~/signals"; import { SyncStorytellerApiConfig } from "./api/SyncStorytellerApiConfig";-import { posthog } from "posthog-js"; import { SoundManager } from "@storyteller/soundboard"; import { useModelsStore } from "@storyteller/tauri-api"; @@ -34,21 +33,9 @@ const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {     persistLogin();   }); -  /// Initizations that depends on ENV vars ///-  function PostHogInit() {-    const apiKey = import.meta.env.VITE_POSTHOG_API_KEY;-    posthog.init(apiKey, {-      api_host: "https://us.i.posthog.com/",-      ui_host: "https://us.i.posthog.com/",-    });-  }-+  // PostHog analytics initialization removed (privacy).   useEffect(() => {     EnvironmentVariables.initialize(env);-    if (import.meta.env.DEV) {-      return;-    }-    PostHogInit();   }, [env]);    // Apply stored theme early on mountdiff --git a/frontend/apps/artcraft/app/src/root.tsx b/frontend/apps/artcraft/app/src/root.tsx--- a/frontend/apps/artcraft/app/src/root.tsx+++ b/frontend/apps/artcraft/app/src/root.tsx@@ -1,6 +1,4 @@ import { LinksFunction } from "@remix-run/deno";-import { posthog } from "posthog-js";-import { PostHogProvider } from "posthog-js/react"; import {   Links,   Meta,@@ -55,9 +53,7 @@ export default function App() {         <div className="topbar-spacer"           data-tauri-drag-region={true}         />-        <PostHogProvider client={posthog}>-          <Outlet />-        </PostHogProvider>+        <Outlet />         <ScrollRestoration />         <Scripts />       </body>diff --git a/frontend/libs/analytics/google-analytics/src/lib/gtag-event.ts b/frontend/libs/analytics/google-analytics/src/lib/gtag-event.ts--- a/frontend/libs/analytics/google-analytics/src/lib/gtag-event.ts+++ b/frontend/libs/analytics/google-analytics/src/lib/gtag-event.ts@@ -1,10 +1,6 @@ -// Send an event with name and "args". Args is a string-to-string map.-export const gtagEvent = function(eventName: string, args?: { [key: string]: string }) {-  console.debug("gtagEvent", eventName, args);-  if (!args) {-    (window as any).gtag('event', eventName);-  } else {-    (window as any).gtag('event', eventName, args);-  }+// Google Analytics disabled (privacy). Kept as a no-op so existing call sites compile.+// eslint-disable-next-line @typescript-eslint/no-unused-vars+export const gtagEvent = function(_eventName: string, _args?: { [key: string]: string }) {+  // no-op }diff --git a/frontend/libs/analytics/google-analytics/src/lib/gtag-login.ts b/frontend/libs/analytics/google-analytics/src/lib/gtag-login.ts--- a/frontend/libs/analytics/google-analytics/src/lib/gtag-login.ts+++ b/frontend/libs/analytics/google-analytics/src/lib/gtag-login.ts@@ -1,10 +1,7 @@-import { TAG_ID } from './tag-id.js'; -export const gtagLogin = function(userToken: string) {-  console.debug("gtagLogin", TAG_ID, userToken);--  (window as any).gtag(-    'config', TAG_ID, {-    'user_id': userToken-  });+// Google Analytics disabled (privacy). Kept as a no-op so existing call sites compile.+// Previously this sent the user's session token to Google Analytics as user_id.+// eslint-disable-next-line @typescript-eslint/no-unused-vars+export const gtagLogin = function(_userToken: string) {+  // no-op }diff --git a/frontend/libs/analytics/google-analytics/src/lib/gtag-logout.ts b/frontend/libs/analytics/google-analytics/src/lib/gtag-logout.ts--- a/frontend/libs/analytics/google-analytics/src/lib/gtag-logout.ts+++ b/frontend/libs/analytics/google-analytics/src/lib/gtag-logout.ts@@ -1,10 +1,5 @@-import { TAG_ID } from './tag-id.js'; +// Google Analytics disabled (privacy). Kept as a no-op so existing call sites compile. export const gtagLogout = function() {-  console.debug("gtagLogout", TAG_ID);--  (window as any).gtag(-    'config', TAG_ID, {-    'user_id': null-  });+  // no-op }