Skip to content

strip custom css from block widget content saved without edit_css - #13820

Open
nvxbug wants to merge 1 commit into
WordPress:trunkfrom
nvxbug:block-widget-custom-css
Open

nvxbug wants to merge 1 commit into
WordPress:trunkfrom
nvxbug:block-widget-custom-css

Conversation

@nvxbug

@nvxbug nvxbug commented Sep 29, 2026

Copy link
Copy Markdown

The custom CSS block support strips style.css from block attributes on content_save_pre when the saving user lacks edit_css, but block widget content never reaches that filter. WP_Widget_Block::update() and the raw_instance branch of WP_Customize_Widgets::sanitize_widget_instance() only run wp_kses_post(), which leaves block comment JSON alone, so a site administrator on multisite (or any administrator under DISALLOW_UNFILTERED_HTML) can save a block widget carrying custom CSS that do_blocks() then emits for every visitor.

Run the content through wp_strip_custom_css_from_blocks() in both sanitizers when the current user lacks edit_css, next to the existing unfiltered_html check. Every save path for block widgets (widgets screen, REST controllers, Customizer changesets) ends in one of these two methods, so the gate holds without each entry point needing its own filter. Includes regression tests for both paths.

Trac ticket: https://core.trac.wordpress.org/ticket/64771

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude
Used for: Locating the widget save paths, drafting the patch and the regression tests. The change and tests were reviewed and run locally against the PHPUnit suite in single site and multisite.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions

github-actions Bot commented Sep 29, 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.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @nvxbug.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

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

Props westonruter.

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

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@westonruter westonruter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@nvxbug You opened this PR against a closed ticket that landed in WordPress 7.0: https://core.trac.wordpress.org/ticket/64771

Is there a new ticket you meant to target instead? Is this still an issue? If so, make sure there is a new ticket written up that documents it so you can properly attach a PR to it.

@nvxbug

nvxbug commented Sep 30, 2026

Copy link
Copy Markdown
Author

No new ticket, that one's on me: 64771 is the ticket that introduced the block support and I linked it as the closest match. There's no ticket for this gap yet.

It's still an issue on current trunk. wp_strip_custom_css_from_blocks() only runs on content_save_pre / content_filtered_save_pre, and block widget content never passes through those. WP_Widget_Block::update() and the 'block' raw_instance branch of WP_Customize_Widgets::sanitize_widget_instance() only apply wp_kses_post(), which leaves the block comment JSON alone. So a user with edit_theme_options but without edit_css (a site admin on multisite, or any admin under DISALLOW_UNFILTERED_HTML) can save a block widget carrying style.css, and do_blocks() on widget_block_content renders it for every visitor. The tests in this PR fail against trunk's copies of those two files and pass with the patch, in both single site and multisite.

On the ticket: I'm not set up with a wordpress.org account to file one from, and since it's a capability gate being bypassed I also wasn't sure whether public Trac or HackerOne is the right place for the writeup. If Trac is fine and you can open one, I'll update the ticket link here and the @ticket annotations in the tests. If you'd rather it go through HackerOne, I'll send it there instead.

@westonruter

Copy link
Copy Markdown
Member

If this is a security issue then I'm confused why you're opening a PR publicly unless someone else on the security team told you it was a public hardening.

In any case, I encourage you to create a WordPress.org account to create a ticket properly in Trac for this. PRs that aren't attached to Trac tickets in an open milestones are normally not even looked at.

@nvxbug

nvxbug commented Sep 30, 2026

Copy link
Copy Markdown
Author

Nobody on the security team told me that, the call was mine. Since it needs edit_theme_options without edit_css (multisite site admins, or admins under DISALLOW_UNFILTERED_HTML) I read it as hardening in the same bucket as the existing unfiltered_html check in these two sanitizers, but you're right that a capability bypass should have gone through HackerOne first. I'll report it there and let the security team decide how they want it handled. If they're fine with it as public hardening, I'll set up a wordpress.org account, open a Trac ticket and attach this PR to it. I'll leave this PR alone until then.

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