Security Fix: Closing the API Key Bypass on Sanctions Sync Endpoints (SEC-04)
Security Fix: Closing the API Key Bypass on Sanctions Sync Endpoints (SEC-04)
Version: 0.1.35
OWASP Category: Broken Access Control (A01:2021)
Severity: High
Control ID: SEC-04
Overview
Version 0.1.35 patches a broken access control vulnerability in the sanctions sync API endpoints. The nightly sync and manual sync routes accepted requests that bypassed the intended API key requirement, meaning the sanctions list could be triggered to resync by parties without a valid API key.
The Vulnerability
Two endpoints are responsible for synchronising the OFSI consolidated sanctions list:
| Endpoint | Purpose |
|---|---|
POST /api/sanctions/nightly | Scheduled nightly sync |
POST /api/sanctions/sync | Manual on-demand sync |
Both routes were intended to be protected exclusively by an API key passed via the x-api-key request header, compared against the server-side SANCTIONS_SYNC_API_KEY environment variable.
However, the authentication logic contained a fallback branch:
// ❌ Vulnerable pattern (before v0.1.35)
const apiKey = req.headers.get('x-api-key');
const expectedKey = process.env.SANCTIONS_SYNC_API_KEY;
if (apiKey !== expectedKey) {
// Fallback: accept any Bearer token
const authHeader = req.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
}
This fallback meant that any request carrying a valid Authorization: Bearer header — including a standard NextAuth session token — could successfully invoke either sync endpoint, regardless of whether the x-api-key header was present or correct.
Compounding the issue, the nightly sync route was registered as a public route in middleware.ts. This meant the Next.js middleware layer applied no session authentication check at all before the request reached the route handler, making the Bearer fallback a wholly inadequate guard.
The Fix
1. Unconditional API key enforcement
The Authorization: Bearer fallback has been removed. Both routes now apply a single, unconditional check:
// ✅ Fixed pattern (v0.1.35+)
const apiKey = req.headers.get('x-api-key');
const expectedKey = process.env.SANCTIONS_SYNC_API_KEY;
if (expectedKey && apiKey !== expectedKey) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
If SANCTIONS_SYNC_API_KEY is configured and the provided key does not match, the request is rejected immediately with a 401 Unauthorized response. There is no fallback path.
2. Nightly sync removed from public routes
The /api/sanctions/nightly route has been removed from the publicRoutes list in middleware.ts. All requests to this endpoint now pass through the standard authentication middleware before reaching the route handler.
Impact Assessment
- Before fix: Any caller with a valid NextAuth
Authorization: Bearertoken (i.e. any logged-in user) could trigger a sanctions list resync — or any unauthenticated caller if the nightly route was accessed directly, since it was a public route. - After fix: Only callers presenting the correct
x-api-keyvalue matchingSANCTIONS_SYNC_API_KEYcan trigger a sync. Session tokens are no longer accepted as an alternative credential for these endpoints.
Action Required
If you operate a self-hosted deployment:
- Ensure
SANCTIONS_SYNC_API_KEYis set in your environment. If this variable is unset, the guard conditionif (expectedKey && ...)will not enforce key checking. Treat an unset key as a misconfiguration. - Rotate your API key if you suspect the sync endpoints may have been triggered by unauthorised parties prior to this release.
- Review your middleware public routes to ensure no other sensitive API routes are inadvertently listed as public.
See the environment variable reference for details on configuring
SANCTIONS_SYNC_API_KEY.
Affected Files
src/app/api/sanctions/nightly/route.tssrc/app/api/sanctions/sync/route.tssrc/middleware.ts