-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Global Styles: Allow arbitrary CSS, protect from KSES mangling #10641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Global Styles: Allow arbitrary CSS, protect from KSES mangling #10641
Conversation
STYLE tags may contain any raw text up to a closing STYLE tag https://html.spec.whatwg.org/multipage/parsing.html#generic-raw-text-element-parsing-algorithm
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
src/wp-includes/customize/class-wp-customize-custom-css-setting.php
Outdated
Show resolved
Hide resolved
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
Outdated
Show resolved
Hide resolved
Strictly speaking, a `STYLE` tag will be closed by a `STYLE` close tag. However, the CSS contents are concatenated together and the strict check for a STYLE close tag may later be invalidated by concatenation.
c0dab9b to
99b8733
Compare
src/wp-includes/customize/class-wp-customize-custom-css-setting.php
Outdated
Show resolved
Hide resolved
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
Outdated
Show resolved
Hide resolved
|
As I explored the space, this PR grew in scope. There are at least two concerns that I plan to split up:
|
Co-authored-by: Weston Ruter <westonruter@gmail.com>
|
YES! KSES indeed breaks this as can be seen in the multisite tests: That's almost certainly a missing |
The method serves no purpose and can be deprecated.
…custom-css-handling
Thank you for flagging that @ramonjd. This is an interesting case and I'd like to get your feedback. This depends on the work in #10656. That's another Core change to how How should we approach that? One option is to maintain the validation in such a way that unsafe CSS is not allowed, @dmsnell shared a suitable snippet in this comment. This relaxes the check and should be safe to include in Gutenberg today because the |
Dang! Can I take that back about syncing? Yeah, I think we'll have to have In my opinion, Dennis's comment is a safe bet.
Or, since it's plugin-only, use Dennis's new and improved version! Does that sound reasonable? |
|
I'm quite ignorant on a lot of this. The last change to that file in Gutenberg was WordPress/gutenberg@f77a466 (Sep 18, 2024).
|
|
Good questions:
For context, the global styles controller was created as a dedicated, bundled class because the Core equivalent was being extended and overridden so frequently, and referenced others classes like For example, the controller relies on My instinct is that Gutenberg needs to keep its changes for this reason. Folks are pretty good at migrating said changes to Core, and theoretically they should be synced in that direction (plugin > Core).
I reckon after this merges is fine. Not speaking specifically to this PR, but if Core introduces a bugfix/new feature in global styles controller is generally good to sync that back before the next plugin update so plugin users get the benefit (since the plugin uses it's own class
This sounds wholly reasonable to me. I appreciate you being on top of this! Thank you! |
|
@dmsnell and I discussed this recently. We decided to:
This is likely sufficient to allow the valid CSS folks want to use without introducing problems in Gutenberg for WordPress versions that won't include the improved safety when constructing style tags from #10656. |
This reverts commit e1322b0.
|
I've updated the check to disallow:
These changes should make the style tag contents safe to save, print, and concatenate with other arbitrary contents even without the STYLE tag change in #10656. They are safe to port to Gutenberg. (This was another approach I discussed with @dmsnell and it seems like the most appropriate.) |
ramonjd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is working well for me. Do folks have any other test cases or concerns?
I've prepped a Gutenberg PR over at
I think this could go in at any time.
| sprintf( | ||
| /* translators: %s is the CSS that was provided. */ | ||
| __( 'The CSS must not end in "%s".' ), | ||
| esc_html( substr( $css, $at ) ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to confirm that when I try to save unsafe CSS content (with a closing style tag) I see:
"The CSS must not contain \"<\/style>\"."
👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need good wording here. it doesn’t require the closing >. what @sirreal mentioned is more accurate, but I think not the most helpful either. it requires a case-insensitive match for <style followed by a tag name terminator, which could be a > or a whitespace character or a solidus /.
The CSS must not prematurely end the STYLE element: e.g.
</style>.
I’ve found it really hard to be terse and clear, but I anticipate the response, “I don’t have </style>, I have </style and swag/>.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's difficult to print a generic error message that's informative and doesn't look like a typo to the average user (</style seems like it's missing a character).
That's one reason I decided to use the more specific checks. It simplifies printing an informative and accurate error message:
The CSS must not end in "%s".
(This could be <, </,… </styl, etc.)
or
The CSS must not contain "%s".
(This could be </style>, </style , </style/, etc.)
Where %s is the problematic text (HTML escaped).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are unit tests that include the error messages.
The client doesn't do anything helpful with the error messages:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request enhances the Global Styles custom CSS handling to allow arbitrary CSS content while protecting it from corruption by WordPress KSES HTML filtering. The solution uses JSON encoding flags (JSON_HEX_TAG and JSON_HEX_AMP) to escape characters that could be mangled when KSES processes the post content as HTML.
Key changes:
- Improved CSS validation to allow legitimate CSS containing
<and&characters while blocking actual</style>closing tags - Added JSON encoding flags to escape potentially problematic characters in both REST API and KSES filter functions
- Comprehensive test coverage for validation logic including edge cases
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php |
Replaced strict regex validation with precise algorithm that allows CSS with HTML-like syntax while blocking style tag closure; added JSON_HEX_TAG and JSON_HEX_AMP flags to JSON encoding |
src/wp-includes/kses.php |
Added same JSON encoding flags to wp_filter_global_styles_post() for consistency with REST API |
tests/phpunit/tests/rest-api/rest-global-styles-controller.php |
Added comprehensive test coverage for new validation logic including allowed CSS patterns and various forms of disallowed style tag closures |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
Show resolved
Hide resolved
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
Show resolved
Hide resolved
src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Under some circumstances KSES would run post content filters and change the resulting content like this:
@property --animate { - syntax: "<custom-ident>"; + syntax: ""; inherits: true; initial-value: false; }The Custom CSS is stored as JSON-encoded data in post content. KSES filters this content as HTML.
I explored a change that would remove and restore the offending KSES filters when saving the custom CSS posts, however the simplest way of protecting the data is by escaping the JSON-encoded data so that it does not contain HTML-like syntax.
<>&and Unicode-escaped by the JSON flagsJSON_HEX_TAGandJSON_HEX_AMP.This PR does not change the Customizer Custom CSS handling. That will be done separately.
This depends on #10656 to ensure styles output is safely printed in the HTML (merged here).
Trac ticket: https://core.trac.wordpress.org/ticket/64418
To do:
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.