Skip to content

Security Audit: Multiple Vulnerabilities Found (Critical/High/Medium) #29364

Description

@SH20RAJ

Security Audit Report - Cal.diy Codebase

Executive Summary

A comprehensive security audit identified 4 Critical, 7 High, and 11 Medium severity vulnerabilities across the codebase. The most urgent issues involve credential exposure, rate limiter bypass, CORS misconfiguration, and missing security headers.


CRITICAL Findings

1. credential.key Exposed in tRPC Booking Queries

File: packages/trpc/server/routers/viewer/bookings/util.ts:42

user: {
  include: {
    credentials: true,  // FETCHES ALL CREDENTIAL FIELDS INCLUDING `key`
  },
},

The bookingInclude object uses include: { credentials: true } on the user relation. The Prisma Credential model has a key field of type Json that stores OAuth tokens, API keys, and other secrets. Using include: true fetches ALL fields including key. This data flows through the tRPC pipeline and could be serialized to API responses.

Impact: OAuth access tokens, refresh tokens, API keys, and other credential secrets could be exposed to any authenticated user who can view bookings.

Fix: Replace include: { credentials: true } with a select that explicitly lists needed fields, excluding key.


2. Rate Limiter Fails Open (Bypasses All Rate Limiting)

File: packages/lib/rateLimit.ts:36-41

if (!UNKEY_ROOT_KEY) {
  return () => ({ success: true, limit: 10, remaining: 999, reset: 0 }) as RatelimitResponse;
}

If UNKEY_ROOT_KEY is unset or the Unkey service is down, all rate limiting is silently disabled. The onError handler (line 55) also returns success: true, meaning any Unkey outage removes all rate limiting from the entire application.

Impact: Complete bypass of all rate limiting on auth endpoints, booking creation, email sending, SMS, and API calls.

Fix: Fail closed (reject requests) when the rate limiter is unavailable, or implement a local fallback rate limiter.


3. CORS origin: "*" on API v2

File: apps/api/v2/src/bootstrap.ts:44

app.enableCors({
  origin: "*",
  methods: ["GET", "PATCH", "DELETE", "HEAD", "POST", "PUT", "OPTIONS"],
});

Any malicious website can make authenticated cross-origin requests to the Cal.com API. Combined with cookieParser() middleware, this creates a CSRF attack surface for cookie-based auth flows.

Impact: Cross-site request forgery, data theft from authenticated sessions.

Fix: Configure CORS to only allow specific trusted origins.


4. Missing Security Headers (No CSP, No HSTS)

File: apps/web/next.config.ts:371-499

Only X-Content-Type-Options and Referrer-Policy are set globally. X-Frame-Options: DENY only applies to /auth/* and /signup. Missing:

  • Content-Security-Policy (CSP) — intentionally disabled
  • Strict-Transport-Security (HSTS) — not set
  • X-XSS-Protection — not set
  • Permissions-Policy — not set

Impact: No protection against XSS, clickjacking (on most pages), or protocol downgrade attacks.


HIGH Findings

5. SSRF via User-Controlled ICS Feed URLs

File: packages/app-store/ics-feedcalendar/lib/CalendarService.ts:86

const reqPromises = await Promise.allSettled(this.urls.map((x) => fetch(x)));

User-configured URLs fetched server-side without URL validation. Could target internal services (169.254.169.254, localhost).

6. SSRF via Avatar API Prefill

File: packages/features/auth/signup/utils/prefillAvatar.ts:14

URLs from external Avatar API fetched without validation.

7. Non-Timing-Safe HMAC Comparison (HitPay Webhook)

File: packages/app-store/hitpay/api/webhook.ts:107

if (signed !== obj.hmac) {  // Vulnerable to timing attacks

Should use crypto.timingSafeEqual() (as BTCPay webhook correctly does).

8. Weak PRNG for Random Strings

File: packages/lib/random.ts:11

result += CHARACTERS.charAt(Math.floor(Math.random() * CHARACTERS_LENGTH));

Math.random() is cryptographically insecure. Should use crypto.randomBytes().

9. MD5 Used for Email Hashing

File: packages/trpc/server/routers/viewer/me/get.handler.ts:122

emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),

MD5 is cryptographically broken.

10. postMessage Without Origin Validation (Embed SDK)

Files: packages/embeds/embed-core/src/embed.ts:1565, embed-iframe.ts:559

All postMessage handlers lack origin checks. Outbound messages use targetOrigin="*". The codebase has a TODO acknowledging this gap.

11. Unauthenticated AES Encryption (No GCM)

File: packages/lib/crypto.ts:3

const ALGORITHM = "aes256";  // CBC mode without authentication

Vulnerable to padding oracle attacks. Should use aes-256-gcm.


MEDIUM Findings

12. HttpExceptionFilter Leaks Internal Details

File: apps/api/v2/src/filters/http-exception.filter.ts:35

Returns exception.getResponse() directly to clients without environment check.

13. ValidationPipe Leaks Target & Value

File: apps/api/v2/src/bootstrap.ts:63-64

validationError: { target: true, value: true }

Leaks full request object and submitted values in error responses.

14. Swagger UI Publicly Accessible

File: apps/api/v2/src/swagger/generate-swagger.ts:38-41

No auth gate when DOCS_URL env var is unset.

15. SameSite=none on Session Cookies

File: packages/lib/default-cookies.ts:25

Weakens CSRF protection for iframe embedding support.

16. JSON.parse Before OAuth State Verification

File: packages/app-store/_utils/oauth/decodeOAuthState.ts:12

State parsed before HMAC check. Several apps (stripe, basecamp3, dub, webex, tandem) skip nonce verification entirely.

17. Open Redirect (Signup)

File: apps/web/modules/signup-view.tsx:298

callbackUrl search parameter concatenated into redirect URL without getSafeRedirectUrl() validation.

18. Open Redirect (Stripe)

File: apps/api/v2/src/modules/stripe/controllers/stripe.controller.ts:58

returnTo query parameter taken directly from user input without validation.

19. Env Var JSON Injection

File: packages/lib/constants.ts:140-141

JSON.parse(`[${process.env.ALLOWED_HOSTNAMES || ""}]`)

20. Script Injection via Env Vars

File: apps/web/app/(use-page-wrapper)/layout.tsx:9-35

NEXT_PUBLIC_HEAD_SCRIPTS and NEXT_PUBLIC_BODY_SCRIPTS injected via dangerouslySetInnerHTML.

21. Inline Script Interpolation (Locale)

File: apps/web/pages/_document.tsx:61

__html: `window.calNewLocale = "${newLocale}";`

Locale from cookie interpolated without escaping.

22. Overly Permissive include in API v2 Repositories

File: apps/api/v2/src/modules/teams/event-types/teams-event-types.repository.ts

Uses include: { users: true, owner: true } fetching all fields including sensitive ones.


Positive Findings

  • Webhook signature verification correctly implemented for BTCPay using crypto.timingSafeEqual()
  • OAuth state verification uses HMAC-SHA256 with timingSafeEqual
  • CSRF protection properly implemented with randomBytes(32) and HttpOnly cookies
  • NextAuth session cookies are HttpOnly with secure flags
  • Credential encryption uses AES-256 with random IVs
  • Cloudflare Turnstile integration for bot protection
  • getSafeRedirectUrl() correctly validates redirect URLs on login page

Recommended Priority

  1. Immediate: Fix credential.key exposure (The getIntegrations function runs repeatedly due to state polling #1) — data leak in production
  2. Immediate: Fix rate limiter fail-open (When navigating to a calendso user that does not exist, return notFound. #2) — complete security bypass
  3. Urgent: Fix CORS wildcard (Fixed some minor bugs that caused console errors #3) — CSRF attack surface
  4. Urgent: Add security headers (Unable to login if running on custom port #4) — baseline protection
  5. High: Fix SSRF vectors (Error on startup if running on custom port #5, README Improvements #6) — server-side request forgery
  6. High: Fix crypto issues (Added Docker Support #7, Implemented the ability for users to modify their locale and timezone #8, Caldav support #9, MySQL support #11) — weak cryptography
  7. Medium: Fix remaining issues in order of impact

This audit was performed by automated security scanning of the codebase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions