Cross-origin isolate the editor page - #9462
Open
iHiD wants to merge 1 commit into
Open
Conversation
SharedArrayBuffer is only available to a cross-origin isolated document, which means the editor needs to send COOP and COEP. We use `Cross-Origin-Embedder-Policy: credentialless` rather than `require-corp`. Both grant isolation, but under `require-corp` every cross-origin subresource without Cross-Origin-Resource-Policy is blocked outright - which would include the stylesheets we pull from the assets host and any image an exercise author puts in their markdown. `credentialless` fetches those without credentials instead. Cross-origin iframes are strict under both values though, so the Vimeo embed (the one iframe reachable from the editor - the hello-world tutorial video) gets the `credentialless` attribute. Nothing else can get an iframe in there: Markdown::ParseDoc enables `tagfilter`, which escapes raw <iframe> tags. The subtle part is Turbo. Isolation is a property of the document, and every in-app navigation on the site renders into the persistent tf-main frame, so the editor would inherit whatever isolation the *previous* page was loaded with - i.e. none, silently. So the editor now renders the full layout even for frame requests, and carries `<meta name="turbo-visit-control" content="reload">`, which makes Turbo hand over to a real browser navigation. The direct links into the editor also get `data-turbo="false"` so the common paths skip the wasted round trip. This needs Cross-Origin-Resource-Policy on assets.exercism.org to land first (separate terraform change) so we keep the option of tightening to `require-corp` later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGsPyM4G17uXGDLmmF8caF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sends COOP/COEP on the editor page so
SharedArrayBufferis available there (needed for the embedded C++ runtime).Scoped to
Tracks::ExercisesController#editonly, so marketing pages, checkout and everything else are untouched.Why
credentiallessand notrequire-corpBoth give cross-origin isolation. The difference is what happens to a cross-origin subresource with no
Cross-Origin-Resource-Policy:require-corpblocks it,credentiallessfetches it without credentials.require-corpwould break the stylesheets we pull fromassets.exercism.org(stylesheet_link_tagsets nocrossorigin, unlike our JS and font preloads which already do), plus any image an exercise author puts in their markdown, plus the Cloudflare Insights beacon. All of those are fine undercredentialless.Iframes
Iframes are strict under both values - an embedded document must send its own COEP or the element needs the
credentiallessattribute. Only one iframe is reachable from the editor, the hello-world tutorial video (VimeoEmbedviaInstructionsPanel, gated onexercise.slug === 'hello-world'), so that gets the attribute. Nothing else can sneak one in:Markdown::ParseDocenablestagfilter, which escapes raw<iframe>in exercise markdown even though we render withUNSAFE.The Turbo part
This is the bit that would have silently broken it. Cross-origin isolation is a property of the document, and every in-app navigation renders into the persistent
tf-mainframe, so the editor would have inherited the isolation state of whatever page you came from - i.e. none - while still sending the headers.Two changes:
Tracks::ExercisesControlleroverrides the layout lambda soeditrenders the full layout even for a frame request.edit.html.hamlcarries<meta name="turbo-visit-control" content="reload">. Turbo checks this on the response snapshot for both frame requests (handleUnvisitableFrameResponse) and Drive visits (PageRenderer#shouldRender), and hands over to a real browser navigation.That covers every entry point, including the ones that go through
redirectTo(window.Turbo.visit) -StartExerciseButton,EmptyIterations,useTrackWelcomeModal- at the cost of one wasted round trip. The direct<a>links also getdata-turbo="false"so the common paths skip that.Depends on
A separate terraform change adding
Cross-Origin-Resource-Policy: cross-origintoassets.exercism.orgresponses. Not strictly required forcredentialless, but it's what keeps the door open to tightening torequire-corplater.Verifying
crossOriginIsolated === truein the console on the editor page, checked after navigating into it from elsewhere on the site, not just on a reload.Tests
Added three controller tests: the headers are present, a frame request gets a full document with the meta tag, and
showstill renders into the frame as before.🤖 Generated with Claude Code
https://claude.ai/code/session_01VGsPyM4G17uXGDLmmF8caF