Skip to content
CP-MCP
Get Support

Security model

CP-MCP is a thin adapter that holds your Cloudpath admin credentials on your behalf, authenticates every request to a single user, and never exposes secrets to Claude. This page covers the posture in detail.

Two independent OAuth flows authenticate the two things that reach the server: your browser (to manage credentials) and Claude (to make MCP requests).

  • Web dashboard — Google OAuth (Authorization Code + PKCE) issues a session cookie cpmcp_session. Only the SHA-256 hash of the cookie is stored.
  • MCP connector — Claude registers via OAuth 2.1 Dynamic Client Registration (RFC 7591) + PKCE. The access token (prefix cpmk_) is stored hashed and bound to your user id; the hash is checked before any Cloudpath call.

Both flows resolve to the same user record, so your connector identity and your dashboard identity are one and the same.

Your Cloudpath password is stored in Cloudflare D1, encrypted with AES-256-GCM using a per-record key derived via HKDF-SHA256 from a root ENCRYPTION_KEY that lives only in Cloudflare’s encrypted secret store. The plaintext password is decrypted in memory only when a new Cloudpath JWT needs to be minted — it is never returned to Claude, written to a log line, or placed in any tool response.

Cloudpath authenticates API callers with a short-lived JWT (~5 minute lifetime) minted by posting your username and password to /token on the relevant surface root. CP-MCP manages that lifecycle server-side:

  1. On every call, CP-MCP first loads your credential record from D1. If there isn’t one, the call stops there with no_credentials — the token cache is never consulted.
  2. It then reads a cached JWT from a per-user, per-surface key-value entry (Cloudflare KV, cp_token:<userId>:<surface>). Property-surface entries add the portal GUID — cp_token:<userId>:property:<portalGuid> — because each portal authenticates separately.
  3. On a miss, it decrypts your password just long enough to mint a fresh JWT, then caches it — capped at 240 seconds, a safety margin below the upstream’s ~5-minute expiry.
  4. The JWT is attached to every upstream request as the raw Authorization header — Cloudpath does not accept a Bearer prefix.
  5. A 401 or 406 from Cloudpath triggers exactly one automatic re-mint and retry. Repeated auth failures surface to the model as ok: false.

Claude never handles the JWT directly.

Your CloudpathKV (token cache)D1 (credentials)CP-MCP Workercode_mode isolateYour CloudpathKV (token cache)D1 (credentials)CP-MCP Workercode_mode isolatealt[Cached token still valid][Miss or expired]opt[Cloudpath answers 401 or 406]core.call({ method, path })Load credential recordEncrypted password (or nothing → no_credentials)Read cp_token:user:surfaceJWTDecrypt password in memoryPOST /token (username + password)JWT + expireDateTimeCache JWT (max 240s)Request with raw Authorization: JWTFlush the cached token, re-mint onceRetry the request exactly onceResponse{ ok, status, body, retried }

Cloudflare Workers enforce strict TLS on outbound fetch and do not expose a skip-verify option, so your Cloudpath deployment must present a publicly trusted TLS certificate.

  • Cloud-hosted Cloudpath at *.cloudpath.net already presents a valid certificate — nothing to do.
  • On-premises installs with a self-signed certificate will not work. Install a certificate from a public CA.

CP-MCP refuses to connect to hosts that are not on the public internet, both when you save credentials and on every subsequent call. This stops the Worker from being used as a confused deputy to probe private networks from Cloudflare’s egress.

Rejected with a forbidden_host error:

RejectedExamples
Private IPv4 ranges10.x, 172.16–31.x, 192.168.x
Loopback and unspecified127.x, 0.x, ::1
Link-local and CGNAT169.254.x, 100.64–127.x, fe80::
Multicast and reserved IPv4224.x through 255.x
Unique-local and multicast IPv6fc00::/fd00::, ff00::
IPv4-mapped IPv6 of any range above::ffff:10.0.0.1
IPv4 written in anything but dotted decimal, public or not2130706433, 0x7f.0.0.1, 0177.0.0.1, 127.1
Internal hostnameslocalhost, broadcasthost, and anything ending .local, .internal, or .localhost

A single trailing dot is stripped before these checks, so 127.0.0.1. is rejected like 127.0.0.1.

So an on-premises Cloudpath must be reachable at a public FQDN with a public IP, not at an internal address or a split-horizon internal-only name. A publicly resolvable name that happens to resolve to a private address is not blocked by this check — it will simply fail to connect.

CP-MCP deliberately does not layer its own role model on top of Cloudpath. Claude can reach any endpoint the saved admin account is permitted to call, for any HTTP method — there is no separate read-only mode or confirm-before-write gate in CP-MCP. Access is governed entirely by the Cloudpath account you save. Use Cloudpath’s role-based access controls to scope that account by role and permission if you want to limit what Claude can do.

To cut Claude off from your Cloudpath — offboarding, responding to a suspected leak, or just pausing while you investigate — delete your saved credentials in the dashboard. Deletion is immediate and complete:

  • The encrypted password row is deleted from D1.
  • Every cached JWT is flushed from KV at the same time.
  • Crucially, the credential record is checked before the token cache on every call, so even a token that survived the flush can never be used. There is no window in which a cached JWT keeps working.

You do not need to rotate the Cloudpath password to make the cutover take effect. Rotate it if the password itself may have been exposed — that is a separate concern from revoking CP-MCP’s access.

  • Rotate — paste the new password in the dashboard and save. The old ciphertext is replaced, and the next call mints a fresh JWT with the new password. No change is needed in Claude.
  • Saving new credentials flushes the cached JWTs, so switching deployments takes effect on the next call.

Every tools/call writes one usage row, and each generate_image writes one cost row. Both are metadata only:

RecordedFields
MCP usageYour user id, timestamp, tool name (code_mode), ok/error, duration in milliseconds
Image generationYour user id, timestamp, target, model, neuron cost, AI and overlay durations, ok/error
Terms acceptanceYour user id, the terms and privacy versions accepted, timestamp, IP address, and user agent

Nothing about the content of a request is stored: not the TypeScript Claude wrote, not the arguments it passed, not the Cloudpath endpoints it hit, and not a single byte of a Cloudpath response. Your password and the minted JWTs are never written to a log line either.

You accept the terms and privacy policy when you first sign in; a version bump re-prompts you, and each acceptance is logged separately.