Skip to main content

Privacy and Stored Data

What Daintree keeps and what leaves your machine: where data is stored on each platform, what telemetry does and does not send and how to turn it off, and how macOS folder access permissions work.

Reviewed

Data storage

Owner-only on POSIX

Persisted runtime data is written owner-only on macOS and Linux: files at 0600 and directories at 0700, so nothing durable is left world-readable at the umask default on a shared machine. This is best-effort (a failure to tighten permissions does not fail the write) and it is a no-op on Windows, which uses a different model.

Forge token

Your GitHub personal access token is stored in Daintree's electron-store config file as plain text. That is the same security model as ~/.gitconfig or a .env file, not OS keychain encryption. The file itself is owner-only per the section above, and the token is reachable only from the main process: it never goes to the renderer.

Plugin settings declared as secret are the exception: those go through the OS keychain where one is available, falling back to owner-only plaintext where it is not, with the tier disclosed per field in the plugin's settings UI.

Project environment variables

Sensitive project environment values (the ones whose names contain KEY, SECRET, TOKEN or PASSWORD) are stored in electron-store, separate from the plaintext project settings file. This is file-level isolation, equivalent to a .env file. Standard variables stay in the project's own settings.

Clipboard

Clipboard writes are capped before any native call: 8 MiB of text and 20 MiB for an image. Binary payloads skip the IPC byte-budget gate, so these handler-level caps are the actual backstop against a runaway renderer exhausting the main-process heap. Clipboard image files are written to a temp directory bounded at 20 files and a 24-hour age.

Image decoding happens in the main process out of necessity rather than preference: a renderer-side binary clipboard write crashes on Linux. Plugin clipboard reads are text-only by design: there is no image, HTML or file read for plugins, because the read side is where richer payload types would let a plugin pull out more than it declared.

Local data and cleanup

Everything else (settings, recipes, project state, session history) is stored locally in Daintree's app data directory. Two maintenance behaviors are worth knowing:

  • Database maintenance never overwrites a good backup with a corrupt one. The backup is only replaced once the new copy verifies, so a corrupted primary cannot take the recovery path down with it. See Crash recovery.
  • V8 heap snapshots are pruned automatically. V8 is configured to dump a heap snapshot when a process approaches its memory limit, which is invaluable diagnostically and about 55 MB a time. A count-based prune keeps the ten newest and deletes the rest, strictly filtered by extension so ordinary logs and crash reports are never touched.

No data leaves your machine except forge API traffic when you have configured a provider and only with your token, version checks against the update server, and crash reports if you have turned telemetry on.

Telemetry and privacy

Telemetry is off by default and stays off until you explicitly pick a level.

Note
No telemetry data leaves your machine unless you turn it on in Settings > Privacy & Data. The default is off, and you can change your choice at any time.
LevelWhat is sent
Off (default)Nothing. No crash reports, no analytics, no requests to the telemetry service at all.
Errors onlyCrash reports and error stack traces. No usage analytics.
Full usageCrash reports plus a small set of anonymous usage events.

A change takes effect on the next app restart. Alongside the level there is a log retention setting (7, 30 or 90 days, or off to keep everything) which governs how long local log files survive. It defaults to 30 days and is unrelated to telemetry; it applies whether or not anything is being sent anywhere.

What is never collected

Whatever your level, Daintree never collects source code or file contents, agent prompts or outputs or conversation history, API keys or credentials, or personally identifiable information.

No agent provider probes

Earlier versions pinged Anthropic, OpenAI and Google on a timer and on every window focus to show provider reachability, regardless of which agents you actually used. Those probes were removed in v0.31. Daintree no longer makes periodic outbound requests to model providers, and an installation that never launches an agent makes no provider network traffic at all.

Path and PII scrubbing

Before any report leaves the process, a scrubbing pass runs over paths and then over secret shapes. Both passes are idempotent. The path scrubber replaces the account segment with a placeholder and covers more forms than the obvious ones:

  • macOS and Linux home paths, including the case where the path ends at the username with no trailing segment.
  • Windows user paths on any drive letter, not just C:, in backslash, JSON-doubled-backslash and forward-slash forms.
  • WSL UNC paths: both \\wsl$\<distro>\home\<user> and \\wsl.localhost\.... The distro name is kept as useful signal; only the username is redacted.
  • Git remotes, where the organization and repository segments name private projects. The host is kept; the path is redacted, including nested group hierarchies.
  • Tilde-relative paths, temp directories and macOS per-session folders, which carry usernames and session tokens.

Crash report titles and Windows argument paths were both closed as PII leaks in v0.22: a title is a surface that is easy to forget, because it is assembled separately from the body it summarizes.

Crash report environment suppression

Node's diagnostic report includes a top-level environmentVariables field by default. Even after path stripping and secret scrubbing, that field is a known PII vector: it carries arbitrary user-set variables that no pattern set will catch in full.

Daintree handles it two ways. The utility processes it forks are launched with --report-exclude-env, so the field never reaches the payload from those processes at all. The main Electron process cannot take that flag, since its execArgv is owned by Electron's bootstrap, so the diagnostics collector rebuilds the report from a field allowlist instead, dropping the environment at construction time. Different mechanism, same outcome.

During first launch, before you make a telemetry choice, up to 100 usage events are buffered in memory and sent nowhere. Choosing Full usage flushes the buffer; choosing Errors only or Off discards it permanently.

The first-run flow offers a binary toggle: crash reports on, meaning Errors only, or off. Full usage is only selectable in Settings afterwards, so in practice the pre-consent buffer is always discarded.

To change your level later, open Settings > Privacy & Data.

macOS folder access

This section is macOS only. The first time a CLI agent running inside Daintree reads or writes files in certain protected folders, macOS shows a dialog along the lines of "Daintree" would like to access files in your Documents folder. The request came from your agent, not from Daintree itself, but macOS attributes it to the app that launched the agent. Granting access lets through exactly what the agent was already trying to do.

Which folders trigger a prompt

macOS gates a specific set of user folders and volumes behind Files and Folders permissions: Desktop, Documents and Downloads; removable volumes; network volumes; and iCloud Drive plus third-party cloud storage served through the File Provider API.

A project stored outside these locations will not trigger prompts on its own, so day-to-day work inside a worktree stays quiet. You normally see the dialog only when an agent reaches outside the project. The exception is a project that lives in a protected location: a repository cloned into ~/Documents, ~/Desktop or iCloud Drive prompts during ordinary in-project work. Cloud-synced folders cause git and terminal problems on top of the prompts; see the cloud-synced folder warning.

Why Daintree's name appears

Daintree runs CLI agents inside its own process tree: the main Electron process, a sandboxed PTY host, node-pty, your shell, and finally the agent. macOS traces a file access request back to the root signed app bundle in that chain, and every child inherits that identity for permission purposes. That is standard behavior for any app that spawns sub-processes, not a Daintree design choice, and an Electron app has no way to re-attribute the request to the child.

Granting and revoking access

The simplest path is to click Allow when the dialog appears: that grants per-folder access on demand, which keeps the surface as tight as it can be, and macOS remembers the choice.

To review or revoke later, open System Settings > Privacy & Security > Files and Folders and toggle individual folders. You can also reset from the command line using Daintree's bundle identifier:

# Reset a single folder (Documents shown here)
tccutil reset SystemPolicyDocumentsFolder org.daintree.app

# Or other protected folders
tccutil reset SystemPolicyDesktopFolder org.daintree.app
tccutil reset SystemPolicyDownloadsFolder org.daintree.app

# Reset every TCC privacy grant for Daintree at once. This also clears
# microphone and camera approvals, so Voice Input will re-prompt.
tccutil reset All org.daintree.app

After a reset, the next agent access to that folder triggers the dialog again. Prefer the per-service commands unless you want to wipe every grant.

Tip
If your agents routinely work across many protected folders, granting Daintree Full Disk Access replaces every per-folder prompt with one grant. It is broader than per-folder permissions, so enable it only if that trade-off suits how you work.