Skip to content

15 — Security and Data Protection

PRD Document · Savoy Signature Hotels — Multi-Site Headless Platform
Version: 1.0 · Date: 2026-03-04
Related docs: 02_Infrastructure_and_Environments.md, 08_API_Contracts.md, 12_Forms_and_Data_Collection.md


This document outlines the security architecture for the Savoy Signature headless platform. It defines boundary protections, environment variables management, API security, automated scanning, and GDPR compliance.


The headless architecture naturally mitigates many traditional CMS vulnerabilities by removing direct public access to the CMS database and backend code. However, new attack vectors (API abuse, unauthorized data exfiltration) must be secured.

Cloudflare is the first line of defense for both the Next.js frontend and the Umbraco backend.

Protection LayerConfigurationPurpose
WAF (Web Application Firewall)Cloudflare Managed Ruleset (Strict)Blocks SQLi, XSS, and known CMS exploits before they reach Azure.
Bot ManagementSuper Bot Fight Mode (Enabled)Challenges or blocks automated scrapers, form-fill bots, and vulnerability scanners.
Rate Limiting5 req/5min per IP on /api/forms/*; 20 req/min per IP on /api/searchPrevents denial-of-service and brute-force attacks on custom endpoints.
DDoS ProtectionUnmetered L3/L4/L7 mitigationKeeps the site online during volumetric attacks.
SSL/TLSFull (Strict)Enforces end-to-end encryption. Cloudflare issues edge certificates; Azure holds origin certificates.

The CMS is the ultimate source of truth and must be heavily insulated.

  • The Umbraco backoffice (/umbraco) is not publicly accessible.
  • Cloudflare Zero Trust (Access) or Azure IP Restrictions lock down the backoffice URL to specific corporate IP addresses (e.g., WYcreative office, Savoy HR/Marketing VPNs).
  • The Content Delivery API (/umbraco/delivery/api/v2) is only accessible via the Next.js frontend origin.

3.2 Dev Auth Gate (Non-Production Environments)

Section titled “3.2 Dev Auth Gate (Non-Production Environments)”

All non-production environments (DEV, Stage, QA) are protected by an authentication gate that blocks public access. Users must log in with Umbraco backoffice credentials before viewing any content.

FeatureImplementation
Auth MethodUmbraco backoffice user credentials (email + password)
TokenJWT (__dev_gate cookie), signed with HMAC-SHA256
TTL7 days
Cookie Scope.wycreative.com (shared across all DEV subdomains)
Next.jsMiddleware checks cookie; redirects to /gate/login if invalid
StorybookCloudflare Pages Function (_middleware.js) checks same cookie
Umbraco EndpointPOST /api/auth/validate-backoffice (validates credentials, protected by X-Gate-Key header)
ProductionGate disabled (GATE_ENABLED=false) — sites are public
Exemptions/api/health, /api/webhooks/*, /_next/static/*

Spec: docs/superpowers/specs/2026-03-19-dev-auth-gate-design.md

  • Identity Provider: Umbraco uses Azure Active Directory (Microsoft Entra ID) via OpenID Connect. Local CMS accounts are disabled for all users except emergency admins.
  • MFA: Multi-Factor Authentication is enforced at the Azure AD level for all editors.
  • Role-Based Access Control (RBAC): Editors are restricted to specific hotel nodes. For example, a “Savoy Palace Editor” cannot edit “Royal Savoy” content.
  • Automated Scanning: GitHub Dependabot is enabled on the apps/cms repository to scan NuGet packages for CVEs.
  • Patch Management: The CI/CD pipeline fails if high-severity vulnerabilities are detected during the dotnet restore phase.

The Node.js/React layer requires strict data handling and header configurations.

The following headers are injected via next.config.ts:

// next.config.ts headers configuration
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN' // Prevents clickjacking
},
{
key: 'X-Content-Type-Options',
value: 'nosniff' // Prevents MIME-type sniffing
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
// Content Security Policy (CSP)
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com https://www.googletagmanager.com https://*.recaptcha.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.blob.core.windows.net https://savoysignature.com/cdn-cgi/image/; font-src 'self' data:; connect-src 'self' https://api.navarino.co https://*.google-analytics.com https://connect.facebook.net https://consent.cookiebot.com;"
}
];

Environment variables and connection strings are never committed to version control.

EnvironmentSecrets EngineInjection Method
Local.env.local (git-ignored)Next.js CLI / dotnet run
CI/CDGitHub Actions SecretsPassed to build context
Azure (All)Azure Key VaultApp Services fetch secrets via Managed Identity at runtime.
  • Tokens required by the browser (e.g., NEXT_PUBLIC_RECAPTCHA_SITE_KEY) are explicitly prefixed.
  • API keys for Umbraco (UMBRACO_API_KEY), Cloudflare (CLOUDFLARE_API_TOKEN), and Mailjet are restricted to the Node.js context and never exposed to the client bundle.

As defined in 08_API_Contracts.md, webhooks from Umbraco to Next.js (for cache purging) are secured via a shared secret.

  1. Umbraco hashes the payload with the REVALIDATE_SECRET and sends an X-Webhook-Signature header.
  2. Next.js computes the HMAC SHA-256 signature of the received payload using the same secret.
  3. If signatures match, the payload is trusted. Furthermore, the webhook endpoint enforces a strict IP allowlist (accepting requests only from the Umbraco Azure App Service IP).

Savoy Signature processes PII (Personally Identifiable Information) via forms and analytics.

  • At Rest: The Azure SQL database (Umbraco content/forms) and Azure Blob Storage (media/uploads) utilize TDE (Transparent Data Encryption) with Microsoft-managed keys.
  • In Transit: All traffic is mandated over TLS 1.3 (fallback 1.2).
  • Form Submissions: Stored in Umbraco Forms to handle email failure retries. Auto-deleted via a scheduled task after 90 days (configurable per form).
  • Log Data: Azure Application Insights retains application logs for 90 days. Log payloads are stripped of request bodies (e.g., passwords, credit card info).

The platform utilizes Cookiebot.com as the official Consent Management Platform (CMP).

  • Implementation: The Cookiebot script (<script id="Cookiebot" ...>) is injected into the &lt;head&gt; of the layout.tsx file for all 8 sites.
  • Auto-blocking: Cookiebot’s automatic cookie blocking prevents GTM, Navarino tracking, and other third-party scripts from executing before explicit user consent is granted.
  • Exemptions: Essential cookies (e.g., Next.js locale routing, CSRF tokens, Cloudflare protection tokens) are classified strictly non-tracking and exempt from blocking.
  • Alerts/Audits: The DPO receives monthly automated scan reports from Cookiebot. Unclassified cookies trigger an alert requiring manual categorization by the dev team.

  • Cloudflare WAF is blocking known exploits across all domains.
  • Umbraco backoffice is inaccessible from unauthorized IP addresses.
  • Next.js HTTP response headers achieve an “A” grade on SecurityHeaders.com.
  • No secrets or API keys are present in the frontend bundle (verified via bundle analysis).
  • Webhook cache purge endpoints require a valid cryptographic signature.
  • Form payloads containing PII are encrypted at rest and auto-erased after 90 days.
  • Azure Key Vault is successfully utilized for all production secrets via Managed Identity.

Next document: 16_Analytics_and_Tracking.md