CCM is a single-user desktop app. It is not intended to be:
- Exposed to untrusted users on a shared machine
- Made network-reachable beyond
127.0.0.1 - Used to drive sites where you wouldn't want an AI making decisions
The MCP control surface (HTTP server in electron/browser-http-server.js) binds 127.0.0.1 only, requires a bearer token written to a mode 0600 file in ~/.claude/, and Host-header-allowlists localhost/127.0.0.1. The Chrome DevTools Protocol port (9221 + slot) is also localhost-only by design — but note that any process running as the same OS user can connect to it without auth. If you share a machine, treat CCM as you would any local dev tool: don't leave it running unattended.
If you find a security issue:
- Do not open a public GitHub issue.
- Open a private security advisory via GitHub's "Security" tab → "Report a vulnerability", or email the address listed in the repo's
package.jsonauthor.emailfield. - Include: affected file/line, reproduction steps, suggested fix (if you have one), and whether you'd like credit in the changelog.
I aim to respond within 7 days for any clear vulnerability. There is no bug bounty (this is a personal project), but I'm happy to credit reporters in the CHANGELOG.
- Bearer-token MCP auth:
crypto.randomBytes(24)per session, written0600to~/.claude/ccm-browser-endpoint.json. - URL-scheme allowlist (
_safeNavUrl): blocksjavascript:,data:,file:,vbscript:,ms-msdt:,mhtml:,view-source:from anychrome_*navigation tool. - Browseable-target filter (
_pageById+cdpRaw): refuses to address CCM's own renderer process, devtools, or extensions viatargetId. Blocks the privilege-escalation path where prompt-injected MCP results could chainTarget.getTargets+Runtime.evaluateto execute privileged JS. - Prototype-pollution block:
_assertSafePathrejects__proto__/prototype/constructorin dotted path keys for prefs/bookmarks setters. - Policy-name regex allowlist:
policySet/policyDeletereject anything outside^[A-Za-z][A-Za-z0-9_]{0,127}$;REG_EXPAND_SZ(env-var expansion persistence vector) explicitly blocked. - Companion MV3 extension permissions diet: no
<all_urls>, nodebugger, nowebRequest/cookies/scripting;host_permissionstightened to localhost. - Picker DoS resistance: overlay membership tracked via closure-scoped
Set, not a publicdata-*attribute pages can spam. - Observe ref stability:
data-ccm-ref="N"survives re-renders; cached refs from a priorchrome_observekeep pointing to the same element across mutations (refuses to be silently corrupted). - No
allow-same-originon code-preview iframes: JSX/HTML/React previews render insrcdocsandboxes withallow-scriptsONLY.allow-same-originon a srcdoc iframe makes it same-origin with the app → Claude-authored preview code could reachwindow.parent.electronAPI(fs/shell/terminal) = RCE. esm.sh module imports still work via wildcard CORS under the opaque origin. (External-URL preview iframes keepallow-same-origin— they carry the remote site's origin, not the app's.) - frameAttach is iframe-gated:
chrome_frame_attachrefuses any target that isn'ttype:'iframe'with a browseable URL — blocks attaching a CDP session to the CCM app renderer and running JS there. - cdpRaw escalation guards:
chrome_cdp_rawroutesPage.navigateURLs through the scheme allowlist and blocksTarget.attachToTarget/attachToBrowserTarget(the escalation primitive). - No plaintext secret fallback: GitHub PAT + Anthropic API key are stored ONLY via OS
safeStorage(DPAPI/Keychain/libsecret). If secure storage is unavailable, CCM refuses to persist them rather than writing cleartext. - App-window navigation guard: the privileged app window (which holds the
electronAPIbridge) is pinned to its own origin viawill-navigate/will-redirect; any attempt to navigate it elsewhere is blocked and http(s) links are handed to the real browser. Both app windows also runsandbox: true, andwebSecurityis force-enabled in packaged builds (theCCM_DEV_INSECUREescape hatch is dev-only). - Renderer markdown is escaped-first: the notes/preview markdown renderers HTML-escape input before applying transforms, so pasted/agent-written content can't inject markup (CSP already blocks script execution; this stops content spoofing + beacons too).
- Bearer tokens, OAuth state, API keys: stored under
~/.claude/(not in the repo) - Saved chat sessions:
sessions/*.json— gitignored - User memory:
memory/*.md— gitignored - Embedded browser cookies/sessions: stored under
%APPDATA%\claude-code-desktop\(out of repo) - Personal agent configs:
agents/crowbyte-ops.jsonand similar are gitignored by name
Anything in the patterns above is never committed by the build/dist pipeline.
- The CDP port (
9221 + slot) is not authenticated. Any process running as your OS user can drive Chromium directly. The bearer-auth HTTP server is the auth boundary for the MCP layer, not for CDP itself. - The Phase 18b "link to project" feature uses Windows junctions (or POSIX symlinks). If a Claude CLI session is open during migration, the rename step fails — close active sessions first.
- The companion MV3 extension is loaded with
allowFileAccess: true. It only fetches from127.0.0.1per its host_permissions, but if you modify the manifest you could broaden that. chrome_browser_set_download_behaviorlets the model set an arbitrary download directory (by design, for save-file workflows). Combined with the download tools it's a write-to-disk primitive — treat prompt-injected download requests with the same care as any other side-effectful tool call.- The JSX preview pulls Babel (unpkg) + React/framer-motion (esm.sh) from public CDNs at preview time. These are unpinned beyond major version and have no SRI (esm.sh resolves dynamically, so static SRI isn't practical). A CDN compromise could affect preview rendering only — the preview is sandboxed (
allow-scripts, no app access), so the blast radius is the iframe, not the app. - Git history (pre-2026-05-31) may contain a previously-committed session note with personal details and a
workspace-index.jsonsnapshot. HEAD is scrubbed + the paths are gitignored; a history rewrite (git filter-repo+ force-push) is the way to purge older blobs if that matters for your fork.