> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pensionsportal.ie/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloudflare Configuration Guide

> DNS, WAF, security, and performance configuration for PensionsPortal.ie

## Overview

PensionsPortal.ie uses Cloudflare as its CDN, WAF, and DNS layer in front of Vercel. All traffic to `*.pensionsportal.ie` passes through Cloudflare before reaching Vercel's edge network. This provides DDoS protection, web application firewall rules, rate limiting, and TLS termination.

***

## DNS Records

Configure the following records in Cloudflare Dashboard → DNS → Records for `pensionsportal.ie`.

| Type    | Name                | Value                                                              | Proxy Status | Purpose                |
| ------- | ------------------- | ------------------------------------------------------------------ | ------------ | ---------------------- |
| `CNAME` | `www`               | `cname.vercel-dns.com`                                             | Proxied      | Marketing site         |
| `CNAME` | `app`               | `cname.vercel-dns.com`                                             | Proxied      | Application portal     |
| `CNAME` | `docs`              | `cname.vercel-dns.com`                                             | Proxied      | Mintlify documentation |
| `CNAME` | `*`                 | `cname.vercel-dns.com`                                             | Proxied      | Wildcard coverage      |
| `TXT`   | `@`                 | `v=spf1 include:_spf.resend.com ~all`                              | DNS only     | Email SPF (Resend)     |
| `TXT`   | `resend._domainkey` | DKIM value from Resend Dashboard                                   | DNS only     | Email DKIM             |
| `TXT`   | `_dmarc`            | `v=DMARC1; p=quarantine; rua=mailto:dmarc@pensionsportal.ie`       | DNS only     | DMARC policy           |
| `MX`    | `@`                 | `mx1.improvmx.com` (priority 10), `mx2.improvmx.com` (priority 20) | DNS only     | Email routing          |

<Note>
  Email records (SPF, DKIM, DMARC, MX) must be set to **DNS only** (grey cloud). Proxying these record types through Cloudflare would break email delivery and DKIM signature validation.
</Note>

<Warning>
  After adding or modifying DKIM and DMARC records, allow up to 48 hours for DNS propagation before running DMARC verification. Validate with `dig TXT _dmarc.pensionsportal.ie` and a DMARC checker tool.
</Warning>

***

## TLS Configuration

Configure in Cloudflare Dashboard → SSL/TLS.

| Setting             | Value                                              | Notes                                                                  |
| ------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
| SSL Mode            | **Full (Strict)**                                  | Vercel certificates are verified end-to-end; rejects self-signed certs |
| Minimum TLS Version | **TLS 1.2** (minimum)                              | Recommend **TLS 1.3 only** for DORA compliance                         |
| HSTS                | Enabled                                            | `max-age=31536000; includeSubDomains; preload`                         |
| Certificate         | Cloudflare Universal SSL + Vercel Edge Certificate | Both auto-renewed                                                      |

<Warning>
  **SSL Mode must be Full (Strict), never Flexible.** Flexible mode encrypts only the Cloudflare-to-browser connection, leaving the Cloudflare-to-Vercel leg unencrypted. This would violate GDPR Article 32 and DORA technical security requirements for a regulated financial application.
</Warning>

### HSTS Configuration

Enable HSTS in Cloudflare Dashboard → SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS):

```
Max Age: 12 months (31536000 seconds)
Include Subdomains: ON
Preload: ON
No-Sniff Header: ON
```

<Warning>
  Once HSTS with `preload` is submitted to the HSTS preload list, it is very difficult to reverse. Ensure all subdomains serve valid HTTPS before enabling preload. Confirm with the team before enabling.
</Warning>

***

## WAF Configuration

Configure in Cloudflare Dashboard → Security → WAF.

### Managed Rulesets

Enable the following managed rulesets under WAF → Managed Rules:

| Ruleset                              | Status | Notes                                     |
| ------------------------------------ | ------ | ----------------------------------------- |
| Cloudflare Managed Ruleset           | ON     | Covers common web exploits                |
| Cloudflare OWASP Core Ruleset        | ON     | OWASP Top 10 protections                  |
| Cloudflare Exposed Credentials Check | ON     | Detects use of known breached credentials |

