CNIL's €500K Fine for a Missing MFA Toggle: What 5 Days of Undetected Access Really Means for Your Stack
On 3 September 2026, France's data protection authority CNIL published a €500,000 sanction against Hôpital Privé de la Loire, a private hospital, over a breach that gave an attacker unsupervised access to its patient file system for several days during summer 2025. The English-language summary on CNIL's sanctions list confirms the same figures. This is not a story about a hospital forgetting to buy a firewall. It's a story about two separate, ordinary engineering decisions — no MFA on an external login, no alerting on abnormal read volume — that together turned a stolen password into a five-day, half-million-record exfiltration nobody noticed until it was over.
If you build or operate any SaaS product that touches health data, employee records, or anything else GDPR classifies as a special category under Article 9, this decision is worth reading closely — not for the headline fine, but for exactly which two controls CNIL named as missing, and what a working version of each one actually looks like in code.
What CNIL actually found
The facts, as stated in CNIL's own decision:
- Attack vector: an attacker obtained valid credentials and used them to log into the hospital's external-facing patient record system (the dossier patient informatisé, DPI).
- Root cause CNIL cites: the external authentication procedure "was not sufficiently robust, due to the absence of VPN and means of multifactor authentication" — a direct quote from CNIL's decision, confirmed via WebFetch against cnil.fr.
- Detection failure: the attacker "was able to explore the DPI for several days and extract a very large volume of data, without this abnormal activity being detected" — CNIL is explicit that the hospital had no mechanism to flag unusual access patterns in real time.
- Scope: 524,867 patient records and 202,246 additional people registered in those records as third-party contacts.
- Articles cited: Article 32 (security of processing) as the primary violation, Article 34 (communication of a breach to data subjects) as a secondary one.
- Remedy: a formal order to fix the underlying security gaps within three to fifteen months, depending on the measure, plus two years of public visibility for the sanction itself.
Two things stand out if you read this as an engineer rather than a compliance officer. First, CNIL didn't fine the hospital for having a breach — breaches happen to well-run organizations too. It fined the hospital for a specific, named, avoidable gap: no MFA, no VPN, on a system holding special-category health data. Second, and more interesting for anyone building detection tooling: the sanction explicitly calls out the absence of anomaly detection as its own finding, separate from the authentication gap. A five-day, half-million-record read spike from a single account is not a subtle signal. It's the kind of thing a rate threshold on a log pipeline would have caught on day one.
Why this is a detection story, not just an authentication story
It's tempting to read "no MFA" as the whole lesson and stop there. That undersells what actually went wrong. MFA is a prevention control — it raises the cost of using a stolen password. But prevention controls fail. Credentials get phished, reused, or leaked from an unrelated breach, and no MFA implementation is bulletproof (SIM-swap and MFA-fatigue attacks are well documented). The reason a single compromised login turned into 727,113 exposed people's data over multiple days is that nothing downstream of the login noticed. Prevention and detection are two independent layers under GDPR Art.32(1)(b), which requires "the ability to ensure the ongoing confidentiality, integrity, availability and resilience of processing systems" — resilience here means catching a compromise in progress, not just keeping attackers out in the first place. CNIL's decision treats both as distinct obligations because they are: an org can have excellent MFA and still fail Art.32 if it has zero visibility into what an authenticated session actually does once it's inside.
That framing matters because MFA is now table stakes almost everywhere, but real-time access anomaly detection on the actual data layer — as opposed to network-perimeter monitoring — is still something most small and mid-size SaaS teams have never built. It's the control CNIL is signaling it will look for next.
Control 1: MFA is a starting point, not the finish line
If your product exposes any interface — admin panel, API with elevated scopes, or a patient/customer record view — to the public internet with only a password gate, you have the exact configuration CNIL cited. The fix is well understood, but worth being explicit about the shape of it: MFA needs to be enforced server-side, on every session-issuing path, not just the primary login UI (a common gap is a "legacy" API key or password-reset flow that bypasses the MFA check entirely).
A minimal enforcement pattern for a Node/Express service, using TOTP so it works with any standard authenticator app:
import { authenticator } from "otplib";
// Called after primary password verification succeeds.
// Session is only issued once BOTH factors pass.
async function verifySecondFactor(req, res, next) {
const { userId, totpCode } = req.body;
const user = await db.users.findById(userId);
if (!user.mfaSecret) {
// Fail closed: accounts without MFA enrolled cannot reach
// systems handling special-category data (GDPR Art.9).
return res.status(403).json({ error: "mfa_enrollment_required" });
}
const valid = authenticator.check(totpCode, user.mfaSecret);
if (!valid) {
await auditLog.record({ userId, event: "mfa_failed", ip: req.ip });
return res.status(401).json({ error: "invalid_totp" });
}
req.session.mfaVerifiedAt = Date.now();
next();
}
The important line is the "fail closed" branch: an account without MFA enrolled should not be able to reach a system with health or other special-category data at all, not fall back to password-only. That's the difference between "MFA is available" (what most breached orgs already had) and "MFA is required" (what CNIL's decision actually demands).
Control 2: catching a slow, quiet exfiltration in progress
This is the control almost nobody builds until after an incident like this one. The signal you're looking for isn't sophisticated — a single account reading an abnormal volume of records over a short window is a strong anomaly regardless of what your normal traffic pattern looks like. A simple rolling-window detector catches exactly the pattern CNIL described ("several days," "very large volume"):
from collections import defaultdict
from datetime import datetime, timedelta
# access_events: [{user_id, record_id, timestamp}, ...] from your app's
# audit log — every patient/customer record read, not just writes.
def flag_bulk_access_anomaly(access_events, window_hours=24, threshold=200):
"""
Flags any account whose distinct-record read count in a rolling
window exceeds `threshold` — tune per role, not globally: a billing
clerk's baseline is very different from a treating physician's.
"""
by_user = defaultdict(list)
for e in access_events:
by_user[e["user_id"]].append(e)
alerts = []
for user_id, events in by_user.items():
events.sort(key=lambda e: e["timestamp"])
window_start = 0
seen_records = set()
for i, e in enumerate(events):
while events[i]["timestamp"] - events[window_start]["timestamp"] > timedelta(hours=window_hours):
window_start += 1
seen_records = {ev["record_id"] for ev in events[window_start:i + 1]}
if len(seen_records) > threshold:
alerts.append({
"user_id": user_id,
"distinct_records": len(seen_records),
"window_end": e["timestamp"],
})
break # one alert per user is enough to trigger a review
return alerts
Three implementation notes that separate a real control from a compliance-theater one:
- The threshold has to be role-aware. A receptionist reading 200 records in a day is an anomaly; a physician on call might legitimately read that many. Baseline per role, not globally — a single global threshold either misses real attacks (set too high) or drowns your team in false positives (set too low) until everyone ignores the alert channel.
- Alerting has to be real-time, not a weekly report. CNIL's finding is specifically that the abnormal activity ran for days undetected. A detector that runs as a nightly batch job and lands in someone's inbox the next morning would still have let this attack finish before anyone read the email. This needs to feed a paging/alerting pipeline that can force a session revocation, not a dashboard someone checks eventually.
- Log the read, not just the write. Most application audit logs are built around writes (create/update/delete) because that's what needs to be undone if something goes wrong. A bulk-exfiltration attack is almost entirely reads. If your audit trail doesn't capture record-level reads on sensitive tables, you have no data to run this detector against in the first place — this is usually the actual blocker, not the detection logic itself.
Control 3: reduce what a compromised session can actually take
Detection and prevention both matter, but the third lever — data minimization at the access-control layer — determines how bad a successful breach is even when the first two controls both fail. If every authenticated clinician session can query the entire 524,867-record database rather than the specific patient panel they're treating, a single compromised account has maximum blast radius by design. Scoping reads to a need-to-know set (patients currently assigned to that provider, records touched in the last N days, whatever your domain's legitimate access pattern actually is) doesn't stop an attacker from using a stolen credential — but it caps what they can take with it, which is exactly the kind of "appropriate technical measure... appropriate to the risk" language in Art.32(2) that CNIL is evaluating against.
Why Article 34 is the part teams underestimate
CNIL cited Art.34 as a secondary violation alongside Art.32. Article 34(1) requires notifying affected individuals directly, without undue delay, "when the personal data breach is likely to result in a high risk to the rights and freedoms of natural persons." For most SaaS breaches, "high risk" is a judgment call your legal team argues about. For health data — a special category under Art.9 — that argument mostly doesn't exist: a breach exposing hundreds of thousands of patient records is close to a textbook case of high risk, which makes individual notification close to mandatory rather than discretionary. Teams that build products touching health, biometric, or similarly sensitive data should assume Art.34 notification is the default outcome of any meaningful breach, not an edge case to be argued down after the fact — and should have a notification runbook that doesn't start from scratch during an active incident.
What this means if you host or build EU healthcare SaaS
None of the three controls above are exotic, and none of them require a particular hosting provider — you can build MFA enforcement, read-anomaly detection, and scoped access control on any infrastructure. What changes with your hosting choice is a narrower, but still real, piece of the puzzle: where the audit logs, session data, and the anomaly-detection pipeline itself physically live and under whose legal jurisdiction. A US-headquartered cloud provider subject to the CLOUD Act can be compelled to hand over that data regardless of where the servers sit; an EU-domiciled provider with no US parent removes that exposure entirely. That's the specific, narrow claim sota.io's EU-sovereignty positioning makes — it's a piece of the Art.32/44 puzzle around where data and its access trail live, not a substitute for the application-layer controls above. Hosting location alone would not have stopped this breach; the missing MFA and missing anomaly detection would have failed identically on any cloud, EU or not. Both layers — infrastructure jurisdiction and application-layer security — are independently necessary, and CNIL's decision is a reminder that regulators are now willing to price the application-layer gap at half a million euros on its own.
A checklist before your next audit
- Does every session-issuing path (login, password reset, API key exchange, SSO callback) enforce MFA, with no bypass route for legacy clients?
- Do accounts without MFA enrolled get hard-blocked from systems handling Art.9 special-category data, rather than falling back to password-only?
- Does your audit log capture record-level reads, not just writes, for anything classified as special-category or otherwise sensitive?
- Is there a real-time (not batch/nightly) anomaly detector on read volume, scoped per role rather than a single global threshold?
- Does an anomaly alert have a defined response — session revocation, forced re-authentication — or does it just land in a channel someone might see?
- Is your Art.34 individual-notification runbook written and rehearsed before an incident, not drafted for the first time during one?
- Do authenticated sessions get scoped to a need-to-know record set, so a compromised account can't query your entire dataset in one query?
CNIL's decision gives every team building on top of sensitive EU data a specific, dated, primary-sourced answer to "what does 'appropriate technical measures' actually mean in practice" — and it's a shorter list than most compliance checklists suggest: MFA that's actually required, and someone (or something) watching what happens after login.
EU-Native Hosting
Ready to move to EU-sovereign infrastructure?
sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.