Skip to content

feat(preview): fall back when the site's own CSP blocks the iframe#152

Merged
markdumay merged 1 commit into
mainfrom
feat/preview-check-site-csp
Jul 13, 2026
Merged

feat(preview): fall back when the site's own CSP blocks the iframe#152
markdumay merged 1 commit into
mainfrom
feat/preview-check-site-csp

Conversation

@markdumay

Copy link
Copy Markdown
Contributor

Problem

The embedding detection shipped in v2.0.3 only inspects the target's response headers. That is
one half of the contract. A target that happily permits framing still renders as an empty box
when the embedding site's own CSP frame-src directive does not cover it — and no response header
can rescue that.

This is not hypothetical: it is how every preview bug in the recent round actually failed.

Preview Target allows framing? Site's frame-src covers it? Result
demo.gethinode.com on gethinode.com (after fix) yes ❌ no empty box
theme-agency on gethinode.com (after fix) yes ❌ no empty box
gethinode.com on itself yes ❌ no empty box
Wikipedia on demo.gethinode.com yes ❌ no empty box
OpenStreetMap on demo.gethinode.com yes ❌ no empty box

In every case the build was silent. This check makes all five a build warning plus a graceful
screenshot fallback.

Change

Check the URL against the frame-src the site itself publishes, and show the fallback with an
actionable warning when it is not covered:

WARN  partial [assets/preview.html] - iframe blocked by this site's own CSP: docs/blocks/preview.md
    URL 'https://en.wikipedia.org/...' allows being framed, but is not covered by the frame-src
    directive this site publishes, so the browser refuses the iframe; showing fallback.
    Add its host to a 'frame-src' entry under [modules.<name>.csp] to render it.

The published frame-src is the union of what each module contributes through
[params.modules.<name>.csp], since the mod-csp baseline declares no frame-src of its own. This
reads the same source of truth mod-csp does, without coupling to its assembly code. A site that
contributes nothing is not using this CSP system
, so nothing can be concluded and the preview
renders unchanged — no false positives for non-mod-csp sites.

Ordering

The check runs after the response-header check, and only for a target that would otherwise have
loaded. Running it first would be cheaper (no network request), but it would tell an author to add
google.com to frame-src — sending them after a fix that cannot work, since google.com refuses
to be framed at all. The block reference previews google.com on purpose to demonstrate exactly
that case.

Verification

Against a local server returning synthetic headers:

  • The 15 existing header cases still produce the correct verdict, over both the HEAD and the
    GET path — no regression.
  • 4 new site-side cases, with the site's frame-src set to ['*.wikipedia.org', "'self'"]:
Case URL Expected Result
covered by frame-src en.wikipedia.org live
'self' the site's own origin live
absent from frame-src weareadaptiq.com fallback
target allows, site does not mock allow-host fallback

End to end on the real hinode exampleSite (via a workspace use), with mod-docs declaring its
sources: Wikipedia and OpenStreetMap render live, google.com falls back, no warnings. Removing
the mod-docs declaration flips Wikipedia and OpenStreetMap to a screenshot with the warning above
— i.e. the check catches precisely the bug class it exists for.

pnpm test passes.

Related

  • gethinode/mod-docs#... declares *.wikipedia.org and www.openstreetmap.org for the block
    reference, so consuming sites inherit them instead of hand-maintaining the list.

🤖 Generated with Claude Code

The embedding detection added in v2.0.3 only inspects the target's response headers. That misses
the other half of the contract: a target that happily allows framing still renders as an empty box
when the embedding site's own CSP frame-src directive does not cover it, and no response header
can rescue that. Every preview broken on gethinode.com and demo.gethinode.com failed this way, and
the build reported nothing.

Check the URL against the frame-src the site itself publishes, and show the fallback with an
actionable warning when it is not covered. The published frame-src is the union of what each
module contributes through [params.modules.<name>.csp], since the mod-csp baseline declares no
frame-src of its own. A site that contributes nothing is not using this CSP system, so nothing can
be concluded and the preview renders unchanged.

The check runs after the response-header check and only for a target that would otherwise have
loaded. Telling an author to add google.com to frame-src would send them after a fix that cannot
work, because it refuses to be framed at all.

Verified against a local server returning synthetic headers: the 15 existing header cases still
produce the correct verdict over both the HEAD and the GET path, and 4 new cases cover the site
side — a host allowed by frame-src, 'self', a host absent from frame-src, and a host that the
target permits but the site does not.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@markdumay markdumay enabled auto-merge July 13, 2026 11:25
@markdumay markdumay merged commit 8d91e9a into main Jul 13, 2026
8 checks passed
@markdumay

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 2.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant