Security Deep-Dive: SSRF Prevention in the OFSI Nightly CSV Sync
Security Deep-Dive: SSRF Prevention in the OFSI Nightly CSV Sync
Release: v0.1.52
Control: SEC-10
OWASP Category: Server-Side Request Forgery (SSRF)
Affected file: src/app/api/sanctions/nightly/route.ts
Background
The sanctions platform performs a nightly sync against the UK Office of Financial Sanctions Implementation (OFSI) consolidated list. As part of this process, the discoverLatestCSVUrl() function fetches the gov.uk OFSI publications page and uses a regular expression to extract the most recent CSV download URL dynamically — avoiding hardcoded links that can go stale as OFSI publishes updated lists.
The Vulnerability
Prior to v0.1.52, the URL returned by discoverLatestCSVUrl() was passed directly into a subsequent fetch() call without any validation:
// Before — no domain validation
const csvUrl = await discoverLatestCSVUrl();
const response = await fetch(csvUrl); // ⚠️ SSRF risk
This created a Server-Side Request Forgery (SSRF) vulnerability. Two realistic attack scenarios existed:
- Compromised upstream page — If the gov.uk OFSI page were defaced or its content delivery network tampered with, a malicious
hrefcould be injected into the page HTML, causing the platform's server to fetch from an attacker-controlled endpoint. - Regex matching an unexpected href — A broad regex could inadvertently match an unrelated link on the page pointing to an internal network address (e.g.
http://169.254.169.254/for cloud metadata endpoints), leaking environment secrets or internal service responses.
The Fix
A domain allowlist check is now applied immediately after the URL is discovered, before any network request is made:
const ALLOWED_CSV_ORIGINS = [
'https://assets.publishing.service.gov.uk/',
'https://ofsistorage.blob.core.windows.net/',
];
const csvUrl = await discoverLatestCSVUrl();
const isAllowed = ALLOWED_CSV_ORIGINS.some((origin) => csvUrl.startsWith(origin));
if (!isAllowed) {
// Fall through to known hardcoded fallback URLs
console.warn(`Discovered CSV URL failed allowlist validation: ${csvUrl}`);
return useFallbackCsvUrls();
}
const response = await fetch(csvUrl); // ✅ Safe — origin validated
Why these two origins?
| Origin | Purpose |
|---|---|
https://assets.publishing.service.gov.uk/ | Primary CDN for UK government publications, used by the OFSI consolidated list |
https://ofsistorage.blob.core.windows.net/ | Azure Blob Storage endpoint used by OFSI as a secondary/legacy distribution URL |
Both origins use HTTPS, meaning transport-layer integrity is enforced in addition to the domain check.
Graceful degradation
Rather than throwing an error and halting the nightly sync entirely, a failed allowlist check falls through to the platform's hardcoded fallback CSV URLs. This ensures:
- The sync job continues to run and deliver an up-to-date sanctions list under most conditions.
- The validation failure is logged as a warning, triggering alerting for operator review.
- No unvalidated URL is ever fetched by the server.
Impact Assessment
| Factor | Assessment |
|---|---|
| Exploitability | Requires upstream gov.uk page compromise or regex manipulation — not directly exploitable by an end user |
| Impact if exploited | Internal network probing, metadata endpoint access, potential credential leakage |
| Fix complexity | Low — a single startsWith allowlist check |
| Regression risk | Low — fallback URLs provide continuity if the allowlist ever needs updating |
Keeping the Allowlist Current
If OFSI migrates to a new CDN or storage domain in future, the ALLOWED_CSV_ORIGINS array in src/app/api/sanctions/nightly/route.ts must be updated before the old URLs are decommissioned. Monitor the OFSI publications page and SSRF-related warnings in application logs to detect any domain changes early.
This post is part of the platform's ongoing OWASP-aligned security hardening programme. See the Changelog for all security releases.