Skip to content
SZ-MCP
Get Support

Security model

Two facts shape everything below. Your SmartZone password never reaches Claude, and your SmartZone account is the only thing limiting what Claude can do. The first is enforced by the architecture; the second is a deliberate design decision that puts the control in your hands.

SZ-MCP exposes every WSG and SwitchM endpoint your saved admin account is permitted to call — reads, writes and deletes alike. It does not gate by HTTP method, does not maintain an allowlist, and does not layer its own role model on top of SmartZone’s.

That is on purpose: SmartZone already has role-based access control, with roles, domain scoping and per-permission grants. Duplicating it would mean two models to keep in sync and two places to get it wrong.

  • Create a dedicated SmartZone admin for SZ-MCP. Do not reuse one that humans or other automations log in with.
  • Scope it to the minimum role and domain your use case needs. If you want a read-only experience, grant a read-only role in SmartZone — that is where the restriction belongs.
  • Rotate its password on your normal schedule.

The password is stored in Cloudflare D1, encrypted with AES-256-GCM. The key is derived per record with HKDF-SHA256 from an ENCRYPTION_KEY that exists only in Cloudflare’s encrypted secret store.

Each record carries its own random 16-byte salt, stored alongside the ciphertext. The salt is not a secret; its job is to force per-record key derivation, so compromising the master key still means attacking each ciphertext individually rather than all of them at once.

The plaintext is decrypted in memory, only when a new service ticket needs minting, and is never written anywhere. It is never returned by any API: the credentials endpoint omits it, and the dashboard renders a placeholder instead.

SmartZone authenticates API calls with a service ticket, minted from your username and password and then passed on each request.

SmartZoneTicket cache (KV)SZ-MCP WorkerClaudeSmartZoneTicket cache (KV)SZ-MCP WorkerClaudealt[cached and fresh][missing or expired]call({ method, path })look up sz_ticket:userIdticketdecrypt passwordPOST /serviceTicketserviceTicketcache for 25 minutesrequest + ?serviceTicket=...responseresponse with url redacted

As the diagram shows, the ticket is fetched from cache where possible and minted from the decrypted password only when the cache misses. It is attached to the upstream URL by the Worker, and the response handed back to Claude has that parameter replaced with <redacted>.

PropertyValue
Cache locationCloudflare KV, keyed sz_ticket:<userId>
Cache lifetime25 minutes — deliberately shorter than SmartZone’s idle timeout
On 401The ticket is discarded, one fresh ticket is minted, and the request is retried exactly once
FlushedOn every credentials save, on delete, and on Test connection

A second 401 after the retry is returned to the model as-is rather than triggering another login.

How often SZ-MCP logs in to your controller

Section titled “How often SZ-MCP logs in to your controller”

Every service ticket is a login to SmartZone under the admin username you saved, so the ticket lifecycle decides what your controller’s admin or audit log sees from SZ-MCP:

  • While Claude is using the connector: at most one login every 25 minutes per SZ-MCP account. The ticket is not refreshed in the background and its lifetime is not extended by use, so a long session logs in again roughly every 25 minutes.
  • While nobody is using it: no logins at all. A ticket is minted only when a call finds none cached.
  • After a 401: one extra login for the single retry.
  • On Save & detect or Test connection: one login to detect the API version (a save that keeps the stored password skips detection). Both also flush the cached ticket, so the next call logs in again.
ItemReaches Claude?
SmartZone passwordNo — decrypted in the Worker only, never in the isolate
Service ticketNo — attached server-side, redacted from the url field
Your Google credentialsNo — Google handles the sign-in
MCP access tokenHeld by Claude; stored here only as a SHA-256 hash
SmartZone API responsesYes — this is the point

The program Claude writes runs in a separate sandboxed isolate with no network access of its own and no ambient credentials. Its only reach is the five primitives. See How code mode works.

Every MCP request is authenticated to exactly one user before any tool runs. Stored credentials, cached tickets and decrypted passwords are all keyed by user id, and there is no code path that serves one user’s request from another’s data.

Tokens and session cookies are stored hashed, never in plaintext:

CredentialStorage
Web session cookie szmcp_sessionSHA-256 hash; HttpOnly, Secure, SameSite=Lax; 30 days
MCP access token (szmk_…)SHA-256 hash; 30 days
MCP refresh tokenSHA-256 hash; 90 days, rotated on use
OAuth authorization codeSHA-256 hash; 10 minutes, single use

All calls to your controller are HTTPS with certificate validation enforced by the platform — which is why a publicly trusted certificate is a hard requirement rather than a recommendation.

Outbound destinations are constrained: hosts in private, loopback, link-local, CGNAT, multicast and reserved ranges are refused, both when you save credentials and on every call. The full list is in Controller requirements.

Observability logs record method, status and duration. Request and response bodies are not logged; SmartZone’s own login error bodies are logged server-side but never echoed to the client, because they can disclose account-lockout state and username hints.