Files
cv-site/doc/API-PROTECTION.md
T
juanatsap ea6ccf9cdd feat: add origin validation and rate limiting for PDF endpoint
Security enhancements:
- Implement origin/referer validation middleware
- Add rate limiting (3 requests/min per IP)
- Default to production domain (juan.andres.morenorub.io)
- Verify all protection mechanisms working correctly

Documentation updates:
- Update README to reflect personal portfolio nature
- Remove template encouragement from README
- Add verification status to API-PROTECTION.md
- Document ALLOWED_ORIGINS configuration in .env.example

Cleanup:
- Remove templates.backup/ folder
- Remove old test screenshots
2025-11-09 14:13:22 +00:00

12 KiB

API Protection & Security

Protection against external access and DDoS attacks on resource-intensive endpoints.


VERIFICATION STATUS

Last Tested: November 9, 2025 Status: ALL PROTECTION MECHANISMS VERIFIED WORKING

Verified Test Results

Test Expected Actual Status
External referer (evil.com) 403 Forbidden 403 Forbidden PASS
Localhost referer 200 OK 200 OK PASS
Production domain referer 200 OK 200 OK PASS
External Origin header 403 Forbidden 403 Forbidden PASS
No referer (development) 200 OK 200 OK PASS
Rate limit (requests 1-3) 200 OK 200 OK PASS
Rate limit (request 4+) 429 Too Many 429 Too Many PASS

Protection Layers: Origin checking + Rate limiting both working correctly.


Overview

The CV website implements multiple layers of protection to prevent external sites from accessing the API and to protect against DDoS attacks on resource-intensive endpoints like PDF generation.

Protection Layers

  1. Origin Checking - Only allows requests from your domain
  2. Rate Limiting - Prevents abuse even from allowed origins
  3. Production Restrictions - Stricter rules in production environments

1. Origin Checking

File: internal/middleware/security.go (OriginChecker middleware)

How It Works

The origin checker examines incoming HTTP requests and validates them against a whitelist of allowed domains. It checks two headers:

  1. Origin Header - Set by browsers for CORS requests
  2. Referer Header - Set by browsers for navigation requests

Configuration

Environment Variable: ALLOWED_ORIGINS

# Development (default)
ALLOWED_ORIGINS=

# Production
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com

Behavior

Environment No Header Localhost Your Domain External Domain
Development Allowed Allowed Allowed Blocked
Production Blocked (PDF) Allowed Allowed Blocked

Production PDF Endpoint:

  • Requires Origin or Referer header
  • Blocks direct URL access without headers
  • Prevents bookmarking and external hotlinking

Example Responses

Allowed Request:

curl -H "Referer: https://yourdomain.com/" http://localhost:1999/export/pdf?lang=en
# Status: 200 OK
# PDF file downloaded

Blocked Request (External Domain):

curl -H "Referer: https://externaldomain.com/" http://localhost:1999/export/pdf?lang=en
# Status: 403 Forbidden
# Response: Forbidden: External access not allowed

Blocked Request (Production, No Headers):

# In production with GO_ENV=production
curl http://yourdomain.com/export/pdf?lang=en
# Status: 403 Forbidden
# Response: Forbidden: Direct access not allowed

2. Rate Limiting

File: internal/middleware/security.go (RateLimiter)

How It Works

The rate limiter tracks requests per IP address and enforces limits on resource-intensive endpoints.

Current Configuration:

  • Limit: 3 requests per minute per IP
  • Window: 1 minute
  • Applied to: /export/pdf endpoint only

Implementation

// Create rate limiter for PDF endpoint
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)

// Apply to PDF endpoint
protectedPDFHandler := middleware.OriginChecker(
    pdfRateLimiter.Middleware(
        http.HandlerFunc(cvHandler.ExportPDF),
    ),
)

Behavior

Requests Status Response
1st request 200 OK PDF generated
2nd request 200 OK PDF generated
3rd request 200 OK PDF generated
4th request (within 1 min) 429 Too Many Requests Rate limit exceeded
After 1 minute 200 OK Counter reset

Headers

Rate Limit Exceeded Response:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: text/plain; charset=utf-8

Rate limit exceeded. Please try again later.

IP Detection

The rate limiter detects client IP from:

  1. X-Forwarded-For header (proxy/CDN)
  2. X-Real-IP header (alternative proxy header)
  3. RemoteAddr (direct connection)

Supports reverse proxies: Yes (Nginx, Cloudflare, etc.)


3. Combined Protection

The PDF endpoint has both origin checking and rate limiting applied:

Request → OriginChecker → RateLimiter → PDF Handler

Protection Flow:

  1. Check Origin/Referer

    • If external domain → 403 Forbidden
    • If production + no headers → 403 Forbidden
    • Otherwise, continue
  2. Check Rate Limit

    • If > 3 requests/minute → 429 Too Many Requests
    • Otherwise, continue
  3. Generate PDF

    • Process request normally

4. Configuration Examples

Development Environment

# .env
PORT=1999
HOST=localhost
GO_ENV=development
ALLOWED_ORIGINS=

Behavior:

  • Allows localhost and 127.0.0.1
  • Allows requests without headers
  • Rate limit: 3 PDF/min per IP

Production Environment

# .env
PORT=1999
HOST=0.0.0.0
GO_ENV=production
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com

Behavior:

  • Only allows yourdomain.com and www.yourdomain.com
  • Requires Origin or Referer header for PDF endpoint
  • Rate limit: 3 PDF/min per IP

Multiple Domains

# Support multiple domains (e.g., staging + production)
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com,staging.yourdomain.com

5. Testing Protection

Test Origin Checking

