A Content-Security-Policy is an allowlist: anything you
do not explicitly permit, the browser refuses to load.
That is exactly what makes CSP one of the strongest
defenses against cross-site scripting — and exactly why
it breaks third-party widgets first. reCAPTCHA is not
one resource but a chain of them: a script from one
Google domain, supporting code from another, and an
iframe that hosts the actual challenge. Miss any link
in that chain and the widget never appears, the
g-recaptcha-response field stays empty,
and every form submission fails server-side
verification. The browser tells you precisely what it
blocked — "Refused to load the script ... because
it violates the following Content Security Policy
directive" — but only in the console, where users
never look.
The Exact Directives reCAPTCHA Needs
Google's official reCAPTCHA FAQ documents the policy entries the widget requires. For a domain-allowlist CSP, the recommended form scopes the permissions to the reCAPTCHA paths rather than whole hosts:
script-src https://www.google.com/recaptcha/
https://www.gstatic.com/recaptcha/release/
frame-src https://www.google.com/recaptcha/
https://recaptcha.google.com/recaptcha/
Two details in there cause most of the retry-and-fail
loops we see in forum threads. First,
both script-src entries are needed:
api.js is served from
www.google.com/recaptcha/, but it then
loads the actual widget code from
www.gstatic.com/recaptcha/release/.
Allowing only the first host produces a widget that
half-loads and then dies. Second,
frame-src is not optional. The
checkbox, the image challenges, and even the invisible
variant's challenge fallback all live inside an iframe;
if frame-src (or its legacy parent
child-src, or a restrictive
default-src with no
frame-src override) blocks Google's
frame, users see either an empty box or a checkbox
that spins forever.
If your site serves users through the
recaptcha.net endpoint — common for
audiences where google.com is unreachable,
as we covered in our
recaptcha.net guide for China
— mirror every directive for
www.recaptcha.net as well. A CSP that only
names the Google hosts will block the alternate
endpoint just as thoroughly as the Great Firewall
blocks the original.
Why you should not just allowlist google.com
The tempting shortcut — script-src
https://www.google.com with no path — works, but
it quietly weakens the policy you just deployed. Large
domains host endpoints that can return script in
attacker-influenced shapes (the classic example being
JSONP-style endpoints), which is why CSP auditing tools
such as Google's own CSP Evaluator flag broad
allowlists of big multi-product domains. Scoping to
/recaptcha/ paths keeps the widget working
while leaving the rest of the domain outside your trust
boundary. The
official google/recaptcha repository ships a CSP
example
demonstrating a working policy — worth comparing
against yours before you start loosening directives in
frustration.
The nonce option for strict policies
Teams running a strict, nonce-based CSP (where
script-src trusts only tagged scripts
rather than domains) do not have to fall back to
domain allowlists. Per Google's FAQ, the reCAPTCHA
script tag accepts a nonce:
<script src="https://www.google.com/recaptcha/api.js"
nonce="YOUR_RANDOM_NONCE" async defer></script>
The nonce must be generated per response and echoed in
the Content-Security-Policy header — a
static nonce is no policy at all. You still need the
frame-src entries, because nonces apply to
scripts, not to where iframes may point.
Cloudflare Turnstile: One Host, Same Two Directives
Turnstile keeps the list shorter. Cloudflare's official CSP reference names a single host for both the script and the challenge frame:
script-src https://challenges.cloudflare.com frame-src https://challenges.cloudflare.com
The failure modes rhyme with reCAPTCHA's: allow the script but not the frame and you get Turnstile's error-code family instead of a token — if you are staring at codes like 300030 or 600010, our Turnstile error-code guide walks the full decode table. CSP belongs near the top of that investigation whenever the codes appear only on your hardened production domain and never on staging.
A Debugging Workflow That Doesn't Involve Guessing
- Read the violation, not the symptom. Open DevTools on the broken page. Every CSP block produces a console line naming the blocked URL and the directive that blocked it. That line is the entire diagnosis: add that URL's origin (and path prefix, where supported) to that directive.
-
Fix directives one violation at a
time. The widget loads resources in
sequence, so fixing
script-srcmay reveal a newframe-srcviolation. Repeat until the console is clean and the widget renders. -
Use Report-Only mode for rollout.
Deploying
Content-Security-Policy-Report-Onlyfirst lets the browser report what it would block without breaking production forms — the difference between an inbox of reports and an inbox of angry users. - Find all your policy sources. Multiple CSP headers combine restrictively, and policies arrive from more places than your app code: web-server config, CDN page rules, hosting "security hardening" panels, and WordPress security plugins (CAPTCHA-plugin vendors publish their own CSP guides for exactly this reason). A perfect header in your app is still defeated by a stricter one injected upstream.
- Re-test the full submit path. A rendering widget is not the finish line — submit the form and confirm the token verifies. If the widget renders but tokens fail, you have left CSP territory; start with our script-loading and browser-error guide.
The Real Lesson: Third-Party Trust Is a Line Item
Every CSP entry above is a statement: this outside party may run code on my pages and frame content into them. Writing the directives forces a question that auto-loading scripts let teams skip — which third parties does this site actually trust, and on which pages? That is a healthy audit to run annually regardless of CAPTCHAs. It is also fair to note that verification layers differ in how much CSP surface they demand: a system like rCAPTCHA's behavioral verification keeps the third-party allowlist small, though any externally loaded verification — ours included — belongs in your policy explicitly rather than under a wildcard.
People Also Ask: CSP and CAPTCHA FAQ
Which CSP directives does reCAPTCHA require?
script-src entries for
https://www.google.com/recaptcha/ and
https://www.gstatic.com/recaptcha/release/,
plus frame-src entries for
https://www.google.com/recaptcha/ and
https://recaptcha.google.com/recaptcha/.
Sites using the recaptcha.net endpoint must mirror the
entries for that host.
Why does reCAPTCHA work on staging but not production?
Usually because the CSP differs: production fronts often add security headers at the CDN, load balancer, or hosting-panel level that staging never sees. Compare the actual response headers from both environments rather than the application config.
Can I use a nonce instead of allowlisting Google's domains?
Yes — the reCAPTCHA script tag supports a per-response
nonce attribute for strict CSPs. You still
need the frame-src allowlist entries,
since nonces do not apply to iframe destinations.
What CSP does Cloudflare Turnstile need?
Add https://challenges.cloudflare.com to
both script-src and
frame-src. That single host serves both
the api.js script and the challenge iframe.
Conclusion
A CAPTCHA that vanishes behind a new security header is
not a widget bug — it is your allowlist doing its job
on a dependency nobody wrote down. The fix is five
minutes once you read the violation message: scope
script-src and frame-src to
the documented reCAPTCHA or Turnstile paths, prefer
path-scoped entries or nonces over whole-domain trust,
roll out in Report-Only mode, and keep the directives
in version control next to the integration they
protect. Hardening and working forms are not in
conflict — they just have to be deployed by the same
checklist.
Sources & Further Reading
- Google for Developers: reCAPTCHA FAQ — CSP directives and nonce support
- google/recaptcha: official Content-Security-Policy example
- Cloudflare Docs: Turnstile Content Security Policy reference
- MDN: Content Security Policy
- Melapress KB: Configuring CSP for CAPTCHA plugins
- Keycloak user group: reCAPTCHA blocked by CSP frame-src (discovery signal)