### Custom WAF Rules

Create the following rules in Security → WAF → Custom Rules:

**Rule 1 — Geo-restrict regulated data endpoints**

This rule challenges non-EU/non-Irish traffic attempting to access regulated pension data APIs. PensionsPortal.ie operates under IORP II, which is an Irish/EU regulatory framework.

```
(ip.geoip.continent ne "EU" and ip.geoip.country ne "IE"
 and http.request.uri.path matches "^/api/(schemes|members|employers|compliance).*")

Action: Challenge (Managed CAPTCHA)
```

<Note>
  Disable or modify this rule if expanding to non-EU markets (Wave 2). Ensure legal review of data residency obligations before disabling geo-restriction for any regulated endpoint.
</Note>

**Rule 2 — Protect RAG ingest endpoint**

The RAG ingest endpoint triggers AI document processing and is not intended for public access. Block all traffic except from known infrastructure IPs.

```
(http.request.uri.path matches "^/api/rag/ingest.*")

Action: Block (allowlist known IPs via IP Lists)
```

Create an IP List in Cloudflare → Manage Account → Configurations → Lists, and reference it in the rule to permit your CI/admin IPs.

**Rule 3 — Block common exploit patterns**

```
(http.request.uri.query contains "../"
 or http.request.uri.query contains "UNION SELECT")

Action: Block
```

<Warning>
  These custom WAF rules supplement, but do not replace, secure server-side input validation. The application must validate and sanitize all user input regardless of WAF rules — WAF rules can be bypassed by sophisticated attackers and should be treated as defence-in-depth.
</Warning>

***

## Rate Limiting Rules

Configure in Cloudflare Dashboard → Security → WAF → Rate Limiting Rules.

| Rule Name              | Path Pattern                     | Threshold    | Period   | Action               |
| ---------------------- | -------------------------------- | ------------ | -------- | -------------------- |
| Login Rate Limit       | `/api/auth/callback/credentials` | 10 requests  | 1 minute | Block for 10 minutes |
| AI Chat Rate Limit     | `/api/ai/chat`                   | 30 requests  | 1 minute | Throttle             |
| API General Rate Limit | `/api/*`                         | 500 requests | 1 minute | Throttle             |
| RAG Ingest Protection  | `/api/rag/ingest`                | 5 requests   | 1 hour   | Block                |

<Note>
  The login rate limit (10 req/min) is intentionally strict to prevent credential stuffing attacks against trustee accounts. Legitimate users should not approach this threshold during normal use. If users report false positives, investigate before raising the limit.
</Note>

***

## Bot Protection

Configure in Cloudflare Dashboard → Security → Bots.

| Setting                     | Value                              | Notes                                     |
| --------------------------- | ---------------------------------- | ----------------------------------------- |
| Bot Fight Mode              | ON                                 | Blocks known bad bots                     |
| Super Bot Fight Mode (Pro+) | Block definitely automated traffic | Requires Pro plan or higher               |
| Browser Integrity Check     | ON for `/dashboard/*` paths        | Validates browser fingerprint             |
| Challenge Passage           | 30 minutes                         | Time before re-challenging solved CAPTCHA |

<Note>
  Super Bot Fight Mode may block legitimate API clients such as monitoring services (e.g., BetterUptime). Allowlist known monitoring IPs or user-agent strings in the Cloudflare firewall before enabling.
</Note>

***

## Zero Trust Configuration Plan

Cloudflare Zero Trust (formerly Cloudflare Access) provides identity-aware access control for sensitive admin paths, replacing IP-allowlisting with company identity authentication.

**Target state (future hardening):**

1. **Create a Cloudflare Access application** scoped to `app.pensionsportal.ie/admin/*`
2. **Require authentication** via Cloudflare Access + company email (Google Workspace or Azure AD IdP)
3. **Enable device posture checks** — allow access only from managed, MDM-enrolled devices
4. **Log all access events** to Cloudflare SIEM / Logpush (forward to your SIEM or audit log store)
5. **Conduct weekly access reviews** — security lead reviews active users and revokes stale access

