Skip to main content
This page describes security behavior you can observe and verify through the product and the API. It documents what the platform does — not aspirations.

API key handling

  • A key’s secret is generated from 32 random bytes and shown once, when the key is created or rotated. Cactal stores only its SHA-256 hash; nothing on Cactal’s side can reveal the plaintext again.
  • Rotation (POST /v1/apiKeys/{apiKeyId}/rotate) replaces the secret immediately — the old secret stops authenticating the moment the call returns.
  • Revocation (POST /v1/apiKeys/{apiKeyId}/revoke) is permanent. Keys can also carry an expiresAt timestamp; expired and revoked keys fail authentication with 401.
  • REST routes on api.cactal.ai accept bearer API keys. The MCP endpoints also accept OAuth access tokens. Requests carrying a browser session cookie are rejected with 401, so bearer credentials and sessions can never be confused on the public surface.
See Authentication for the full key lifecycle.

Access control

Authorization is capability-based. Roles expand to capability sets, and every operation — over the dashboard, REST, or MCP — checks one required capability before running.
  • Organization roles: owner, admin, full_editor, content_editor.
  • Website-scoped roles: full_editor, content_editor, granted per website.
  • API keys carry the same model: organization-scoped or website-scoped access with a role. The owner role is not grantable to keys.
  • AI connections carry the grants chosen at consent, never more than the connecting user’s own. See AI connections.
  • Only people can create organizations. API keys and AI connections cannot.
The MCP server applies the same checks and additionally filters tools/list, so a connection or key never sees tools it cannot call. See Access control.

MCP OAuth

  • Each MCP endpoint is its own OAuth protected resource with standard metadata, so MCP clients discover the authorization flow without Cactal-specific configuration, and its signed access tokens carry that endpoint as their exact audience, so a token for one endpoint is rejected by the other. See OAuth for scopes, metadata, and tokens.
  • Consent is explicit and binds the connection to the access chosen there.
  • Disconnecting an app cuts it off immediately: every call checks that the connection is still live, so the next call fails even with an access token already issued. Removing someone from an organization also cuts their connections to it.
  • The consent screen shows verified identity only where Cactal can prove it. Claude (claude.ai), Claude Code, and ChatGPT are verified by their Client ID Metadata Documents or by where authorization codes are delivered. Every other app is shown as unverified, next to the domain its identity rests on, and apps that receive the code on the user’s own computer are labeled as such. Cactal accepts Client ID Metadata Documents from public HTTPS origins; client metadata may link to a public homepage on another HTTPS origin, while non-loopback redirect URIs remain bound to the metadata document’s origin.
  • All public clients use the authorization code flow with PKCE; client-credentials grants are not exposed.
  • Tool calls run with the connection’s grants and are attributed to the person who connected it.

Tenant isolation

A website you cannot access returns 404 not_found, the same response as a website that does not exist, so website existence never leaks across organizations or across website scopes.
This applies to websites and everything scoped under them: source code, CMS, assets, domains, and analytics. Organization-scoped endpoints (members, invitations, the audit log, and billing) behave differently: a caller without a grant on an organization that exists gets 403 forbidden, and 404 not_found means the organization id itself is unknown.

Audit logging

Lifecycle and access-control operations write audit events to the organization’s audit log, whichever interface invoked them: website create/rename/delete/restore/transfer, editor grants and invitations, API keys, domains, organization members and invitations, billing, and analytics CSV export downloads. Source file edits, publishes, CMS content, and asset operations are not audit-logged — review content work through source versions, changed-file lists, and targeted diffs instead. Each audit event records:
  • The event type and a human-readable summary.
  • The affected resource type and id.
  • Actor attribution: actorKind distinguishes a user from an api_key, with a reference to the specific user or key. Actions through an AI connection are attributed to the person who connected it. A person who deleted their account is shown as Deleted user.
Query the log with GET /v1/organizations/{organizationId}/audit-log, filtering by actorKind, eventType, resourceType, actorPrincipalId, or websiteId (which matches both the website itself and resources belonging to it). Agent-safe publishing shows how to use this for agent oversight.

Draft privacy

Draft and version preview hosts (draft--<label> and v<n>--<label> on cactal.app) are served with:
Search engines are instructed not to index preview content. Preview URLs are reachable by whoever holds the link, so share them deliberately. Draft-only pages are part of the draft: they are absent from the published site and its code, but anyone with the draft link can view them.

Separated planes

The control plane and customer content run on different domains: Browser sessions exist only on the control plane. Customer-authored content — pages, scripts, assets — is served from the content plane, never from a control-plane origin. See Domains.

Publish build isolation

Publishing builds a website’s source in a disposable sandbox created for that job and discarded afterwards. A deployment serves traffic only after its build reaches ready; a failed build does not replace what is currently serving. See Publishing.

Abuse mitigation

The API rate-limits at several layers. Beyond the per-key buckets, two per-IP buckets protect the authentication surface itself:
  • A pre-authentication bucket caps total requests per IP before any key is checked.
  • An authentication-failure bucket allows a burst of 25 failed attempts per IP, refilling at 0.5 per second — throttling key guessing to a crawl.
Exact bucket sizes and 429 semantics are in Rate limits.

Sign-in

Human sign-in uses Google or a one-time code (OTP) sent to the email address. There are no passwords. The public API host exposes no sign-in routes; MCP OAuth sends users to the product app’s authorization server, while REST access uses API keys.

Reporting

Report suspected vulnerabilities or security incidents to support@cactal.ai. Include reproduction details; do not include API key secrets in the report.