Open-Source Wikis

/

Cal.com

/

Security

calcom/cal.com

Security

The trust boundaries Cal.diy crosses and how the codebase defends each one.

Threat model

Cal.diy runs as a multi-tenant scheduling platform. The major trust boundaries:

  1. Public booking page — anonymous internet visitors hit /[user]/[eventType]. Untrusted input flows into slot queries and booking submissions.
  2. Authenticated dashboard — logged-in users (or team members) see and edit their own data.
  3. Admin / system actions — promoted users with cross-tenant power.
  4. Inbound webhooks — third-party services (Stripe, Daily, calendar providers) deliver signed payloads.
  5. Outbound calls — calendar / video / payment / CRM APIs receive credentials and accept event payloads.
  6. API v2 surface — third-party developers and Platform clients call the REST API.
  7. Embeds — Cal.diy is loaded inside iframes on third-party sites; postMessage is the only sanctioned channel.

Encryption at rest

The headline secret is CALENDSO_ENCRYPTION_KEY — a 24-byte key (openssl rand -base64 24) used by packages/lib/crypto.ts (AES-256-CBC) to encrypt:

  • Credential.key (every third-party access/refresh token)
  • 2FA secrets on User
  • Some app-specific secrets

AGENTS.md is explicit: Credential.key must never be selected into anything that flows to a client. Repositories use select to omit it; audit logs and Sentry payloads run through packages/lib/redactSensitiveData.ts.

NEXTAUTH_SECRET (32-byte base64) signs NextAuth's JWT and CSRF tokens.

SSRF protection

Cal.diy makes outbound HTTP calls in many places (calendar providers, webhooks, redirect helpers). packages/lib/ssrfProtection.ts blocks SSRF attempts:

  • Rejects private IPv4 ranges (10/8, 172.16/12, 192.168/16, 169.254/16)
  • Rejects loopback (127/8, ::1)
  • Rejects link-local and unique local addresses
  • Resolves DNS and rechecks
  • Used by the fetch wrapper in packages/lib/fetch-wrapper.ts

Input validation

  • Every tRPC procedure has a Zod schema (<action>.schema.ts). The router refuses requests that don't validate.
  • API v2 controllers use class-validator DTOs.
  • Booking responses are validated through a per-event-type schema generated from the booking-fields configuration (packages/features/bookings/lib/getBookingResponsesSchema.ts).
  • Markdown content is sanitized through packages/lib/markdownToSafeHTML.ts (markdown-it + DOMPurify).

Redirect safety

packages/lib/getSafeRedirectUrl.ts validates a redirect URL against the configured allowlist. Used after authentication and after the booking flow.

Rate limiting

  • Web apppackages/lib/rateLimit.ts (Redis token bucket) is wrapped around tRPC procedures and Pages-Router APIs.
  • API v2CustomThrottlerGuard (apps/api/v2/src/lib/throttler-guard.ts), Redis-backed, registered as APP_GUARD.
  • IP extraction — both rely on packages/lib/getIP.ts, which is X-Forwarded-For aware.

Webhooks (inbound)

Inbound webhooks (Stripe, Daily, Vercel deployments, calendar push notifications) verify signatures before mutating state:

  • Stripe — verified with the Stripe webhook secret in packages/app-store/stripepayment.
  • Vercel deployment webhook — apps/api/v2/src/vercel-webhook.guard.ts checks the configured shared secret.
  • Calendar push notifications — verified per-provider in each app-store adapter; the body is fetched fresh from the API (we don't trust the push payload).

Webhooks (outbound)

Outbound webhooks (features/webhooks) HMAC-sign each payload with a per-subscriber secret. Subscribers verify the signature before trusting the payload.

Auth hardening

  • Passwords hashed with bcrypt; rules in packages/features/auth/lib/validPassword.ts.
  • Email verification required for new signups (sendVerificationRequest.ts).
  • TOTP-based 2FA (packages/lib/totp.ts).
  • Bot detection on signup via packages/features/bot-detection and packages/features/watchlist.
  • Session cookies follow packages/lib/default-cookies.ts (SameSite=Lax, Secure in production).

Authorization

  • PBAC (permission-based access control) governs team / org boundaries. SQL helpers in packages/prisma/sql/, runtime checks in packages/features/membership and per-resource code paths.
  • Permission checks belong in page.tsx, never layout.tsx (AGENTS.md).
  • The Credential.key rule is enforced as a coding convention, not a DB constraint. Reviewers watch for select: { key: true } slipping in.

Embed boundary

embed-core only accepts a fixed set of postMessage commands; the iframe-side runtime in apps/web validates event.origin before reacting to messages. The published bundle pins NEXT_PUBLIC_WEBAPP_URL so embeds can't be tricked into pointing at a hostile origin.

Sensitive log redaction

packages/lib/redactSensitiveData.ts and packages/lib/piiFreeData.ts strip:

  • Passwords, password resets
  • API keys, OAuth tokens
  • credential.key (the encrypted blob)
  • Email addresses where the surface doesn't need them
  • IPs where the surface doesn't need them

Both tslog configurations apply this redaction by default.

Reporting

SECURITY.md at the repo root describes responsible disclosure. The repo also has a .snyk policy file for the Snyk scanner inside apps/api/v2/.

Where to look

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Security – Cal.com wiki | Factory