<Note>
  Zero Trust configuration requires a Cloudflare Zero Trust plan (free tier available for small teams). Review [Cloudflare Access documentation](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/) before implementing.
</Note>

***

## Admin Route Protection — Current State

Until Zero Trust is fully configured, the following interim controls apply:

* **No dedicated `/admin/*` URL path exists.** SuperAdmin capabilities are not exposed via a separate URL prefix.
* **Admin users are identified by role in the JWT**, not by URL path. The `SuperAdmin` role is stored in the database and reflected in the session token.
* **All `/api/*` routes perform server-side role checks** using the session JWT before executing privileged operations.

<Warning>
  Role-based access in the JWT is only as secure as the `AUTH_SECRET` used to sign tokens. Ensure `AUTH_SECRET` is unique per environment, never committed to source control, and rotated if ever exposed. See the [Vercel Deployment Guide](/deployment/vercel) for secret generation instructions.
</Warning>

**Recommended next step:** Implement a dedicated `/admin` route tree protected by both JWT role check and Cloudflare Access, providing defence-in-depth for the highest-privilege operations.

***

## Performance Settings

Configure in Cloudflare Dashboard → Speed → Optimization and Caching.

| Setting                  | Value   | Notes                                                   |
| ------------------------ | ------- | ------------------------------------------------------- |
| Auto Minify — HTML       | ON      | Reduces HTML payload size                               |
| Auto Minify — CSS        | ON      | Reduces CSS payload size                                |
| Auto Minify — JavaScript | ON      | Reduces JS payload size                                 |
| Brotli Compression       | ON      | More efficient than gzip for modern browsers            |
| HTTP/3 with QUIC         | ON      | Reduces latency on unreliable connections               |
| Rocket Loader            | **OFF** | Incompatible with Next.js hydration — **do not enable** |

### Cache Rules

Create a Cache Rule in Cloudflare Dashboard → Caching → Cache Rules:

**Rule: Cache Next.js static assets at edge**

```
If URI path matches: ^/_next/static/.*
Cache TTL: 1 year (31536000 seconds)
Browser TTL: 1 year
Edge TTL: 1 year
Cache Status: Cache Everything
```

Next.js content-hashes all files in `/_next/static/`, making long-lived caching safe — stale files are never served because filenames change with each build.

<Note>
  Do not cache `/api/*` paths or `/dashboard/*` paths at the Cloudflare edge. These routes return user-specific or real-time data. Vercel handles caching for these routes via the Next.js cache headers.
</Note>

***

## Analytics and Monitoring

| Feature                            | Target                           | Notes                                                  |
| ---------------------------------- | -------------------------------- | ------------------------------------------------------ |
| Cloudflare Analytics               | All zones                        | Traffic, bandwidth, threat insights                    |
| Web Analytics (privacy-preserving) | `www.pensionsportal.ie`          | No cookies, GDPR-compliant, no consent banner required |
| Security Events                    | Review weekly                    | Check for blocked requests, unusual geo patterns       |
| Under Attack Mode                  | Activate during active DDoS only | JS challenge for all visitors; disables normal access  |

**Web Analytics setup:**

Add the Cloudflare Web Analytics script to the marketing site (`www.pensionsportal.ie`). This script does not use cookies and does not require a GDPR consent banner, making it suitable for a regulated financial platform.

<Warning>
  **Under Attack Mode should only be activated during an active, confirmed DDoS event.** Enabling it outside of an incident will challenge all legitimate users — including pension trustees — with a browser integrity check, causing significant friction. Coordinate activation with the security lead and document the decision in the incident log.
</Warning>

**Security Events review checklist (weekly):**

* Check for any WAF rule triggers on `/api/auth/*` — spikes may indicate credential stuffing
* Review blocked geo-restriction hits on regulated endpoints
* Check rate limit trigger counts — a significant increase may indicate automated probing
* Review any manual `Block` actions and confirm they remain appropriate
