Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c124eb5
Update wp_custom_css_cb to rely on HTML API for safe SCRIPT tag print…
sirreal Dec 17, 2025
e055156
Wrap customizer CSS test in newlines
sirreal Dec 17, 2025
33f9616
Use HTML API for style tags in script-loader
sirreal Dec 17, 2025
606539e
Use HTML Tag Processor to produce WP_Styles style tags
sirreal Dec 17, 2025
c938d4c
Use HTML Tag Processor for STYLE tags in theme.php
sirreal Dec 17, 2025
dd919f1
Build font style tags with HTML API
sirreal Dec 23, 2025
d29900a
PICKME: Update font tests to use semantic HTML comparison
sirreal Dec 23, 2025
6c6a72b
Use HTML API for hide header text
sirreal Dec 23, 2025
aad4744
Revert "Use HTML API for hide header text"
sirreal Dec 23, 2025
c3ae9a9
Merge branch 'trunk' into styles/use-html-api-for-style-tags
sirreal Dec 26, 2025
4e88745
Fix lint
sirreal Dec 26, 2025
d296d6c
Merge branch 'trunk' into styles/use-html-api-for-style-tags
sirreal Dec 29, 2025
d8a6f02
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 29, 2025
67500e0
Allow arbitrary customizer custom CSS
sirreal Dec 29, 2025
01b6fb8
Fix lints
sirreal Dec 29, 2025
0141653
Restore STYLE tag trailing newline
sirreal Dec 29, 2025
6585099
Restore STYLE tag trailing newlines in theme.php
sirreal Dec 29, 2025
b0020d8
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 29, 2025
407d43f
Move trailing newline out of Tag Processor
sirreal Dec 30, 2025
ffd5b45
Merge branch 'styles/use-html-api-for-style-tags' into 64418/customiz…
sirreal Dec 30, 2025
8268865
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Dec 30, 2025
9e7e04d
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 14, 2026
879c5d1
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
b340650
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
c5c8507
Update test to remove type attribute
sirreal Jan 15, 2026
6932b6c
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 15, 2026
f515c02
Merge branch 'trunk' into 64418/customizer-allow-arbitrary-custom-css
sirreal Jan 16, 2026
cbf7e21
Add covers annotations to new test
sirreal Jan 16, 2026
b13930f
Use verbose variable name for clarity
sirreal Jan 16, 2026
5a414b6
Expand explanatory comment
sirreal Jan 16, 2026
2407549
Move new test to end of file
sirreal Jan 16, 2026
ae66f56
Add test that at-property rule passes validation and is printed as de…
sirreal Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,35 +144,6 @@ public function value() {
return $value;
}

/**
* Validate a received value for being valid CSS.
*
* Checks for imbalanced braces, brackets, and comments.
* Notifications are rendered when the customizer state is saved.
*
* @since 4.7.0
* @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor.
* @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support.
*
* @param string $value CSS to validate.
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
*/
public function validate( $value ) {
// Restores the more descriptive, specific name for use within this method.
$css = $value;

$validity = new WP_Error();

if ( preg_match( '#</?\w+#', $css ) ) {
$validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
}

if ( ! $validity->has_errors() ) {
$validity = parent::validate( $css );
}
return $validity;
}

/**
* Store the CSS setting value in the custom_css custom post type for the stylesheet.
*
Expand Down
14 changes: 14 additions & 0 deletions src/wp-includes/theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,16 @@ function wp_update_custom_css_post( $css, $args = array() ) {

// Update post if it already exists, otherwise create a new one.
$post = wp_get_custom_css_post( $args['stylesheet'] );

/**
* Temporarily remove the {@see wp_filter_post_kses()} `content_save_pre` filter. CSS text is
* stored in post_content, but the filter would process it as HTML and may mangle valid CSS.
*/
$kses_filter_priority = has_filter( 'content_save_pre', 'wp_filter_post_kses' );
if ( false !== $kses_filter_priority ) {
remove_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority );
}

if ( $post ) {
$post_data['ID'] = $post->ID;
$r = wp_update_post( wp_slash( $post_data ), true );
Expand All @@ -2153,6 +2163,10 @@ function wp_update_custom_css_post( $css, $args = array() ) {
}
}

if ( false !== $kses_filter_priority ) {
add_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority );
}

if ( is_wp_error( $r ) ) {
return $r;
}
Expand Down
68 changes: 50 additions & 18 deletions tests/phpunit/tests/customize/custom-css-setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,27 +375,59 @@ public function filter_update_custom_css_data( $data, $args ) {
}

/**
* Tests that validation errors are caught appropriately.
* Ensure that dangerous STYLE tag contents do not break HTML output.
*
* Note that the $validity \WP_Error object must be reset each time
* as it picks up the Errors and passes them to the next assertion.
*
* @covers WP_Customize_Custom_CSS_Setting::validate
* @ticket 64418
* @covers ::wp_update_custom_css_post
* @covers ::wp_custom_css_cb
*/
public function test_validate() {

// Empty CSS throws no errors.
$result = $this->setting->validate( '' );
$this->assertTrue( $result );
public function test_wp_custom_css_cb_escapes_dangerous_html() {
wp_update_custom_css_post(
'*::before { content: "</style><script>alert(1)</script>"; }',
array(
'stylesheet' => $this->setting->stylesheet,
)
);
$output = get_echo( 'wp_custom_css_cb' );
$expected = <<<'HTML'
<style id="wp-custom-css">
*::before { content: "\3c\2fstyle><script>alert(1)</script>"; }
</style>
HTML;
$this->assertEqualHTML( $expected, $output );
}

// Basic, valid CSS throws no errors.
$basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }';
$result = $this->setting->validate( $basic_css );
$this->assertTrue( $result );
/**
* @ticket 64418
* @covers WP_Customize_Custom_CSS_Setting::validate
*/
public function test_validate_accepts_css_property_at_rule() {
$css = <<<'CSS'
@property --animate {
syntax: "<custom-ident>";
inherits: true;
initial-value: false;
}
CSS;
$this->assertTrue( $this->setting->validate( $css ) );
}

// Check for markup.
$unclosed_comment = $basic_css . '</style>';
$result = $this->setting->validate( $unclosed_comment );
$this->assertArrayHasKey( 'illegal_markup', $result->errors );
/**
* @ticket 64418
* @covers ::wp_update_custom_css_post
* @covers ::wp_custom_css_cb
*/
public function test_save_and_print_property_at_rule() {
$css = <<<'CSS'
@property --animate {
syntax: "<custom-ident>";
inherits: true;
initial-value: false;
}
CSS;
wp_update_custom_css_post( $css, array( 'stylesheet' => $this->setting->stylesheet ) );
$output = get_echo( 'wp_custom_css_cb' );
$expected = "<style id='wp-custom-css'>\n{$css}\n</style>\n";
$this->assertEqualHTML( $expected, $output );
}
}
Loading