We broke sign-up for thirty-six hours, and the error blamed the browser
On 30 July at 12:29 we promoted our Content-Security-Policy from report-only to enforcing. That is the change that turns a policy from advisory into a browser actually refusing to load things. It is a good change and we had spent two days observing the live site to make it safely.
The policy did not permit challenges.cloudflare.com. That is the origin of the CAPTCHA widget our authentication provider renders when its bot protection is switched on. Bot protection was switched on. So from that moment, on every sign-up attempt, the browser refused to load the widget, the widget never mounted, and the form could not be submitted.
Not slow. Not degraded. Sign-up did not work, for everyone, for about thirty-six hours.
The error a prospect sees in that situation is: 'The CAPTCHA failed to load. This may be due to an unsupported browser or a browser extension.' It is a generic string and it points at the client. The person who reported it had already tried Chrome, Edge, incognito, and with extensions disabled — four eliminations, all of them in the wrong place, because the message sent him there. It was never his browser.
We found out because he was on a call with us when it happened. Everybody else who tried in that window is unrecorded and unrecoverable: they never got an account, so there is no row, no session, and no support ticket. We do not know how many there were and we never will.
The part we find hardest to write down is that we had predicted it. The file that builds the policy contains a comment, written before the incident, identifying this exact widget, naming the two directives it would need, and observing that the origin was absent only because a dashboard setting was off. It then left the origin out and pointed at a live check that would catch the widget appearing.
That check could never have fired first. It compares the origins a page references against the origins the policy permits — and the widget is not referenced until bot protection is on, at which point sign-up is already broken. The first thing it could possibly observe was a customer failing to create an account.
So the rule we took from this is about detectors rather than about CSP: a foreseen failure deferred to a detector is only mitigated if you can state the observation the detector fires on. If that observation is the harm, it is not a mitigation. It is a record of the incident, written in advance.
The remedy was one origin in two directives, and it cost nothing while the feature was off. The general form, which is what we actually changed: when a dashboard we cannot read controls what gets rendered, the policy has to cover the superset of what that dashboard may render. The safe direction is to permit. The unsafe direction is to discover.