Skip to content

Security: Rate limiting missing on critical endpoints #245

Description

@chonilius

Problem

The backend API lacks rate limiting on authentication and mutation endpoints, making it vulnerable to:

  • Brute force attacks on OAuth flows
  • Replay attacks on idempotent mutations
  • DoS attacks exhausting database/Soroban RPC resources

Vulnerable Endpoints

Based on the API overview (README.md), these endpoints lack rate limiting:

Authentication

  • POST /api/auth/login - OAuth initiation
  • POST /api/auth/callback - OAuth completion

High-value mutations

  • POST /api/bounties/:id/fund
  • POST /api/bounties/:id/claim
  • POST /api/escrow/:id/release
  • POST /api/escrow/:id/refund
  • POST /api/milestones/:id/fund
  • POST /api/maintenance-pools/:id/deposit
  • POST /api/github/webhooks (external endpoint)

List endpoints (resource exhaustion)

  • GET /api/bounties
  • GET /api/milestones
  • GET /api/analytics/*

Attack Scenarios

1. Brute Force OAuth State Parameter

An attacker could repeatedly hit the OAuth callback trying different state values to hijack sessions.

2. Idempotency Key Exhaustion

The idempotency system has a 30-second conflict window. An attacker could:

  • Generate thousands of UUIDs
  • Send concurrent requests with unique keys
  • Exhaust database connections and Soroban RPC quota
  • Cause legitimate requests to fail

3. Webhook DoS

Without rate limiting, an attacker could flood /api/github/webhooks with invalid payloads to:

  • Exhaust database with WebhookEvent rows
  • CPU spike from HMAC verification
  • Block legitimate GitHub webhook deliveries

Recommended Solution

Implement multi-tier rate limiting with @nestjs/throttler:

// app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      {
        name: 'short',
        ttl: 1000,  // 1 second
        limit: 3,   // 3 requests
      },
      {
        name: 'medium',
        ttl: 60000,  // 1 minute
        limit: 20,
      },
      {
        name: 'long',
        ttl: 3600000,  // 1 hour
        limit: 100,
      },
    ]),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

Per-endpoint overrides:

// Critical mutations: stricter limits
@Throttle({ short: { limit: 1, ttl: 1000 } })
@Post('bounties/:id/fund')
async fundBounty() { ... }

// Webhooks: separate limit by IP
@Throttle({ medium: { limit: 50, ttl: 60000 } })
@Post('github/webhooks')
async handleWebhook() { ... }

// Public lists: lenient but protected
@Throttle({ long: { limit: 1000, ttl: 3600000 } })
@Get('bounties')
async listBounties() { ... }

Redis-backed storage (production):

ThrottlerModule.forRoot({
  storage: new ThrottlerStorageRedisService(new Redis({
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT),
  })),
})

Additional Hardening

  1. IP-based blocking: Ban IPs after N failed signature verifications
  2. Exponential backoff: Increase delays after repeated 429 responses
  3. Webhook IP allowlist: GitHub publishes their webhook source IPs - validate against them
  4. Per-user limits: Track limits by userId for authenticated endpoints, not just IP

References

Priority

🟠 High - Should be implemented before public beta

Metadata

Metadata

Assignees

Labels

Stellar WaveIssues in the Stellar wave program

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions