AgentDock coordinates local provider applications. It does not make an untrusted provider process safe and is not an operating-system sandbox.
Security-sensitive operations validate:
- expected provider bundle identifiers and publisher signing teams;
- code signatures and executable paths;
- exact managed profile roots and ownership markers;
- process ancestry, PIDs, and profile arguments;
- path containment and symlink rejection;
- bounded file counts, bytes, subprocess output, and shared session readers.
If those checks fail, managed launch, focus, close, restore, delete, or local activity reads fail closed.
These boundaries are implemented in DesktopAppRegistry.swift, DesktopInstanceController.swift, ProfileStore.swift, and LocalChatSession.swift.
Managed profiles retain access to normal user resources, including:
- files and project directories;
- shell environment and developer tools;
- network;
- Keychain;
- Git and SSH configuration;
- macOS permissions and provider updater state.
Use separate macOS accounts or stronger operating-system isolation when those resources must not be shared.
AgentDock stores profile metadata under
~/Library/Application Support/AgentDock/. New shortcuts live under
~/Applications/AgentDock/.
Older versions may have left session indexes with bounded titles, previews, timestamps, provider metadata, and relative source paths. They contain no full transcript bodies, tool output, or absolute working directories. The app no longer creates browser indexes; explicit profile removal or deletion cleans up that profile's legacy indexes. Claude activity summaries still scan validated supported local session sources and cache summaries in memory. AgentDock has no chat browser or transcript-copying action; removing that UI does not remove activity-source reads.
AgentDock does not provide cloud synchronization, scrape browser cookies, or read ordinary web-chat caches.
Custom Codex usage refresh is a separate provider egress boundary. AgentDock
reads only the active provider id and its matching provider definition from a
bounded, regular, non-symlink config.toml. Custom usage requests permit HTTPS
or loopback-only HTTP, reject URL credentials and unsafe schemes, and allow
redirects only within the same safe origin. Configured header values,
environment-backed values, and bearer tokens exist only on the request; they
are not persisted, logged, surfaced in raw errors, or included in analytics.
Responses are time- and size-bounded. A failed custom-provider request fails
closed instead of exposing the profile's unrelated OpenAI account quota.
Provider launches discard inherited app-profile, app-server routing, and
Electron/Node runtime overrides before supplying the selected profile's paths.
Managed Codex launches pin both CODEX_HOME and
CODEX_ELECTRON_USER_DATA_PATH, as well as the matching --user-data-dir.
The Electron variable also activates the installed app's preservation of
CODEX_HOME across its login-shell environment reload. Stock launches clear
managed profile selectors. Managed Claude launches also pair their verified
CLAUDE_USER_DATA_DIR override with a matching --user-data-dir argument,
covering Chromium session storage from process startup.
The native OpenAI quota reader additionally removes inherited account, API credential, and endpoint overrides so they cannot redirect the selected account's usage request. Custom-provider usage requests retain the explicit environment-backed authentication configured for that provider.
Ordinary shell variables and shared shell startup files remain host resources. The provider can reload that shell environment after startup; launch-time sanitization is not an operating-system enforcement boundary.
Provider namespace directories cannot be registered as individual profiles or
replayed as profile deletions. Persisted shortcuts must be distinct .app
paths whose normalized name matches the owning profile slug. Recovery journals
cannot claim another profile's directory or shortcut.
Process discovery rejects ambiguous profile arguments and bundle-prefix
lookalikes. Claude helper evidence also requires a publisher signature and a
kernel-resolved executable contained by the selected app bundle before
lifecycle operations revalidate stable process identity. Profile
locks resolve path aliases to one physical root and reject symlinked,
nonregular, multiply linked, or foreign-owned lock files.
Built-in Codex launches set
CODEX_CLI_PATH to the validated Codex app's bundled
Contents/Resources/codex executable. Named config-profile launches point it
to AgentDock's signed proxy, which validates the profile-local bounded regular
<name>.config.toml, converts its assignments to --config arguments accepted
by the desktop app-server, and then executes that same bundled CLI. Managed
named-profile launches set the managed profile's exact CODEX_HOME; official
named-profile launches set ~/.codex; built-in stock launches remove
CODEX_HOME. User-selected executables cannot replace the bundled CLI through
AgentDock's parent environment. Settings that would place bearer tokens, API
keys, or direct HTTP headers in process arguments are rejected; custom
providers should reference credentials through environment-backed settings.
Analytics are a separate explicit-opt-in, immediately revocable boundary implemented in
ProductAnalytics.swift.
Only closed typed events can be batched to the configured regional PostHog
endpoint. Events never carry source content or local identifiers; GeoIP and
person-profile processing are disabled, and queues exist only in bounded
memory. Explicit choices persist across upgrades; opt-out clears the queue and
random identifier. The public project
token is release configuration, not a credential; no private PostHog key
belongs in the app. See the complete catalog.
Local Claude usage summaries do not require credentials. Live subscription limits are an explicit provider-access boundary: AgentDock reads Claude Code or Claude Desktop OAuth access tokens and the active Desktop organization from provider-owned Keychain and local state, then calls Anthropic's OAuth profile and usage endpoints. Tokens are held only for the request; AgentDock never copies them into its own storage, refreshes or rotates them, includes them in analytics, or logs them. The Desktop decryption key is retained only in memory for the usage client's lifetime and reused across official and managed profiles. Each profile's tokens and account identity are still read afresh from its files. Background reads forbid Keychain interaction, including legacy login-Keychain prompts. A manual refresh can request access; choosing Always Allow lets macOS retain that grant for subsequent launches of the signed app. Managed profiles resolve credentials only from their own Desktop user-data roots. The official Claude usage row also uses Desktop credentials exclusively, so a separate Claude Code login cannot replace the displayed account. Desktop usage requires a valid account and organization identity, and responses are verified against that identity before display. An explicit identity mismatch clears the corresponding cached quota instead of showing stale data. Active-organization cookie database reads enforce the same root containment and symlink checks as other databases.
Do not commit:
- credentials, tokens, certificates, provisioning profiles, or environment files;
- provider profiles, chats, transcript exports, logs, databases, crash reports, or generated indexes;
- screenshots containing real account, project, device, or session data;
- build products or notarization material.
The release workflow uses ephemeral credentials, removes temporary keychains and key files, rejects filesystem metadata sidecars and build paths, and publishes checksums for the notarized artifacts.
Sparkle updates require HTTPS, a Developer ID signature, notarization, and an
Ed25519 signature. The public verification key is embedded in AgentDock. The
private key exists only as the environment-protected
SPARKLE_PRIVATE_ED_KEY GitHub Actions secret and is streamed to Sparkle's
tool over standard input. Release assets are published before the signed
appcast, so a client cannot discover an enclosure that is not yet available.
Follow the security policy. Use a private GitHub security advisory instead of a public issue when a report could expose a vulnerability, credential, private transcript, or local path.
Publish only synthetic account/profile examples and anonymous measurement counts. Never include local hardware or installed-version inventories, home paths, provider account details, real chats, logs, credentials, or private profile names in commits, PR descriptions, comments, or their edit history. Use a public GitHub noreply address for commit author and committer identities.
script/audit_privacy.sh scans every locally reachable Git ref for secrets and
checks new commit identities and the measurement receipt schema. Run
python3 script/audit_publication_metadata.py --all-history to audit identities
throughout reachable history. Historical violations need coordinated cleanup;
a clean current tree does not remove earlier commits or PR revisions. Review PR
descriptions and edit history separately from the source scan.