Skip to content

feat: implement CSRF protection for OAuth flows with nonce validation #20983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Aug 1, 2025

Conversation

iQQBot
Copy link
Contributor

@iQQBot iQQBot commented Jul 29, 2025

Description

feat: implement CSRF protection for OAuth flows with nonce validation

Feature Flags:

  • enable_strict_authorize_return_to
  • enable_nonce_validation

Related Issue(s)

Fixes CLC-1592
Fixes CLC-1643
Fixes CLC-1642

How to test

  1. make sure you can login / logout using different scm provider
  2. make sure you can bind other scm provider

Documentation

Preview status

Gitpod was successfully deployed to your preview environment.

Build Options

Build
  • /werft with-werft
    Run the build with werft instead of GHA
  • leeway-no-cache
  • /werft no-test
    Run Leeway with --dont-test
Publish
  • /werft publish-to-npm
  • /werft publish-to-jb-marketplace
Installer
  • analytics=segment
  • with-dedicated-emulation
  • workspace-feature-flags
    Add desired feature flags to the end of the line above, space separated
Preview Environment / Integration Tests
  • /werft with-local-preview
    If enabled this will build install/preview
  • /werft with-preview
  • /werft with-large-vm
  • /werft with-gce-vm
    If enabled this will create the environment on GCE infra
  • /werft preemptible
    Saves cost. Untick this only if you're really sure you need a non-preemtible machine.
  • with-integration-tests=all
    Valid options are all, workspace, webapp, ide, jetbrains, vscode, ssh. If enabled, with-preview and with-large-vm will be enabled.
  • with-monitoring

/hold

iQQBot and others added 6 commits July 28, 2025 15:45
- Add NonceService for cryptographically secure nonce generation and validation
- Include nonce in JWT state for OAuth authorization requests
- Store nonce in secure httpOnly cookie with SameSite=strict
- Validate nonce matches between state and cookie in auth callback
- Add origin/referer header validation for additional CSRF protection
- Use timing-safe comparison to prevent timing attacks
- Clear nonce cookie after successful validation or on error

This prevents CSRF attacks where malicious sites could initiate OAuth flows
on behalf of users by ensuring authorization requests originate from Gitpod.

Co-authored-by: Ona <[email protected]>
…on complexity

Replace complex Authenticator dependency injection test with simple unit test
that focuses on the core logic without requiring all service dependencies.

This makes the test more reliable and easier to maintain while still validating
the critical api subdomain detection logic for the GitHub OAuth edge case.

Co-authored-by: Ona <[email protected]>
Update test examples and documentation to use production-appropriate
domain examples (gitpod.io) instead of specific preview environment
domains for better clarity and maintainability.

Co-authored-by: Ona <[email protected]>
Co-authored-by: Ona <[email protected]>
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements CSRF protection for OAuth flows with nonce validation to prevent cross-site request forgery attacks. The implementation adds cryptographically secure nonce generation, validation mechanisms, and fragment protection for OAuth redirects.

  • Introduces a new NonceService for generating and validating CSRF nonces during OAuth flows
  • Adds fragment protection to prevent OAuth token inheritance attacks by ensuring redirect URLs have fragments
  • Implements origin validation and handles GitHub OAuth API subdomain redirect edge cases

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
components/server/src/container-module.ts Registers the new NonceService as a singleton in the DI container
components/server/src/auth/nonce-service.ts Core implementation of nonce generation, validation, and origin checking for CSRF protection
components/server/src/auth/nonce-service.spec.ts Comprehensive test suite for NonceService functionality
components/server/src/auth/login-completion-handler.ts Adds fragment protection to returnTo URLs using ensureUrlHasFragment utility
components/server/src/auth/fragment-utils.ts Utility function to ensure URLs have fragments to prevent OAuth token inheritance
components/server/src/auth/fragment-protection.spec.ts Tests for fragment protection utility functionality
components/server/src/auth/authenticator.ts Integrates nonce validation into OAuth callback handling and generates nonces for auth flows
components/server/src/auth/auth-provider.ts Extends AuthFlow interface to include optional nonce field
components/server/src/auth/api-subdomain-redirect.spec.ts Tests for API subdomain redirect logic to handle GitHub OAuth edge cases

import * as crypto from "crypto";
import { Config } from "../config";

const NONCE_COOKIE_NAME = "__Host-_gitpod_oauth_nonce_";
Copy link
Preview

Copilot AI Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cookie name uses the __Host- prefix but the cookie configuration may not meet all __Host- requirements. The __Host- prefix requires the cookie to be secure, have no domain attribute, and path must be '/'. Verify that the domain attribute is not set anywhere in the cookie configuration.

Copilot uses AI. Check for mistakes.

iQQBot and others added 2 commits July 29, 2025 15:37
@roboquat roboquat added size/XXL and removed size/XL labels Jul 29, 2025
iQQBot and others added 3 commits July 30, 2025 10:52
Co-authored-by: Ona <[email protected]>
…urnTo

Add two feature flags to control security features with safe defaults:

**Feature Flag 1: enable_nonce_validation (default: false)**
- Controls CSRF nonce validation in OAuth flows
- When disabled: Nonce is generated but not validated (future compatibility)
- When enabled: Full CSRF protection with nonce and origin validation
- Nonce cookies are always generated and cleared for consistency

**Feature Flag 2: enable_strict_authorize_return_to (default: false)**
- Controls returnTo validation strictness for /api/authorize endpoint
- When disabled: Falls back to login validation (broader patterns)
- When enabled: Uses strict authorize validation (limited to specific paths)
- /api/login always uses login validation regardless of flag

**Implementation Details:**
- Always generate nonce for consistency and future compatibility
- Only validate nonce when feature flag is enabled
- Always clear nonce cookies regardless of validation state
- Authorize endpoint checks flag and falls back gracefully
- Comprehensive logging for debugging and monitoring

**Backward Compatibility:**
- Default false ensures no breaking changes
- Gradual rollout possible via feature flag configuration
- Existing authentication flows continue to work
- Safe fallback behavior when flags are disabled

Co-authored-by: Ona <[email protected]>
Update NonceService.validateOrigin to check request origin against the
expected SCM provider domain instead of Gitpod's own domain. This fixes
the CSRF protection logic for OAuth callbacks which legitimately come
from external providers (github.com, gitlab.com, etc.).

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@iQQBot
Copy link
Contributor Author

iQQBot commented Jul 30, 2025

Hey @geropl could you review again?

@iQQBot iQQBot requested a review from geropl July 30, 2025 13:47
@iQQBot iQQBot requested a review from Copilot July 30, 2025 13:47
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@iQQBot iQQBot requested a review from geropl July 30, 2025 16:09
@iQQBot
Copy link
Contributor Author

iQQBot commented Jul 31, 2025

@geropl Cloud you review again?

@geropl
Copy link
Member

geropl commented Jul 31, 2025

@iQQBot Yes, give me 30mins. 👍

return;
}

// Update session info
let returnTo = returnToUrl || this.config.hostUrl.asDashboard().toString();
const returnToParam = returnToUrl || this.config.hostUrl.asDashboard().toString();
let returnTo = returnToParam;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to validate the returnTo param here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returnTo is from the state and signed by JWT. We do verify in the generate side.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my bad. 🎯

@geropl
Copy link
Member

geropl commented Jul 31, 2025

@iQQBot I pushed a commit with minor changes to discuss with you

@geropl
Copy link
Member

geropl commented Jul 31, 2025

@iQQBot I will one more manual test in ~1h and then we can approve and merge.

Copy link
Member

@geropl geropl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM, tested and works! ✔️

@geropl
Copy link
Member

geropl commented Aug 1, 2025

/unhold

@roboquat roboquat merged commit a736c1b into main Aug 1, 2025
33 checks passed
@roboquat roboquat deleted the pd/nonce branch August 1, 2025 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants