Repository navigation
Throttle FranceConnect introspections triggered by unknown bearers - #485
Merged
Merged
Conversation
Any bearer sent to a FranceConnectable endpoint (v3 *_with_france_connect and the legacy v2 ones) triggers a POST to the FranceConnect token introspection URL with our client credentials. Without a resolved user, the existing throttles are keyed on the token hash, so an anonymous caller rotating random bearers gets a fresh quota on every request while holding a Puma thread and spending our FranceConnect client_id quota. Requests carrying a bearer that does not resolve to a user are now also throttled by IP on these endpoints: 30 per minute and 600 per hour. Production usage over the last 30 days peaks at 7 per minute and 166 per hour for a single IP, legitimate callers being partner backends. These protective throttles are left out of the RateLimit headers so the advertised quota stays the per-authorization one, and so legitimate FranceConnect calls do not trigger the multiple throttles warning.
The per IP throttle does not stop an attacker spreading random bearers over many IPs, who could still saturate the Puma workers shared by API Entreprise and API Particulier, or get our client_id limited by FranceConnect. Introspections triggered by a bearer that does not resolve to a user are now capped at 120 per minute across all callers. Production peaks at 9 per minute globally over the last 30 days. During a distributed attack legitimate FranceConnect users get 429 too, which is preferred over exhausting the workers of both APIs.
Un3x
approved these changes
Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Any bearer sent to a FranceConnectable endpoint (v3 *_with_france_connect and the legacy v2 cnav, mesri and cnous ones) triggers a POST to the FranceConnect introspection URL with our client credentials. Without a resolved user, existing throttles are keyed on the token hash, so an anonymous caller rotating random bearers gets a fresh quota on every request, holds a Puma thread and spends our FranceConnect client_id quota.
These requests are now throttled per IP (30/min, 600/h) and globally (120/min). Production usage over the last 30 days peaks at 7/min and 166/h for a single IP and 9/min globally, legitimate callers being a handful of partner backends (~960 requests/day). The protective throttles are left out of the RateLimit headers, which otherwise disappear and raise a Sentry warning when two throttles match.
429 is already documented on these endpoints and handled by the SDKs, no client change needed. Upstream protections (nginx, WAF) are not visible from the repository and were not assumed.