Files
stack/docs/scratchpads/278-csrf-protection.md
Jason Woltje ebd842f007
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix(#278): Implement CSRF protection using double-submit cookie pattern
Implemented comprehensive CSRF protection for all state-changing endpoints
(POST, PATCH, DELETE) using the double-submit cookie pattern.

Security Implementation:
- Created CsrfGuard using double-submit cookie validation
- Token set in httpOnly cookie and validated against X-CSRF-Token header
- Applied guard to FederationController (vulnerable endpoints)
- Safe HTTP methods (GET, HEAD, OPTIONS) automatically exempted
- Signature-based endpoints (@SkipCsrf decorator) exempted

Components Added:
- CsrfGuard: Validates cookie and header token match
- CsrfController: GET /api/v1/csrf/token endpoint for token generation
- @SkipCsrf(): Decorator to exempt endpoints with alternative auth
- Comprehensive tests (20 tests, all passing)

Protected Endpoints:
- POST /api/v1/federation/connections/initiate
- POST /api/v1/federation/connections/:id/accept
- POST /api/v1/federation/connections/:id/reject
- POST /api/v1/federation/connections/:id/disconnect
- POST /api/v1/federation/instance/regenerate-keys

Exempted Endpoints:
- POST /api/v1/federation/incoming/connect (signature-verified)
- GET requests (safe methods)

Security Features:
- httpOnly cookies prevent XSS attacks
- SameSite=strict prevents subdomain attacks
- Cryptographically secure random tokens (32 bytes)
- 24-hour token expiry
- Structured logging for security events

Testing:
- 14 guard tests covering all scenarios
- 6 controller tests for token generation
- Quality gates: lint, typecheck, build all passing

Note: Frontend integration required to use tokens. Clients must:
1. GET /api/v1/csrf/token to receive token
2. Include token in X-CSRF-Token header for state-changing requests

Fixes #278

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 20:35:00 -06:00

4.5 KiB

Issue #278: Implement CSRF protection on state-changing endpoints

Objective

Implement CSRF protection for all state-changing endpoints (POST, PATCH, DELETE) to prevent CSRF attacks.

Security Impact

Vulnerable Endpoints:

  • Connection initiation (POST /api/v1/federation/connections/initiate)
  • Connection acceptance (POST /api/v1/federation/connections/:id/accept)
  • Agent spawn (POST /api/v1/agents/spawn)
  • Identity linking (POST endpoints in auth module)

Modern CSRF Protection Approaches

  • Set SameSite=Strict or SameSite=Lax on session cookies
  • No code changes required if already using sessions
  • Modern browser support
  • Limitation: Doesn't protect against subdomain attacks
  • Generate CSRF token, store in cookie and send in header
  • Validate that cookie and header match
  • No server-side session storage required
  • Works well with stateless apps

Option 3: Synchronizer Token Pattern

  • Generate CSRF token per session
  • Store in session, validate on each request
  • Requires session storage
  • Most secure but complex

Use Double Submit Cookie Pattern:

  1. Generate CSRF token on first authenticated request
  2. Set token in httpOnly cookie
  3. Client includes token in X-CSRF-Token header
  4. Server validates cookie matches header

Exempt signature-based endpoints:

  • Federation incoming connections (already signature-verified)
  • Any public endpoints that don't require authentication

Implementation Plan

1. Create CSRF Guard

// src/common/guards/csrf.guard.ts
@Injectable()
export class CsrfGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();

    // Exempt GET, HEAD, OPTIONS (safe methods)
    if (["GET", "HEAD", "OPTIONS"].includes(request.method)) {
      return true;
    }

    // Get token from cookie and header
    const cookieToken = request.cookies["csrf-token"];
    const headerToken = request.headers["x-csrf-token"];

    // Validate tokens match
    if (!cookieToken || !headerToken || cookieToken !== headerToken) {
      throw new ForbiddenException("Invalid CSRF token");
    }

    return true;
  }
}

2. CSRF Token Generation Endpoint

@Get('csrf-token')
getCsrfToken(@Res() response: Response): { token: string } {
  const token = crypto.randomBytes(32).toString('hex');
  response.cookie('csrf-token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
  });
  return { token };
}

3. Apply Guard Globally (with exemptions)

// main.ts
app.useGlobalGuards(new CsrfGuard());

Or per-controller/route:

@UseGuards(CsrfGuard)
@Controller("api/v1/federation")
export class FederationController {
  // Endpoints automatically protected
}

4. Exempt Signature-Based Endpoints

@Post('incoming/connect')
@SkipCsrf() // Custom decorator
async handleIncomingConnection() {
  // Signature verification provides authentication
}

Alternative: Check Existing Protection

Before implementing, verify if CSRF protection already exists:

  1. Check if session cookies use SameSite attribute
  2. Check for existing CSRF middleware
  3. Check authentication middleware configuration

Testing Requirements

  1. Test CSRF token generation endpoint
  2. Test protected endpoint rejects missing token
  3. Test protected endpoint rejects mismatched token
  4. Test protected endpoint accepts valid token
  5. Test exempted endpoints work without token
  6. Test safe methods (GET) work without token

Progress

  • Create scratchpad
  • Check for existing CSRF protection
  • Decide on implementation approach
  • Create CSRF guard
  • Create token generation endpoint
  • Apply guard to controllers
  • Add exemptions for signature-based endpoints
  • Add tests
  • Update frontend to include tokens
  • Run quality gates
  • Commit and push
  • Close issue

Notes

Important Considerations:

  1. Don't break existing API consumers
  2. Ensure frontend can get and use tokens
  3. Document token usage for API clients
  4. Consider backward compatibility

Scope Decision: Given this is backend-focused and the frontend integration is complex, consider:

  • Implementing SameSite cookie protection (simpler, immediate benefit)
  • OR implementing CSRF guard with proper exemptions
  • Document that frontend integration is required for full protection