Skip to content

fix: add CSRF protection to OAuth callback via HMAC-signed nonce (#28…#1

Open
erberkson wants to merge 1 commit intobefore-csrffrom
demo-csrf
Open

fix: add CSRF protection to OAuth callback via HMAC-signed nonce (#28…#1
erberkson wants to merge 1 commit intobefore-csrffrom
demo-csrf

Conversation

@erberkson
Copy link
Copy Markdown
Owner

…083)

  • fix: add CSRF protection to OAuth callback via HMAC-signed nonce

The OAuth state parameter was used only for passing application data (returnTo, teamId) with no cryptographic binding to the user session. An attacker could authorize their own account on a provider, capture the authorization code, and trick a logged-in user into visiting the callback URL to link the attacker's account to the victim's Cal.com profile.

Changes:

  • encodeOAuthState: generate a random nonce and HMAC-sign it with NEXTAUTH_SECRET + userId, injecting both into the OAuth state
  • decodeOAuthState: verify the HMAC on callback using timingSafeEqual; skip verification when nonce is absent (backwards compatible with apps that don't yet use encodeOAuthState)
  • Stripe callback: replace raw state.returnTo redirect with getSafeRedirectUrl to prevent open redirect, remove redundant getReturnToValueFromQueryState, add missing return on access_denied
  • fix: make CSRF nonce verification mandatory with allowlist for exempt apps

Makes nonce/HMAC verification mandatory by default in decodeOAuthState, preventing attackers from bypassing CSRF protection by omitting nonce fields from the state parameter.

Apps not yet migrated to encodeOAuthState (stripe, basecamp3, dub, webex, tandem) are explicitly allowlisted and pass their slug to decodeOAuthState to skip verification.

Addresses review feedback (identified by cubic) about the conditional check being trivially bypassable.

Co-Authored-By: unknown <>


What does this PR do?

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings
  • My PR is too large (>500 lines or >10 files) and should be split into smaller PRs

…com#28083)

* fix: add CSRF protection to OAuth callback via HMAC-signed nonce

The OAuth state parameter was used only for passing application data
(returnTo, teamId) with no cryptographic binding to the user session.
An attacker could authorize their own account on a provider, capture the
authorization code, and trick a logged-in user into visiting the callback
URL to link the attacker's account to the victim's Cal.com profile.

Changes:
- encodeOAuthState: generate a random nonce and HMAC-sign it with
  NEXTAUTH_SECRET + userId, injecting both into the OAuth state
- decodeOAuthState: verify the HMAC on callback using timingSafeEqual;
  skip verification when nonce is absent (backwards compatible with apps
  that don't yet use encodeOAuthState)
- Stripe callback: replace raw state.returnTo redirect with
  getSafeRedirectUrl to prevent open redirect, remove redundant
  getReturnToValueFromQueryState, add missing return on access_denied

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make CSRF nonce verification mandatory with allowlist for exempt apps

Makes nonce/HMAC verification mandatory by default in decodeOAuthState,
preventing attackers from bypassing CSRF protection by omitting nonce
fields from the state parameter.

Apps not yet migrated to encodeOAuthState (stripe, basecamp3, dub,
webex, tandem) are explicitly allowlisted and pass their slug to
decodeOAuthState to skip verification.

Addresses review feedback (identified by cubic) about the conditional
check being trivially bypassable.

Co-Authored-By: unknown <>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@matrixreview
Copy link
Copy Markdown

matrixreview Bot commented Apr 10, 2026

🔴 MatrixReview — RED

⚙️ = code-backed  ·  🔎 = doc-backed  ·  💭 = AI suggestion  ·  📖 = doc citation  ·  📝 = PR location

Risk: 60 files directly affected
Findings: 81 (72 code-backed, 16 doc-backed)

🔴 SECURITY — 4 findings (4 doc-backed) · expand 🔽
  • 🔎 [SECURITY] The PR introduces CSRF protection for OAuth callbacks using HMAC-signed nonces, which is a security improvement. However, the implementation includes a hardcoded allowlist of apps (stripe, basecamp...

    Read more · expand 🔽

    ...3, dub, webex, tandem) that are exempt from nonce verification. This creates a security bypass for those specific apps, allowing attackers to potentially link their own provider accounts to victim users' Cal.com profiles for those apps.

    - *Also flagged by: ARCHITECTURE, STYLE* 📖 *AGENTS_security_section.md lines 4-4* 📝 `packages/app-store/_utils/oauth/decodeOAuthState.ts line 5`
  • 🔎 [SECURITY] The PR uses process.env.NEXTAUTH_SECRET for HMAC signing. According to the company's security documentation, secrets should be handled carefully. While this is a proper use of a secret for crypto...

    Read more · expand 🔽

    ...graphic signing, there's no validation that NEXTAUTH_SECRET is set and sufficiently strong. If NEXTAUTH_SECRET is weak or missing, the CSRF protection becomes ineffective.

    - *Also flagged by: ARCHITECTURE* 📖 *AGENTS_security_section.md lines 4-4* 📝 `packages/app-store/_utils/oauth/decodeOAuthState.ts line 22`
  • 🔎 [SECURITY] The PR adds getSafeRedirectUrl for redirect validation in the Stripe callback, which helps prevent open redirect vulnerabilities. This is a good security practice that should be applied consisten...

    Read more · expand 🔽

    ...tly across all OAuth callbacks.

    - *Also flagged by: STYLE* 📖 *AGENTS_security_section.md lines 4-4* 📝 `packages/app-store/stripepayment/api/callback.ts line 17`
  • 🔎 [BUG] The PR modifies the Stripe callback to use getSafeRedirectUrl(state?.returnTo) but the state object might be undefined if decodeOAuthState returns undefined (e.g., when verification fails). Thi...

    Read more · expand 🔽

    ...s could cause a runtime error when trying to access state?.returnTo.

    📖 *AGENTS_security_section.md lines 4-4* 📝 `packages/app-store/stripepayment/api/callback.ts line 44`
🔴 ARCHITECTURE — 74 findings (72 code-backed, 2 doc-backed) · expand 🔽

🟢 LEGAL — No issues found

🟡 STYLE — 1 findings (1 doc-backed) · expand 🔽
  • 🔎 [STYLE] The PR modifies the IntegrationOAuthCallbackState type to add nonce and nonceHash fields, but doesn't update the corresponding JSDoc/TypeScript documentation to explain these new fields.
    📖 quality-code-comments_style_section.md lines 1-30 📝 packages/app-store/types.d.ts line 14-15
🔴 ONBOARDING — 2 findings (2 doc-backed) · expand 🔽
  • 🔎 [CHORE] PR description contains placeholder text and incomplete checklist items. The PR template requires specific information like issue numbers, visual demos, and testing instructions, but these sections...

    Read more · expand 🔽

    ... contain generic placeholders or are incomplete.

    📖 *PULL_REQUEST_TEMPLATE_onboarding_section.md lines 1-8*
  • 🔎 [CHORE] The PR checklist at the bottom of the description has all items unchecked, indicating the contributor hasn't completed self-review, documentation updates, or confirmed automated tests are in place ...

    Read more · expand 🔽

    ...as required by the PR template.

    📖 *PULL_REQUEST_TEMPLATE_onboarding_section.md lines 1-8*

👆 Click expand on any gate above to see full findings with evidence and citations.


Powered by MatrixReview · Report incorrect finding

⚙️ Generate Fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants