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
@@ -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(()) }@@ -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") }@@ -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)?;@@ -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,@@ -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)?; @@ -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"@@ -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"@@ -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"@@ -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" />@@ -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; };@@ -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 mount@@ -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>@@ -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 }@@ -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 }@@ -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 }