A YouTube embed needs frame-src https://www.youtube.com, and https://www.youtube-nocookie.com as well if you use privacy-enhanced mode. The IFrame Player API adds script-src https://www.youtube.com, which serves both the iframe_api script and the player script it loads. A plain <iframe> embed needs no script-src at all.
The policy
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.youtube.com; frame-src https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
The builder finds one low point: a <meta> tag ignores frame-ancestors, so send this as a header. Its table shows the chain frame-src → child-src → default-src: without frame-src, default-src 'self' decides, and the player is refused.
Only the frame is yours
The video, thumbnails and the player’s own requests load inside YouTube’s document, and that document answers to YouTube’s policy, not yours. So media-src, img-src and connect-src don’t need YouTube hosts for an embed. They do if your page fetches something itself, such as a thumbnail for a click-to-play placeholder.
Privacy-enhanced mode
YouTube’s help page says to change the embed domain from https://www.youtube.com to https://www.youtube-nocookie.com. The frame then comes from the second host, so list whichever you embed; list both while you switch.
<iframe src="https://www.youtube-nocookie.com/embed/M7lc1UVf-VE" width="640" height="360" allow="autoplay; encrypted-media" allowfullscreen></iframe>
The IFrame API with a nonce
script-src 'nonce-REPLACE_ME' 'strict-dynamic' https://www.youtube.com; frame-src https://www.youtube.com https://www.youtube-nocookie.com
YouTube’s embed and IFrame API docs say nothing about CSP. The loader in their getting-started sample is an inline script that creates the iframe_api tag, so with 'strict-dynamic' that inline script needs only your nonce, and the tag it creates is trusted. The iframe_api script served today also copies its own nonce, if it has one, onto the player script it adds. The host stays as a fallback for browsers without 'strict-dynamic'; frame-src stays as it is, because 'strict-dynamic' covers scripts only.
Gotchas
frame-ancestorsis the other direction. It says who may frame your page; it does nothing for the videos you embed.- Trusted Types.
iframe_apicreates a policy namedyoutube-widget-api. Atrusted-typeslist that leaves it out means the player script can’t be set oncerequire-trusted-types-for 'script'is on. - The
originparameter. Withenablejsapi=1, YouTube asks you to pass your page’s origin, so other scripts can’t take over the player. That is YouTube’s check, separate from your CSP.
In Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.youtube.com; frame-src https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"