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 sign-in flows, one identity
Section titled “Two sign-in flows, one identity”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.
Credentials at rest
Section titled “Credentials at rest”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.
The Cloudpath JWT
Section titled “The Cloudpath JWT”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:
- On every
call, CP-MCP first loads your credential record from D1. If there isn’t one, the call stops there withno_credentials— the token cache is never consulted. - 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. - 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.
- The JWT is attached to every upstream request as the raw
Authorizationheader — Cloudpath does not accept aBearerprefix. - A
401or406from Cloudpath triggers exactly one automatic re-mint and retry. Repeated auth failures surface to the model asok: false.
Claude never handles the JWT directly.
TLS to Cloudpath
Section titled “TLS to Cloudpath”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.netalready 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.
The FQDN must be publicly routable
Section titled “The FQDN must be publicly routable”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:
| Rejected | Examples |
|---|---|
| Private IPv4 ranges | 10.x, 172.16–31.x, 192.168.x |
| Loopback and unspecified | 127.x, 0.x, ::1 |
| Link-local and CGNAT | 169.254.x, 100.64–127.x, fe80:: |
| Multicast and reserved IPv4 | 224.x through 255.x |
| Unique-local and multicast IPv6 | fc00::/fd00::, ff00:: |
| IPv4-mapped IPv6 of any range above | ::ffff:10.0.0.1 |
| IPv4 written in anything but dotted decimal, public or not | 2130706433, 0x7f.0.0.1, 0177.0.0.1, 127.1 |
| Internal hostnames | localhost, 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.
Access control
Section titled “Access control”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.
Stopping Claude’s access
Section titled “Stopping Claude’s access”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.
Rotating the password
Section titled “Rotating the password”- 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.
What CP-MCP records
Section titled “What CP-MCP records”Every tools/call writes one usage row, and each generate_image writes one
cost row. Both are metadata only:
| Recorded | Fields |
|---|---|
| MCP usage | Your user id, timestamp, tool name (code_mode), ok/error, duration in milliseconds |
| Image generation | Your user id, timestamp, target, model, neuron cost, AI and overlay durations, ok/error |
| Terms acceptance | Your 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.