Skip to main content

Security & Privacy

How Canopy isolates processes, hardens IPC, protects git operations, filters environment variables, and manages your data.

Updated
Reviewed

Process Sandboxing

Canopy enables Electron's sandbox mode globally via app.enableSandbox(). Every renderer process runs in a restricted environment with no direct access to Node.js APIs or the file system. This applies to all current and future windows without requiring per-window opt-in.

IPC Isolation

All communication between the UI and the main process goes through a preload script using Electron's contextBridge API. The renderer never touches Node.js directly. It can only call explicitly exposed functions.

Sender Validation

Every IPC message is validated to confirm it originates from Canopy's own window. A global patch runs before any handlers are registered, wrapping all three IPC entry points:

  • ipcMain.handle and ipcMain.handleOnce channels return a wrapped error to untrusted origins
  • ipcMain.on channels silently drop messages from untrusted origins

In production, the trusted origin is app://canopy. This validation is automatic and covers every handler, including fire-and-forget channels. No per-handler changes are needed.

Error messages returned to the renderer are also scrubbed in production builds. Home directory paths, Windows user paths, and UNC paths are stripped before serialisation to prevent information leakage through error responses.

Payload Validation

All IPC payloads are validated with Zod schemas. Malformed or unexpected data gets rejected at the handler boundary.

IPC Rate Limiting

Canopy applies category-based rate limiting to IPC channels that trigger expensive operations. This prevents runaway agent loops or automation from overwhelming the system with rapid file, git, or terminal requests.

Two enforcement strategies are used depending on the operation type:

  • Fail-fast — throws immediately when the rate limit is exceeded. Used for read and delete operations where retrying is straightforward.
  • Queue-based — holds requests in a queue (up to 50 deep) and releases them as the rate window expires. Used for create operations like terminal spawns and worktree creation, so bursts wait rather than fail.
Tip
Terminal and worktree creation use the queue-based strategy. If you launch several terminals at once, the requests queue up and execute in order rather than failing.
CategoryLimitWindowStrategy
File operations5 calls10 sFail-fast
Git operations10 calls10 sFail-fast
Terminal spawn10 calls30 sQueue
Worktree creation20 calls30 sQueue

Session restore operations are exempt from rate-limit quotas, so restoring your previous workspace doesn't consume slots. This exemption applies only to the rate limiter, not to any other security controls.

Content Security Policy

Canopy enforces restrictive Content Security Policy headers across multiple surfaces to limit what scripts, styles, and resources can load in each context.

Response Headers

Every response served through the app:// protocol includes these security headers:

  • Cross-Origin-Opener-Policy: same-origin
  • Cross-Origin-Embedder-Policy: credentialless
  • X-Content-Type-Options: nosniff
  • Cross-Origin-Resource-Policy: same-origin

These headers prevent cross-origin data leaks and MIME type confusion attacks on every asset the app serves.

Webview CSP

Browser panel and dev preview sessions get a per-session CSP applied via session.webRequest.onHeadersReceived. The policy restricts scripts, connections, and media to localhost origins (localhost:*, 127.0.0.1:*, [::1]:*) and blocks object embeds and base URI manipulation. The Portal sidebar is excluded from this localhost restriction because it loads external AI services (Claude, ChatGPT, Gemini). Portal isolation relies on partition separation and permission lockdown instead.

Path Traversal Protection

Both the app:// and canopy-file:// protocol handlers enforce path traversal protection. Requests are checked for null bytes, relative path segments, and backslash injection. Resolved paths must fall within the expected root directory. Non-compliant requests return a 403 Forbidden response.

Embedded Browser Isolation

The portal and dev preview panels use isolated embedded web surfaces with partition isolation. These embedded browsers:

  • Run with nodeIntegration disabled, contextIsolation enabled, and sandbox enforced
  • Have their own storage partitions (cookies, localStorage are isolated)
  • Cannot access Canopy's internal APIs
  • Have navigateOnDragDrop disabled to prevent drag-and-drop navigation hijacking
  • Disable the Blink Auxclick feature to close the middle-click navigation bypass
  • Have preload scripts stripped so they cannot inherit the main window's bridge

Navigation is locked down through will-navigate and will-redirect event handlers. Dev preview sessions only allow localhost URLs. Browser panel sessions block unsafe URLs. Any window.open() call from a webview is denied and, if the URL is safe, routed to the OS browser instead.

Git RCE Protection

Malicious repositories can embed git config directives that execute arbitrary code when git runs. This is a real supply-chain attack vector: a cloned repo can set core.fsmonitor, core.pager, or protocol.ext.allow to run commands on your machine.

Canopy neutralises this by routing every git call through a hardened wrapper that forces nine -c config overrides. These overrides take precedence over any values set in the repository's .git/config:

OverrideThreat neutralised
core.fsmonitor=falsePrevents arbitrary code execution via fsmonitor hook
core.hooksPath=Prevents malicious hooks from running
core.pager=catPrevents pager injection
core.askpass=Blocks credential hijacking via askpass
credential.helper=Blocks credential store hijacking
core.sshCommand=Blocks SSH command injection
core.gitProxy=Blocks proxy injection
protocol.ext.allow=neverPrevents RCE via ext:: protocol URLs
core.untrackedCache=falseDisables untracked cache (can trigger hooks)

These overrides are per-invocation -c flags applied only to git processes Canopy spawns internally. They do not modify your system git configuration or affect git commands you run outside Canopy.

Working directories are also validated before any git call runs. Only absolute paths are accepted, preventing relative path injection from the renderer. A 30-second timeout prevents any git operation from hanging indefinitely.

Environment Variable Filtering

When Canopy spawns a terminal or agent process, it filters the inherited environment to prevent credential leakage. A rogue process in the terminal could otherwise inherit your shell's API keys, database passwords, and cloud credentials.

Blocklist

The filter operates at two levels. First, an exact blocklist strips 35 known credential variables including DATABASE_URL, AWS_SECRET_ACCESS_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, GITHUB_TOKEN, and STRIPE_SECRET_KEY.

Second, a regex pattern catches any variable whose name contains SECRET, PASSWORD, TOKEN, CREDENTIAL, PRIVATE_KEY, API_KEY, ACCESS_KEY, SIGNING_KEY, or ENCRYPTION_KEY as a word segment. The matching is boundary-aware: SECRETARIAT would not match, but MY_SECRET_VALUE would.

CANOPY_* Anti-Spoofing

All inherited CANOPY_* environment variables are stripped before every spawn. Canopy then injects fresh, known-safe values for its own metadata variables (CANOPY_PANE_ID, CANOPY_CWD, CANOPY_PROJECT_ID, CANOPY_WORKTREE_ID). This prevents a project config or external tool from spoofing Canopy's internal environment.

Note
This filtering applies to agent and terminal spawns. It does not affect the project environment variables you define in Settings > Project. Those are injected at spawn time after filtering, so your intentional keys reach the agent.

Permission Lockdown

Electron's permission system lets web content request access to browser APIs like the clipboard, camera, and microphone. Canopy enforces per-session allowlists so each context only gets the permissions it actually needs.

SessionAllowed permissions
App renderer (default)Clipboard read, clipboard write, media (camera, microphone)
persist:canopy-appClipboard read, clipboard write, media (camera, microphone)
persist:portalClipboard write only
persist:browserNone
persist:dev-preview-*None

Any permission not in a session's allowlist is silently denied. A session-created event handler catches dynamically created sessions (like new dev preview partitions) to apply the same restrictions automatically.

macOS Folder Access

This section applies to macOS only. The first time a CLI agent running inside Canopy tries to read or write files in certain protected folders, macOS shows a dialog along the lines of "Canopy" would like to access files in your Documents folder. The request came from your agent, not from Canopy itself, but macOS attributes it to the app that launched the agent. Granting access is safe and enables 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 (introduced in macOS Catalina and unchanged through current releases). You will typically see a prompt the first time an agent touches any of these:

  • Desktop, Documents, and Downloads
  • Removable volumes (USB drives, SD cards)
  • Network volumes (mounted SMB, AFP, or NFS shares)
  • iCloud Drive and third-party cloud storage served through the File Provider API (Dropbox, OneDrive, Google Drive)

A project working directory stored outside these locations will not trigger prompts on its own, so day-to-day agent work inside a worktree stays quiet. You will normally only see the dialog when an agent reaches outside the project, for example to read a reference file in ~/Documents or scan an attached drive. The exception is when the project itself lives inside a protected location — a repo cloned into ~/Documents, ~/Desktop, or iCloud Drive will trigger prompts even during ordinary in-project work. Keeping repos in cloud-synced folders also causes git and terminal issues on top of the permission prompts; see Cloud-Synced Folder Warning in troubleshooting for the full picture.

Why Canopy's name appears

Canopy runs CLI agents inside its own process tree. The spawn chain is Canopy's main Electron process, a sandboxed PTY host utility process, node-pty, your shell, and finally the agent (claude, gemini, codex, and so on). macOS tracks file access requests back to the root signed app bundle in that chain, which is com.canopyide.app. Every child process inherits Canopy's identity for permission purposes.

That is why the dialog names Canopy even when Claude Code or Gemini CLI is the one asking for the file. It is standard macOS behaviour for any app that spawns sub-processes, not a Canopy design choice, and there is no way for an Electron app to re-attribute the request to the child process.

Note
When Claude Code (or any other agent) reads a file in ~/Documents, macOS shows the dialog under Canopy's name because Canopy is the signed app bundle that launched the agent. Clicking Allow grants access for exactly what the agent requested, nothing more. Canopy itself does not reach into these folders on its own.

Granting and revoking access

The simplest approach is to click Allow when the dialog appears. This grants per-folder access on demand, which keeps the permission surface as tight as possible. macOS remembers the choice, so you will not be asked again for that folder.

To review or revoke access later, open System Settings > Privacy & Security > Files and Folders, find Canopy in the list, and toggle individual folders on or off. If you accidentally clicked Don't Allow and want the dialog to reappear, the same panel lets you re-enable the folder. You can also reset permissions from the command line using Canopy's bundle identifier:

# Reset a single folder (Documents shown here)
tccutil reset SystemPolicyDocumentsFolder com.canopyide.app

# Or other protected folders
tccutil reset SystemPolicyDesktopFolder com.canopyide.app
tccutil reset SystemPolicyDownloadsFolder com.canopyide.app

# Reset every TCC privacy grant for Canopy at once.
# This also clears microphone, camera, and any other approvals,
# so Voice Input and similar features will re-prompt next time.
tccutil reset All com.canopyide.app

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

Tip
If your agents routinely work across many protected folders, granting Canopy Full Disk Access is the lowest-friction option. Open System Settings > Privacy & Security > Full Disk Access and add Canopy to the list. This replaces every per-folder prompt with a single one-time grant. It is broader than per-folder permissions, so only enable it if that trade-off suits your workflow.

Data Storage

GitHub Token

Your GitHub personal access token is stored with Electron's safeStorage API, which encrypts it using your OS keychain:

  • macOS — Keychain
  • Windows — DPAPI
  • Linux — libsecret / GNOME Keyring

The token is only accessible to the main process and is never sent to the renderer.

Project Environment Variables

Sensitive environment variable values (those whose names contain KEY, SECRET, TOKEN, or PASSWORD) are stored in electron-store, separate from the plaintext settings.json. This is file-level isolation, equivalent in security to a .env file, not OS keychain encryption. Standard env vars are stored in plain text in the project's settings.json.

Local Data

Everything else (settings, recipes, project state) is stored locally in Canopy's app data directory via electron-store. No data leaves your machine except:

  • GitHub API — when GitHub integration is configured (only with your token)
  • Update server — version checks to updates.canopyide.com
  • Sentry — crash reports and optional usage analytics, only if you enable telemetry

Telemetry & Privacy

Canopy includes an opt-in telemetry system for crash reporting and anonymous usage analytics. Telemetry is off by default and stays off until you explicitly choose a level.

Note
No telemetry data leaves your machine unless you enable it in Settings > Privacy & Data. The default is off, and you can change your choice at any time.

Telemetry Levels

You pick one of three levels. Each one determines what Canopy sends (or doesn't send) to Sentry, the crash reporting service.

LevelWhat is sent
Off (default)Nothing. No crash reports, no analytics, no network requests to Sentry.
Errors OnlyCrash reports and error stack traces. No usage analytics.
Full UsageCrash reports plus anonymous usage analytics (10% sample rate).

Changes to your telemetry level take effect on the next app restart.

What Is Never Collected

Regardless of your telemetry level, Canopy never collects:

  • Source code or file contents
  • Agent prompts, outputs, or conversation history
  • API keys or credentials
  • File names or directory structures from your projects (note: sanitised partial paths may appear in crash report stack traces if telemetry is enabled — the home directory prefix is stripped, but not every path segment)
  • Personally identifiable information

Path Sanitisation

Before any crash report reaches Sentry, a beforeSend hook strips your home directory from all stack traces, error messages, and file paths. Your system username is replaced with ~ on macOS/Linux or USER on Windows. This runs automatically and cannot be bypassed.

Pre-Consent Buffering

During first launch, before you make a telemetry choice in the onboarding flow, up to 100 usage events are buffered in memory. None of these are sent anywhere.

  • If you choose Full Usage, the buffer is flushed to Sentry.
  • If you choose Errors Only or Off, the buffer is discarded. Those events are gone permanently.

Full Usage is only selectable in Settings after onboarding. The first-run onboarding flow only offers a binary toggle (crash reports on → Errors Only, or off). So in practice, the pre-consent buffer is always discarded after completing onboarding, unless you later switch to Full Usage via Settings on a fresh install.

First-Run Consent

When you first launch Canopy, the onboarding flow presents a "Help improve Canopy" toggle. Turning this on sets your level to Errors Only. Leaving it off (or clicking Skip) sets telemetry to Off. You can always change this later in Settings > Privacy & Data.

Changing Your Level

Open Settings > Privacy & Data > Telemetry (Cmd+,) and select your preferred level. The change applies after restarting Canopy. For a full walkthrough of the settings tab, see Settings > Privacy & Data.

MCP Server

Canopy includes an opt-in local MCP server that lets external tools call Canopy actions. It is disabled by default and binds to 127.0.0.1 only, so it is never reachable from the network. When enabled without authentication, any process on your machine can connect. You can restrict access by generating a bearer token in Settings > MCP Server > Authentication.

Single Instance

Canopy enforces a single-instance lock. Only one copy of the app can run at a time — opening a second instance just focuses the existing window. This avoids conflicts with file locks and terminal processes.

Window Close Protection

Cmd+W closes the focused terminal panel, not the application window. This prevents you from accidentally quitting and losing running agent sessions.