# ✅ Allowed (localhost in development)
curl http://localhost:1999/export/pdf?lang=en

# ✅ Allowed (with referer)
curl -H "Referer: http://localhost:1999/" http://localhost:1999/export/pdf?lang=en

# ❌ Blocked (external referer)
curl -H "Referer: https://evil.com/" http://localhost:1999/export/pdf?lang=en
# Expected: 403 Forbidden

Test Rate Limiting

# Generate 4 PDFs quickly
for i in {1..4}; do
  echo "Request $i:"
  curl -w "Status: %{http_code}\n" -o /dev/null -s http://localhost:1999/export/pdf?lang=en
  sleep 1
done

# Expected output:
# Request 1: Status: 200
# Request 2: Status: 200
# Request 3: Status: 200
# Request 4: Status: 429

Test Combined Protection

# Should be blocked by origin checker before rate limiter
for i in {1..5}; do
  curl -H "Referer: https://evil.com/" -w "Status: %{http_code}\n" -o /dev/null -s \
    http://localhost:1999/export/pdf?lang=en
done

# Expected: All requests get 403 (origin check fails immediately)

6. Monitoring & Logs

Log Messages

Origin Check Failure:

# No specific log (returns 403 silently)
# Check server logs for 403 responses

Rate Limit Exceeded:

# No specific log (returns 429 silently)
# Monitor for frequent 429 responses
  1. Track 403 responses - Indicates potential attack attempts
  2. Track 429 responses - Indicates rate limiting in effect
  3. Monitor PDF generation times - Detect abuse patterns
  4. Alert on sustained high request rates - DDoS detection

7. Customization

Adjust Rate Limits

File: main.go

// Current: 3 requests per minute
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)

// More restrictive: 5 per hour
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)

// Less restrictive: 10 per minute
pdfRateLimiter := middleware.NewRateLimiter(10, 1*time.Minute)

Apply to Other Endpoints

// Protect /cv endpoint
protectedCVHandler := middleware.OriginChecker(
    http.HandlerFunc(cvHandler.CVContent),
)
mux.Handle("/cv", protectedCVHandler)
// Apply only rate limiting (no origin check)
mux.Handle("/export/pdf", pdfRateLimiter.Middleware(
    http.HandlerFunc(cvHandler.ExportPDF),
))

8. Security Best Practices

  1. Set ALLOWED_ORIGINS in production - Never run production without it
  2. Use HTTPS - Prevents header spoofing
  3. Monitor 403/429 responses - Detect attack patterns
  4. Consider CloudFlare - Additional DDoS protection layer
  5. Log suspicious requests - For forensic analysis

Anti-Patterns

  1. Don't disable protection in production - Always use origin checking
  2. Don't set rate limits too high - PDF generation is expensive
  3. Don't trust IP addresses alone - Use combined protection
  4. Don't expose internal endpoints - Keep admin routes private

9. Production Deployment Checklist

Before deploying to production:

  • Set GO_ENV=production in environment
  • Configure ALLOWED_ORIGINS with your domain(s)
  • Test origin checking with external domain
  • Test rate limiting with rapid requests
  • Verify HTTPS is enabled (prevents header spoofing)
  • Set up monitoring for 403/429 responses
  • Configure log retention for security analysis
  • Test PDF generation under load
  • Verify reverse proxy headers (X-Forwarded-For)
  • Document allowed origins in runbook

10. Troubleshooting

Problem: Legitimate users getting 403

Cause: ALLOWED_ORIGINS not configured correctly

Solution:

# Ensure all your domains are listed
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com

# Check for typos (case-insensitive but must match exactly)

Problem: Rate limit too restrictive

Cause: Legitimate users hitting limit

Solution:

// Increase limit or window in main.go
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Minute)

Problem: Behind reverse proxy, rate limit not working

Cause: IP detection failing

Solution:

# Ensure Nginx passes correct headers
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;

Problem: Origin header not being sent

Cause: Browser doesn't send Origin for same-origin requests

Solution: This is normal. The middleware checks Referer as fallback.


11. Attack Scenarios & Mitigation

Scenario 1: DDoS via PDF Generation

Attack: External site hotlinks to /export/pdf, triggering many PDF generations

Mitigation:

  1. Origin checker blocks external domains (403)
  2. Rate limiter prevents >3 requests/min per IP (429)
  3. Production mode requires headers (blocks direct access)

Result: Attack fails, server protected

Scenario 2: Header Spoofing

Attack: Attacker spoofs Referer header to bypass origin check

Mitigation:

  1. ⚠️ HTTPS prevents header modification in transit
  2. Rate limiter still applies (3 req/min limit)
  3. IP-based tracking prevents distributed spoofing

Result: Individual attacker limited to 3 req/min

Scenario 3: Distributed Attack

Attack: Botnet with many IPs, each generating PDFs

Mitigation:

  1. Each IP limited to 3 req/min
  2. Origin checker blocks if no valid referer
  3. 🔴 Consider CloudFlare for large-scale DDoS

Result: Slowed but not fully blocked (add CloudFlare)


Summary

Protection Enabled: Origin Checking + Rate Limiting

Endpoints Protected:

  • /export/pdf - Full protection (origin + rate limit)

Endpoints Unprotected:

  • / - Public home page
  • /cv - Public CV content
  • /health - Public health check
  • /static/* - Public static files

Configuration: Environment-based via ALLOWED_ORIGINS

Production Ready: Yes (after setting ALLOWED_ORIGINS)


For questions or to adjust protection levels, modify:

  • internal/middleware/security.go - Origin checking and rate limiting logic
  • main.go - Apply protection to additional endpoints
  • .env - Configure ALLOWED_ORIGINS for your domain