TikTok embeds look simple: paste a blockquote, load their script, done. Then your CSP blocks it and suddenly you’re staring at a blank box, console noise, and a product manager asking why the campaign page is broken.
I’ve seen this pattern a lot. Teams start from a pretty strict policy, add TikTok, and either overcorrect by allowing half the internet or undercorrect and leave the embed half-broken. The sweet spot is narrower than people think.
Here are the mistakes I see most often when adding TikTok embeds to a CSP, and how I’d fix them.
The usual TikTok embed
A typical TikTok embed looks something like this:
<blockquote class="tiktok-embed" cite="https://www.tiktok.com/@scout2015/video/6718335390845095173" data-video-id="6718335390845095173">
<section></section>
</blockquote>
<script async src="https://www.tiktok.com/embed.js"></script>
From a CSP point of view, this usually touches:
script-srcforhttps://www.tiktok.com/embed.jsframe-srcbecause TikTok renders embedded content in framesstyle-srcif inline styles or injected styles are involvedimg-srcfor thumbnails and media assetsconnect-srcfor background requests made by the embed script
If your policy is strict, you should expect to tune more than one directive.
Mistake #1: Only adding TikTok to default-src
This is probably the most common bad fix.
I still see policies like this:
Content-Security-Policy: default-src 'self' https://www.tiktok.com
That looks reasonable until you remember default-src is just a fallback. If you already define script-src, frame-src, img-src, or connect-src, those directives take precedence and default-src stops mattering for those resource types.
A real-world example helps. Headertest.com sends this header:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-OGQwMmViN2EtNmQxMi00ODI5LThkYjEtNWYyYTE5NzdlNjNl' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com; frame-src 'self' https://consentcdn.cookiebot.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
If you dropped a TikTok embed onto that page and only updated default-src, it would still fail because:
script-srcdoes not allow TikTokframe-srcdoes not allow TikTok
Fix
Add TikTok to the directives that actually matter:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
object-src 'none';
base-uri 'self';
If you want a refresher on how fallback works, the directive breakdown at https://csp-guide.com is useful.
Mistake #2: Forgetting frame-src
TikTok embeds are not just a script include. The script bootstraps an embedded player, and that player typically lands in a frame. If frame-src is locked down, the script may load fine while the actual video stays blank.
This is the one that tricks people because they see no script-src violation and assume CSP isn’t the problem.
Broken policy
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self';
Fix
Allow TikTok frames explicitly:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
Sometimes developers use child-src out of habit. Modern browsers still understand it in some cases, but frame-src is clearer and the right directive for this job.
Mistake #3: Using a nonce policy that blocks external embed scripts
This one bites teams running modern strict CSPs with nonces and strict-dynamic.
A policy like the headertest.com example is a good baseline for first-party script control:
script-src 'self' 'nonce-...' 'strict-dynamic' https://www.googletagmanager.com ...
But once you rely on nonces and strict-dynamic, external scripts you paste into markup may not behave the way you expect unless they are nonce-bearing trusted scripts or loaded by an already trusted script.
If you paste this:
<script async src="https://www.tiktok.com/embed.js"></script>
and your policy expects trusted scripts to carry a nonce, that TikTok script may be blocked.
Fix option 1: Add a nonce to the script tag
If your rendering layer can do it, this is the cleanest fix:
<script
nonce="{{ .CSPNonce }}"
async
src="https://www.tiktok.com/embed.js"></script>
Content-Security-Policy:
script-src 'self' 'nonce-{{RANDOM}}' 'strict-dynamic' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
Fix option 2: Load TikTok from a trusted nonce-bearing bootstrap script
If you don’t want third-party script tags directly in templates:
<script nonce="{{ .CSPNonce }}">
const s = document.createElement('script');
s.src = 'https://www.tiktok.com/embed.js';
s.async = true;
document.head.appendChild(s);
</script>
That tends to work well with strict-dynamic, assuming your browser targets support it.
My opinion: if your site is already using nonces correctly, don’t weaken the whole policy just for embeds. Make the embed integration fit the nonce model.
Mistake #4: Allowing https: everywhere because debugging is annoying
I get why people do this. TikTok doesn’t load, console errors stack up, deadline is in an hour, and someone changes the policy to this:
Content-Security-Policy:
default-src 'self';
script-src 'self' https:;
frame-src https:;
img-src https: data:;
connect-src https:;
Yes, the embed probably works. You also just turned your CSP from a control into decoration.
Fix
Start narrow, then expand only when you see actual violations. A decent first pass for TikTok embeds looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://www.tiktok.com;
object-src 'none';
base-uri 'self';
Then watch report-only violations and add only what TikTok really needs in your environment.
Mistake #5: Ignoring connect-src
Not every TikTok embed failure is a script or frame issue. The embed script may make fetch/XHR requests behind the scenes. If connect-src is strict, the player can render partially and still fail to load metadata or other supporting content.
Broken policy
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
connect-src 'self';
Fix
Allow TikTok network requests:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
connect-src 'self' https://www.tiktok.com;
Depending on how TikTok serves assets in practice, you may need to allow additional TikTok-owned origins surfaced by your browser’s CSP reports. Don’t guess. Read the violation reports and add exact origins.
Mistake #6: Blaming frame-ancestors
I’ve seen people change frame-ancestors trying to fix embeds. That’s the wrong direction.
frame-ancestors controls who can embed your page. It does not control what your page can embed. In the headertest.com policy, this is set to:
frame-ancestors 'none';
That means nobody can iframe headertest.com. It does not stop headertest.com from embedding something else. For TikTok embeds, the directive you care about is frame-src.
Fix
Leave frame-ancestors alone unless your actual requirement changed. Update frame-src instead.
Mistake #7: Not using Report-Only first
Third-party embeds are messy. TikTok can change implementation details, and your existing CSP may already be tight in ways nobody documented.
If you enforce immediately, you risk breaking production pages for everyone.
Fix
Roll out with report-only first:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://www.tiktok.com;
object-src 'none';
base-uri 'self';
Then inspect the browser console and your CSP reports. Once the violations are boring, enforce it.
Official CSP docs are here if you need the spec-level behavior: https://developer.mozilla.org/docs/Web/HTTP/CSP
A practical baseline policy for TikTok embeds
If I were starting from scratch for a page that needs TikTok embeds and not much else, I’d begin here:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://www.tiktok.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
If your app already uses nonce-based strict CSP, I’d adapt it like this:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM}}' 'strict-dynamic' https://www.tiktok.com;
frame-src 'self' https://www.tiktok.com;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://www.tiktok.com;
object-src 'none';
base-uri 'self';
form-action 'self';
And the embed script should carry the nonce or be injected by a trusted nonce-bearing script.
My rule of thumb
When a TikTok embed breaks under CSP, I check these in order:
script-srcframe-srcconnect-src- nonce /
strict-dynamicbehavior - actual violation reports before adding more origins
That order saves time. Most failures are one of those.
The biggest mistake is treating CSP tuning like cargo cult configuration. Don’t paste random origins into headers until the console goes quiet. Read the violations, understand which directive is firing, and fix that specific permission. That’s how you keep the embed working without quietly gutting your policy.