How browser console errors revealed a CSP block on WordPress’s blob frames and workers.
We had just finished documenting a Windows button that refused to work.
Then we opened the article in WordPress.
“This content is blocked. Contact the site owner to fix the issue.”
The site owner was already looking at it.
Apparently, the troubleshooting wasn’t finished. It had simply changed departments.
A successfully saved article, minus the article
The first case study described how an AI agent traced a disabled Windows Update install button to a registry policy. We had turned the investigation into a draft and saved it through the WordPress REST API.
Opening the editor produced an oddly selective success: the title loaded, the sidebar showed the excerpt and draft status, and the main editing area displayed a blocked-content message.
Before blaming the new article, the agent checked the saved post through the authenticated API. The text was intact. All nine code blocks were present. The article itself contained no embedded iframe.
The content wasn’t missing. The browser wasn’t letting the editor display it.
That distinction saved us from the classic troubleshooting manoeuvre of rewriting something that wasn’t broken.

The first suspect had a very convincing name
The agent inspected the site’s HTTP response headers and found:
X-Frame-Options: DENY
That is a fairly unambiguous instruction: do not display this document inside a frame.
WordPress’s block editor can use an iframe for its editing canvas. A framing restriction therefore looked like a strong suspect. The site sat behind Traefik, so the administrator added this Headers middleware setting:
traefik.http.middlewares.wordpress_headers.headers.customFrameOptionsValue: "SAMEORIGIN"
SAMEORIGIN allows a document to be framed by pages of the same origin rather than by arbitrary other sites. Traefik documents that this custom value overrides frameDeny within the same middleware.
We checked the live responses again. The header had changed exactly as intended.
The editor had not.
A configuration change taking effect and a problem being solved are two different events. Administrators tend to learn this distinction repeatedly.
The agent’s first explanation had been incomplete. Public response headers also weren’t a substitute for inspecting the authenticated editor in the browser. It was time to stop treating a suspicious header as the whole diagnosis.
The browser finally explained itself
The administrator opened the browser console and reloaded the editor. The useful messages were not subtle. In shortened form, they said:
Creating a worker from 'blob:https://example.com/…' violates CSP.
'worker-src' was not explicitly set, so 'default-src' is used as a fallback.
Framing 'blob:https://example.com/…' violates CSP.
'frame-src' was not explicitly set, so 'default-src' is used as a fallback.
There it was: two blocked resource types, two missing directives, and one policy fallback explaining both.
A later JavaScript error complained that the editor could not read documentElement from a null value. That was consistent with the iframe failing to load. It was not a compelling reason to start editing WordPress’s JavaScript.
The console had supplied something much more useful than another theory: the resource being blocked and the rule blocking it.
WordPress had brought a blob
The existing policy looked like this, with the domain generalised:
default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: *.example.com;
It allowed the site’s own origin, HTTPS resources, and data URLs. It did not explicitly allow blob: URLs.
A blob URL is a browser-managed reference to data created in the browser. It can back a generated document or script without fetching an ordinary page from the server. In this editor session, WordPress was using blob URLs for its iframe canvas and a worker associated with media-upload code.
The https://example.com portion inside a blob URL looks reassuringly familiar. But it doesn’t turn the URL’s scheme from blob: into https:. The actual browser errors showed that this policy’s fallback was rejecting those resources.
The policy was not uniformly strict: allowing inline scripts, eval, and resources from any HTTPS origin is fairly broad. It was simply missing the allowances this editor needed.
“Secure” and “compatible with the application” are not synonyms. Neither are “permissive in several places” and “permits this particular thing.”
Two invitations, not an open door
The repair was to add explicit rules for frames and workers:
frame-src 'self' blob:;
worker-src 'self' blob:;
In the existing Traefik label, the resulting policy became:
traefik.http.middlewares.wordpress_headers.headers.contentsecuritypolicy: "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: *.{{ docker_container__base__dns_domain }}; frame-src 'self' blob:; worker-src 'self' blob:;"
The SAMEORIGIN setting stayed in place. The administrator applied the updated configuration and reported that the editor worked.
Nobody disabled CSP. Nobody added blob: to default-src, which would have made the allowance apply more broadly. The change named the two resource types identified by the console.
There is a trade-off worth preserving: an explicit frame-src 'self' blob: also narrows which external pages can be framed. If the site needs external embeds, their required origins should be added deliberately. Existing directives should be merged, not duplicated, and other middleware or a second CSP header may still impose additional restrictions.
This was a targeted compatibility repair—not a complete security audit or a universal WordPress CSP template.
The tiny cheat sheet that would have helped sooner
These settings address different questions:
frame-src: which frame documents may this page load?worker-src: which worker scripts may this page create?X-Frame-Options/frame-ancestors: who may embed this document in a frame?default-src: the fallback for many resource directives when they aren’t explicitly configured.
frame-ancestors does not inherit from default-src. And SAMEORIGIN does not grant permission to load every resource needed inside an editor.
Both the frame and worker violations mattered here. Calling this just a “frame-src fix” would leave half the console evidence out of the story.
For a similar failure, start with F12 → Console → reload. Match the blocked resource to its directive, then inspect the actual response headers in the Network panel. If the problem persists, look for multiple enforced CSP policies, meta tags, and middleware writers before broadening the rules again.
The useful part wasn’t getting it right first time
The agent verified that the article was saved, identified a plausible framing restriction, checked that the first change took effect, and then corrected course when it did not solve the problem. The administrator supplied the decisive browser evidence and applied the proxy changes.
Success was confirmed by the administrator using the editor. The agent did not independently observe the repaired authenticated canvas, and changing DENY alone was not proven to be the original cause.
That is a more useful account of AI-assisted administration than a story in which the agent knows everything immediately. An assistant should help test explanations—not defend them after the evidence disagrees.
The first article ended with a registry value changing from 1 to 0. Getting that article into a working editor took two explicit CSP directives.
The browser wasn’t refusing to display our article. It was enforcing the policy we had given it. Unfortunately, WordPress had brought a blob to a policy that hadn’t invited one.
