A practical, opinionated checklist plus a verification script for keeping a UK/EU SaaS on EEA/UK-sovereign infrastructure. This is the exact methodology and script I run quarterly on production stacks.
Published under CC BY 4.0 so you can lift it, edit it, and use it without asking. Attribution is welcome but not required.
- Why this exists
- Used in production
- The five principles
- Using the script
- Reference implementation
- What the script checks
- Tailoring the script
- What this is not
- Maintainer
- License
If you run a UK or EU SaaS that processes personal data, three forces push you toward keeping that data in the EEA or the UK by default:
- UK GDPR compliance. An EEA/UK-only sub-processor mix removes the complexity of cross-border transfer mechanisms (UK IDTA, SCCs, adequacy decisions) and reduces the surface for compliance failures.
- Procurement. UK public-sector buyers — schools, councils, MATs, universities, NHS trusts, charities — increasingly require named EEA/UK-only data flows in supplier questionnaires. A sovereign posture wins more procurement responses.
- Optionality and resilience. US CLOUD Act, FISA 702, and evolving transfer-mechanism case law (Schrems II/III) create periodic regulatory surprises for US-hosted dependencies. Avoiding them is operationally simpler than managing them.
This repo is what I codify around those three forces.
The methodology behind the sovereignty posture of ticketwavehq.com, ukwebmarketing.com, and brothersylvester.com, whose public data-processors list reflects it.
Each is a rule, not an aspiration. The script in this repo verifies most of them mechanically; the rest are checklist items you confirm in admin dashboards.
Every sub-processor that processes identifying personal data must, by default, host that processing in the EEA or the UK. Any exception is justified in writing, listed on the public sub-processor disclosure page, and reviewed quarterly.
It is not enough that a vendor "supports" EU regions. Every vendor's specific region is explicitly configured, documented, and verifiable — via response trace headers, account settings screenshots, vendor admin console confirmations, or both. The script in this repo checks the configuration files; you check the admin consoles.
Examples of explicit region pinning the script verifies:
vercel.jsonincludes"regions": ["lhr1"](London) — pin the function execution region rather than relying on Vercel's default region selection.- Sentry config files reference
*.de.sentry.io(Frankfurt) — using the EU SaaS host, not the US default atsentry.io. - Supabase project URL is present (the project region you confirm in the Supabase dashboard — the URL itself does not encode region).
- CSP
connect-srcincludesingest.de.sentry.ioand does not include the US defaultingest.sentry.io.
A live sub-processor list is published at a stable URL (e.g.
/sub-processors or /data-processors). The list is updated within
7 days of any change. Material changes — new sub-processor
processing identifying data, region change, regulator change — are
notified to subscribers and posted on the public page 30 days before
they take effect.
Every quarter, the operator runs node verify-sovereignty.mjs and
pastes the output into the quarterly access review record. Drift is
identified and a remediation timeline is set. The annual review (every
January) verifies each active sub-processor against the previous
year's attestation freshness, region pinning, and continued mission
alignment.
Two areas are typically acceptable exceptions in practice:
- Edge/CDN providers (e.g. Cloudflare). Personal data is not persisted at the edge — only TLS metadata and WAF challenge events transit. Region-agnostic by nature; documented under the principle rather than replaced.
- Payment processors with EU entities (e.g. Stripe Payments Europe Ltd, Adyen). Operational data may transit to a US parent under SCCs for fraud-prevention, customer-support, and platform-engineering purposes. The data exposed is transactional metadata, not learner or customer content at scale. SOC 2 Type II attestation satisfies the exception threshold.
Document the exceptions in writing. Show them to institutional buyers who ask. Do not weaponise sovereignty as a marketing wedge — it is shown, not sold.
# Drop the script into your project
cp verify-sovereignty.mjs /path/to/your-project/scripts/
# Edit the SOVEREIGNTY_CONFIG block at the top of the script to match
# your stack (file paths, expected region, env var names)
# Run it
node scripts/verify-sovereignty.mjsExit code is 0 if every check passes and non-zero otherwise, so you
can wire it into CI:
# .github/workflows/sovereignty.yml
name: Sovereignty
on:
pull_request:
schedule:
- cron: '0 9 1 */3 *' # quarterly: 09:00 on the 1st of every 3rd month
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: CI=1 node scripts/verify-sovereignty.mjsCI mode (CI=1) skips checks that read .env.local, since that file is
expected to be absent on CI runners.
For machine-readable output (e.g. piping to a dashboard), set
SOVEREIGNTY_JSON=1:
SOVEREIGNTY_JSON=1 node verify-sovereignty.mjsSee examples/ for sample JSON output showing what a
fully-passing run looks like.
See how site-templates deploys with these constraints.
| # | Check | Principle |
|---|---|---|
| 1 | Vercel vercel.json pins regions: ["lhr1"] (or your chosen EU region) |
2 |
| 2 | All Sentry config files reference *.de.sentry.io |
2 |
| 3 | Supabase project URL is set (reminder to confirm region in dashboard) | 2 |
| 4 | Public sub-processor disclosure page mentions sovereignty posture | 3 |
| 5 | Privacy page is current — names current auth provider, no stale residue | 3 |
| 6 | SECURITY.md (vulnerability disclosure policy) present |
3 |
| 7 | GDPR Article 20 — data portability endpoint exists | — |
| 8 | GDPR Article 17 — account deletion endpoint exists | — |
| 9 | CSP connect-src targets Sentry EU host only |
2 |
| 10 | X-Frame-Options: DENY is set |
— |
The script is intentionally focused on infrastructure configuration
that affects sovereignty — not the broader compliance posture (DPIAs,
DSAR logging, etc.). For the wider checklist see CHECKLIST.md.
The script's SOVEREIGNTY_CONFIG block at the top is intended to be
edited. Common adjustments:
- Change
vercelRegionto your EU region (lhr1London,fra1Frankfurt,dub1Dublin,cdg1Paris,arn1Stockholm). - Change
sentryEuHostif you self-host Sentry in the EU. - Change
supabaseExpectedRegionto your project's region (eu-west-2London,eu-central-1Frankfurt,eu-west-1Ireland). - Change
privacyMustMention/privacyMustNotMentionto flag any vendor you have migrated away from (e.g. ensure the privacy page no longer mentions a deprecated auth provider). - Add new checks following the same pattern — each is a
try/catcharound a file read with arecord(...)call.
- Not legal advice. Use this as engineering hygiene that supports legal compliance. Run material changes past counsel.
- Not a competitive wedge. Sovereignty is shown, not weaponised in marketing copy.
- Not a perpetual escalation. The principles set a default; new gaps are evaluated against the principles, not ratcheted further than the principles require.
Maintained by Jordan Gilbert (@sansware) — the engineer who codified this checklist while running it quarterly on production UK SaaS stacks. More at ukwebmarketing.com/jordan.
Tag @sansware on PRs and issues for review.
For security issues, see SECURITY.md. For contribution
guidelines, see CONTRIBUTING.md.
By Jordan Gilbert.
Creative Commons Attribution 4.0 International (CC BY 4.0).
You may copy, adapt, and use commercially. Attribution to this repo is appreciated but not required.