Skip to content

Cross-origin isolate the editor page - #9462

Open
iHiD wants to merge 1 commit into
mainfrom
editor-cross-origin-isolation
Open

Cross-origin isolate the editor page#9462
iHiD wants to merge 1 commit into
mainfrom
editor-cross-origin-isolation

Conversation

@iHiD

@iHiD iHiD commented Sep 3, 2026

Copy link
Copy Markdown
Member

Sends COOP/COEP on the editor page so SharedArrayBuffer is available there (needed for the embedded C++ runtime).

Scoped to Tracks::ExercisesController#edit only, so marketing pages, checkout and everything else are untouched.

Why credentialless and not require-corp

Both give cross-origin isolation. The difference is what happens to a cross-origin subresource with no Cross-Origin-Resource-Policy: require-corp blocks it, credentialless fetches it without credentials.

require-corp would break the stylesheets we pull from assets.exercism.org (stylesheet_link_tag sets no crossorigin, 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 under credentialless.

Iframes

Iframes are strict under both values - an embedded document must send its own COEP or the element needs the credentialless attribute. Only one iframe is reachable from the editor, the hello-world tutorial video (VimeoEmbed via InstructionsPanel, gated on exercise.slug === 'hello-world'), so that gets the attribute. Nothing else can sneak one in: Markdown::ParseDoc enables tagfilter, which escapes raw <iframe> in exercise markdown even though we render with UNSAFE.

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-main frame, 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::ExercisesController overrides the layout lambda so edit renders the full layout even for a frame request.
  • edit.html.haml carries <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 get data-turbo="false" so the common paths skip that.

Depends on

A separate terraform change adding Cross-Origin-Resource-Policy: cross-origin to assets.exercism.org responses. Not strictly required for credentialless, but it's what keeps the door open to tightening to require-corp later.

Verifying

crossOriginIsolated === true in 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 show still renders into the frame as before.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VGsPyM4G17uXGDLmmF8caF

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
@iHiD
iHiD requested a review from dem4ron as a code owner September 3, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant