Skip to content

Trac oEmbed: Apply the iframe's sandbox flags to the embed response - #876

Closed
obenland wants to merge 3 commits into
WordPress:trunkfrom
obenland:trac-oembed-embed-response
Closed

Trac oEmbed: Apply the iframe's sandbox flags to the embed response#876
obenland wants to merge 3 commits into
WordPress:trunkfrom
obenland:trac-oembed-embed-response

Conversation

@obenland

@obenland obenland commented Sep 4, 2026

Copy link
Copy Markdown
Member

The oEmbed payload wraps the embed document in an iframe carrying sandbox="allow-scripts allow-top-navigation-by-user-activation". Those flags sit on the element rather than on the response, so they only take effect when the document is loaded through that particular iframe. Consumers that rebuild the iframe from the payload's src, and requests made to the endpoint directly, get the document without them.

This sends the same flags as a Content-Security-Policy: sandbox header so they travel with the response however it is loaded. They are identical to the iframe's, so the framed path is unchanged.

Also builds the injected height reporter with a text node. Passing the script to DOMDocument::createElement() as a value parses it for entity references, which mangles or drops a script containing &. The current script is unaffected; this keeps a later edit to it from breaking silently.

Finally, the reporter now returns early when the document is not framed. It posts to window.parent, which is the window itself on a direct load, so the message comes straight back to its own message listener and the script spins for as long as the page is open. Measured in Chromium: an unframed load reached the probe's 25-message cap in under 1.5s, and reports 0 with the guard in place. A framed load posts twice — DOMContentLoaded and load — both before and after.

Testing

  • The header is sent before both early exits, so the cached and "Temporarily Unavailable" responses carry it too.
  • The flags are byte-identical to the iframe attribute, so the framed path intersects to the same set.
  • phpcs against phpcs.xml.dist reports nothing new.

The sandbox behaviour itself is from the spec and was not exercised in a browser; the reporter change was.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Security
    • Embedded iframe responses now use a restrictive Content Security Policy sandbox to strengthen isolation.
    • Reporting scripts only run when content is displayed within an embedded context.
    • Embedded scripts are handled more safely during creation and execution, improving the security of iframe content.

The oEmbed payload wraps the embed document in an iframe carrying
`sandbox="allow-scripts allow-top-navigation-by-user-activation"`. Those flags
sit on the element rather than on the response, so they only take effect when
the document is loaded through that particular iframe. Consumers that rebuild
the iframe from the payload's `src`, and requests made to the endpoint
directly, get the document without them.

Send the same flags as a `Content-Security-Policy: sandbox` header so they
travel with the response however it is loaded. They are identical to the
iframe's, so the framed path is unchanged.

Also build the injected height reporter with a text node. Passing the script to
`DOMDocument::createElement()` as a value parses it for entity references, which
mangles or drops a script containing `&`. The current script is unaffected;
this keeps a later edit to it from breaking silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 4, 2026 19:56
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props obenland.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 7ad1f175-fc2d-4cb8-8557-2e57fb12f114

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7f676 and 607d0a8.

📒 Files selected for processing (1)
  • api.wordpress.org/public_html/dotorg/trac/oembed/index.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • api.wordpress.org/public_html/dotorg/trac/oembed/index.php

Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The iframe response now sends a sandbox Content Security Policy, skips reporting when opened directly, and creates the reporting script by inserting JavaScript as a text node.

Changes

Iframe response security

Layer / File(s) Summary
Response security and script injection
api.wordpress.org/public_html/dotorg/trac/oembed/index.php
The iframe response adds a sandbox policy that permits scripts and user-activated top-level navigation. The reporting script exits outside an iframe and uses an explicit script element with JavaScript inserted as a text node.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 607d0

The response adds sandbox confinement and adjusts iframe reporting behavior without any identified remaining merge-blocking risk.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: applying the iframe sandbox flags to the Trac oEmbed response.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes security headers and browser sandboxing behavior on a production-facing endpoint and should be validated by a human in a real browser/embed consumer flow.

Pull request overview

This PR updates the Trac oEmbed embed document response so the iframe sandbox restrictions also apply when the document is loaded outside the original oEmbed-provided <iframe> (e.g., consumers reconstruct the iframe from the src, or the embed endpoint is fetched directly).

Changes:

  • Adds a Content-Security-Policy: sandbox ... header matching the existing iframe sandbox attribute flags.
  • Adjusts DOM injection of the height-reporter script to use a text node instead of DOMDocument::createElement( 'script', $js ), avoiding entity parsing of the script content.
File summaries
File Description
api.wordpress.org/public_html/dotorg/trac/oembed/index.php Adds CSP sandbox header to the embed HTML response and changes DOMDocument script injection to preserve script text as-authored.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

obenland and others added 2 commits September 4, 2026 15:06
The reporter posts to `window.parent`, which is the window itself when the
embed document is loaded on its own. That delivers the message straight back to
its own `message` listener, which posts again, so the script spins for as long
as the page is open.

Return early when there is no parent frame; there is nothing to report to.
Framed embeds are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The endpoint re-serves markup it does not control, so the confinement has to
sit on the response rather than on what the markup happens to contain. Record
that alongside the header, since the reasoning is not visible from the flags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wporg-sync wporg-sync closed this in ee0a56f Sep 4, 2026
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.

2 participants