Handoff: Cloudflare API Security — WAF, Rate Limiting, Bot Protection & Caching

Implementation plan for adding Cloudflare security layers to all 4 backend APIs

Handoff: Cloudflare API Security for All Backend APIs

From: Session 99 analysis (Cursor Agent) To: Future session agent Priority: High — APIs currently have no WAF, rate limiting, or bot protection Estimated Scope: ~2-3 hours Cloudflare dashboard + ~2 hours code changes Requires: Cloudflare dashboard access (dash.cloudflare.com)


Context & Motivation

What We Analyzed (Session 99)

A full audit of the Cloudflare integration revealed that:

  1. Cloudflare Pages service (apis/backend-api/src/app/cloudflare/cloudflare-pages.service.ts) is exclusively a frontend static deployment utility. It has zero API proxy/security functionality.
  2. DNS is managed in Cloudflare with proxied (orange cloud) CNAME records pointing API subdomains to Railway.
  3. No Cloudflare security features are configured for API traffic — no WAF, no rate limiting, no bot detection.
  4. CORS is the only access control — and CORS is a browser-enforced policy, not a security boundary. Any curl or script bypasses it entirely.

Current Architecture (As-Is)

┌─────────────────────────────────────────────────────────────────┐
│                          CLIENT                                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                      CLOUDFLARE (DNS Proxy)                      │
│                                                                  │
│  What it DOES:                                                   │
│  ✅ SSL termination                                              │
│  ✅ Basic L3/L4 DDoS absorption                                  │
│  ✅ IP masking of Railway origins                                │
│                                                                  │
│  What it DOES NOT do (gap):                                      │
│  ❌ WAF (Web Application Firewall)                               │
│  ❌ Rate limiting                                                │
│  ❌ Bot detection                                                │
│  ❌ API response caching                                         │
│  ❌ Managed security rulesets                                    │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                         RAILWAY                                  │
│                                                                  │
│  api.gograb.space       → backend-api       (port 3000)         │
│  ai-api.gograb.space    → backend-ai-api    (port 3001)         │
│  docs-api.gograb.space  → backend-docs-api  (port 3002)         │
│  vitto-api.gograb.space     → backend-vitto-api (port 3003)         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Target Architecture (To-Be)

┌─────────────────────────────────────────────────────────────────┐
│                          CLIENT                                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                CLOUDFLARE (Security Gateway)                     │
│                                                                  │
│  ✅ SSL termination                                              │
│  ✅ L3/L4 DDoS absorption                                       │
│  ✅ IP masking                                                   │
│  ✅ WAF managed rulesets (OWASP Top 10)               ← NEW     │
│  ✅ Rate limiting (tiered by endpoint)                ← NEW     │
│  ✅ Bot Fight Mode                                    ← NEW     │
│  ✅ Cache-Control response caching (selective)        ← NEW     │
│                                                                  │
└──────────────────────────────┬──────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────┐
│                         RAILWAY                                  │
│  (same services, now protected by Cloudflare security layer)     │
└─────────────────────────────────────────────────────────────────┘

What Was Explicitly Rejected (and Why)

Option Verdict Reasoning
Cloudflare Workers API Gateway Rejected NestJS monoliths with MongoDB can't decompose to edge functions. Workers have 10ms CPU limit (free) / 50ms (paid). Would create two runtimes to maintain and a debugging nightmare.
Cloudflare Tunnel Deferred Requires cloudflared daemon in Dockerfiles. Complicates branch-based Railway deployment. Marginal security gain at current scale. Revisit if handling sensitive data or under targeted attacks.
Cloudflare API Shield Deferred Enterprise-tier cost. Not justified until third-party API consumers emerge.

Scope: All 4 Backend APIs

This handoff applies to all backend services simultaneously:

Service Subdomain Railway Domain
backend-api api.gograb.space backend-api-production-97c0.up.railway.app
backend-ai-api ai-api.gograb.space backend-ai-api-production.up.railway.app
backend-docs-api docs-api.gograb.space backend-docs-api-production.up.railway.app
backend-vitto-api vitto-api.gograb.space TBD (pending Railway deployment)

Implementation Plan

Phase 1: Enable Bot Fight Mode (5 minutes)

Where: Cloudflare Dashboard > Security > Bots

Steps:

  1. Navigate to dash.cloudflare.com > gograb.space > Security > Bots
  2. Enable Bot Fight Mode (free tier toggle)
  3. This immediately starts challenging automated/bot traffic

Verification:

Risk: Minimal. Bot Fight Mode uses JavaScript challenges that legitimate browsers handle transparently. API clients sending proper headers are unaffected.


Phase 2: Enable WAF Managed Rules (30 minutes)

Where: Cloudflare Dashboard > Security > WAF

Steps:

  1. Navigate to Security > WAF > Managed rules
  2. Enable Cloudflare Managed Ruleset — blocks common attacks (SQLi, XSS, etc.)
  3. Enable Cloudflare OWASP Core Ruleset — broader OWASP Top 10 coverage
  4. Set action to Block for high-confidence rules, Log for medium-confidence initially

Important: Scoping to API subdomains only

Create a WAF custom rule that targets API traffic:

Expression: (http.host contains "api.gograb.space") or
            (http.host eq "ai-api.gograb.space") or
            (http.host eq "docs-api.gograb.space") or
            (http.host eq "vitto-api.gograb.space")

This ensures WAF rules only apply to API subdomains, not frontend Pages projects.

Free Tier Limitation: Free plan allows 5 custom WAF rules. If more are needed, Pro plan ($20/mo) provides managed rulesets.

Verification:

Risk: Low. Start with Log mode for medium-confidence rules. Block only high-confidence threats initially.


Phase 3: Configure Rate Limiting Rules (1 hour)

Where: Cloudflare Dashboard > Security > WAF > Rate limiting rules

Rule 1: Auth Endpoints (Strict)

Name: Auth Rate Limit
Expression: (http.request.uri.path contains "/auth/" or
             http.request.uri.path contains "/login" or
             http.request.uri.path contains "/register" or
             http.request.uri.path contains "/password")
            and
            (http.host contains "api.gograb.space" or
             http.host eq "ai-api.gograb.space" or
             http.host eq "vitto-api.gograb.space")
Rate: 10 requests per 1 minute
Counting: Per IP
Action: Block for 10 minutes
Response: 429 Too Many Requests

Rule 2: General API (Moderate)

Name: General API Rate Limit
Expression: (http.host contains "api.gograb.space") or
            (http.host eq "ai-api.gograb.space") or
            (http.host eq "docs-api.gograb.space") or
            (http.host eq "vitto-api.gograb.space")
Rate: 100 requests per 1 minute
Counting: Per IP
Action: Block for 5 minutes
Response: 429 Too Many Requests

Rule 3: Health Check Exclusion

Name: Health Check Bypass
Expression: http.request.uri.path contains "/health"
Action: Skip (no rate limiting)

IMPORTANT: Rule 3 must be ordered BEFORE Rules 1 and 2, so health checks from Railway monitoring and uptime services are never rate-limited.

Verification:

Risk: Moderate — rate limits need tuning based on actual traffic patterns. Start with these values and adjust after 1 week of monitoring. If legitimate users hit limits, increase thresholds.

Adjustment guidance:


Phase 4: Add Cache-Control Headers for Public Endpoints (2 hours — code changes)

Where: NestJS backend code

This is the only phase requiring code changes. Add Cache-Control headers to specific public endpoints so Cloudflare caches responses at the edge.

4a. Create a shared caching interceptor

File: libs/libs-api/backend-core/src/lib/interceptors/cache-control.interceptor.ts

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, SetMetadata } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
import { Reflector } from '@nestjs/core';

export const CACHE_TTL_KEY = 'cache-control-ttl';
export const CacheTTL = (seconds: number) => SetMetadata(CACHE_TTL_KEY, seconds);

@Injectable()
export class CacheControlInterceptor implements NestInterceptor {
  constructor(private readonly reflector: Reflector) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const ttl = this.reflector.get<number>(CACHE_TTL_KEY, context.getHandler());

    return next.handle().pipe(
      tap(() => {
        if (ttl && ttl > 0) {
          const response = context.switchToHttp().getResponse();
          response.setHeader('Cache-Control', `public, max-age=${ttl}, s-maxage=${ttl}`);
        }
      }),
    );
  }
}

4b. Apply to specific endpoints

Health endpoints — all backends:

@Get('health')
@CacheTTL(30)  // Cache for 30 seconds
getHealth() { ... }

Public docs content — backend-docs-api:

@Get('public/:slug')
@CacheTTL(300)  // Cache for 5 minutes
getPublicDoc() { ... }

Static config/metadata — backend-api:

@Get('config/public')
@CacheTTL(3600)  // Cache for 1 hour
getPublicConfig() { ... }

4c. Cloudflare cache rule (dashboard)

Create a Cache Rule in Cloudflare:

Name: Respect API Cache-Control Headers
Expression: (http.host contains "api.gograb.space") or
            (http.host eq "ai-api.gograb.space") or
            (http.host eq "docs-api.gograb.space") or
            (http.host eq "vitto-api.gograb.space")
Cache eligibility: Eligible for cache
Respect origin Cache-Control headers: Yes

By default, Cloudflare does NOT cache API responses (non-static content types). This rule tells it to respect the Cache-Control headers we set in code.

Verification:

Risk: Low if implemented correctly. Only endpoints explicitly decorated with @CacheTTL() will be cached. Authenticated endpoints remain dynamic.

4d. Export from backend-core barrel

File: libs/libs-api/backend-core/src/index.ts

Add export:

export { CacheControlInterceptor, CacheTTL, CACHE_TTL_KEY } from './lib/interceptors/cache-control.interceptor';

WebSocket Considerations (Vitto API)

The Vitto API uses WebSockets via mcp-gateway.gateway.ts. Cloudflare proxy mode (orange cloud) supports WebSockets, but:

Plan Concurrent WS Connections Notes
Free ~100 per domain Soft limit, may vary
Pro ($20/mo) Higher (undisclosed) Recommended if scaling
Enterprise Unlimited Not needed now

Action items:

  1. After Vitto goes live behind Cloudflare proxy, monitor concurrent WebSocket connections
  2. If connections are refused or dropped, check Cloudflare analytics for WS errors
  3. If limits hit, either upgrade to Pro or switch Vitto subdomain to DNS-only (grey cloud) as a quick fix — losing WAF/rate limiting for that subdomain only

Files That Will Be Modified

# File Action Phase
1 libs/libs-api/backend-core/src/lib/interceptors/cache-control.interceptor.ts Create Phase 4
2 libs/libs-api/backend-core/src/index.ts Modify — add export Phase 4
3 apis/backend-api/src/app/**/*.controller.ts Modify — add @CacheTTL decorators Phase 4
4 apis/backend-ai-api/src/app/**/*.controller.ts Modify — add @CacheTTL decorators Phase 4
5 apis/backend-docs-api/src/app/**/*.controller.ts Modify — add @CacheTTL decorators Phase 4
6 apis/backend-vitto-api/src/app/**/*.controller.ts Modify — add @CacheTTL decorators Phase 4

Phases 1-3 are dashboard-only — zero code changes.


Verification Checklist (Post-Implementation)

Security Verification

Endpoint Testing

Security Testing

Caching Testing (Phase 4 only)

Monitoring (24-48 hours post-deployment)


Rollback Plan

All changes are instantly reversible:

Issue Rollback
WAF blocking legitimate traffic Disable specific WAF rule or switch to Log mode
Rate limit too aggressive Increase threshold or disable rule
Bot Fight Mode causing issues Toggle off (Security > Bots)
Caching stale data Remove @CacheTTL decorator or set to 0
WebSocket issues on Vitto Switch vitto-api.gograb.space to DNS-only (grey cloud)

Cost Analysis

Item Current After Implementation
Cloudflare plan Free Free (or Pro $20/mo for managed WAF rulesets)
Railway costs Unchanged Slightly reduced (cached responses reduce Railway load)
Code maintenance None Minimal (CacheControlInterceptor is ~20 lines)

Recommendation: Start on Free tier. If WAF managed rulesets are desired beyond the 5 custom rules, upgrade to Pro ($20/mo) — still very cost-effective for the security it provides.


Decision Log References

These decisions are logged in MCP and can be retrieved with:

find_similar({ query: "Cloudflare API security WAF rate limiting" })
Decision ID Topic
dev_1770496457666_4mnfw5y Overall Cloudflare API security strategy
dev_1770496462813_ndb2agg Rate limiting tier design
dev_1770496469974_5f03l9l Rejected options (Workers, Tunnel, API Shield)
dev_1770496475908_jdt2ubq WebSocket considerations for Vitto

Created: Session 99 — February 7, 2026 Author: Cursor Agent (architecture analysis) Status: Planned — awaiting dedicated implementation session