From 797ab0e07f2dd1650855d37654b9b761c31858b0 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Fri, 13 Feb 2026 20:01:07 +0900 Subject: [PATCH 1/6] KSES: Add support for rgb(a) color --- src/wp-includes/kses.php | 34 ++++++++++++++ tests/phpunit/tests/kses.php | 90 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index ed2f96503ac27..2b1810e0a3957 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2756,6 +2756,28 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'list-style-image', ); + /* + * CSS attributes that accept rgb(a) color data types. + * + */ + $css_color_data_types = array( + 'color', + + 'border', + 'border-color', + 'border-right', + 'border-right-color', + 'border-bottom', + 'border-bottom-color', + 'border-left', + 'border-left-color', + 'border-top', + 'border-top-color', + + 'background', + 'background-color', + ); + /* * CSS attributes that accept gradient data types. * @@ -2779,6 +2801,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { $css_test_string = $css_item; $found = false; $url_attr = false; + $color_attr = false; $gradient_attr = false; $is_custom_var = false; @@ -2798,6 +2821,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { $found = true; $url_attr = in_array( $css_selector, $css_url_data_types, true ); $gradient_attr = in_array( $css_selector, $css_gradient_data_types, true ); + $color_attr = in_array( $css_selector, $css_color_data_types, true ); } if ( $is_custom_var ) { @@ -2840,6 +2864,16 @@ function safecss_filter_attr( $css, $deprecated = '' ) { } } + if ( $found && $color_attr ) { + $css_value = trim( $parts[1] ); + $comma_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; + $space_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; + + if ( preg_match( $comma_syntax, $css_value ) || preg_match( $space_syntax, $css_value ) ) { + $css_test_string = str_replace( $css_value, '', $css_test_string ); + } + } + if ( $found ) { /* * Allow CSS functions like var(), calc(), etc. by removing them from the test string. diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index dc01b5dfe2979..decffe2d32358 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1203,6 +1203,31 @@ public function data_safecss_filter_attr() { 'css' => 'height: expression( body.scrollTop + 50 + "px" )', 'expected' => '', ), + // RGBA background color are allowed. + array( + 'css' => 'background-color: rgba(0,0,0,0)', + 'expected' => 'background-color: rgba(0,0,0,0)', + ), + array( + 'css' => 'background-color: rgba(0, 0, 0, 0)', + 'expected' => 'background-color: rgba(0, 0, 0, 0)', + ), + array( + 'css' => 'background-color: rgba(100, 100, 100, 0)', + 'expected' => 'background-color: rgba(100, 100, 100, 0)', + ), + array( + 'css' => 'background-color: rgba(10%, 10%, 10%, 0)', + 'expected' => 'background-color: rgba(10%, 10%, 10%, 0)', + ), + array( + 'css' => 'background-color: rgba(0, 0, 0, 0.1)', + 'expected' => 'background-color: rgba(0, 0, 0, 0.1)', + ), + array( + 'css' => 'background-color: rgba(0, 0, 0, .1)', + 'expected' => 'background-color: rgba(0, 0, 0, .1)', + ), // RGB color values are not allowed. array( 'css' => 'color: rgb( 100, 100, 100 )', @@ -1333,6 +1358,71 @@ public function data_safecss_filter_attr() { 'css' => 'gap: 10px;column-gap: 5px;row-gap: 20px', 'expected' => 'gap: 10px;column-gap: 5px;row-gap: 20px', ), + // RGB color. + array( + 'css' => 'color: rgb(255, 0, 0)', + 'expected' => 'color: rgb(255, 0, 0)', + ), + array( + 'css' => 'color: rgb(255 0 0)', + 'expected' => 'color: rgb(255 0 0)', + ), + array( + 'css' => 'color: rgb(100%, 0%, 50%)', + 'expected' => 'color: rgb(100%, 0%, 50%)', + ), + array( + 'css' => 'color: rgb(255, 50%, 0)', + 'expected' => 'color: rgb(255, 50%, 0)', + ), + // RGBA color. + array( + 'css' => 'color: rgba(255, 128, 0, 0.5)', + 'expected' => 'color: rgba(255, 128, 0, 0.5)', + ), + array( + 'css' => 'color: rgb(255 128 0 / 50%)', + 'expected' => 'color: rgb(255 128 0 / 50%)', + ), + // RGB color with extra whitespace. + array( + 'css' => 'color: rgb( 255 , 128 , 0 )', + 'expected' => 'color: rgb( 255 , 128 , 0 )', + ), + // RGB background color. + array( + 'css' => 'background-color: rgb(200, 100, 50)', + 'expected' => 'background-color: rgb(200, 100, 50)', + ), + // RGBA border color. + array( + 'css' => 'border-color: rgba(100, 200, 300, 0.8)', + 'expected' => 'border-color: rgba(100, 200, 300, 0.8)', + ), + // Malformed RGB color, invalid number of values. + array( + 'css' => 'color: rgb(255, 128, 0, 0.5, 100)', + 'expected' => '', + ), + array( + 'css' => 'color: rgb(255, 128)', + 'expected' => '', + ), + // Malformed RGB color, non-numeric values. + array( + 'css' => 'color: rgb(red, green, blue)', + 'expected' => '', + ), + // Malformed RGB color, unmatched parentheses. + array( + 'css' => 'color: rgb(255, 128, 0', + 'expected' => '', + ), + // Malformed RGB color, empty values. + array( + 'css' => 'color: rgb(, , )', + 'expected' => '', + ), // Margin and padding logical properties introduced in 6.1. array( 'css' => 'margin-block-start: 1px;margin-block-end: 2px;margin-inline-start: 3px;margin-inline-end: 4px;', From c3b8ff0711a4300e211e314881a0621b7c238e20 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:59:20 +0900 Subject: [PATCH 2/6] Remove unnecessary PHPDoc lines Co-authored-by: Weston Ruter --- src/wp-includes/kses.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 2b1810e0a3957..a41b2fa31fb70 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2758,7 +2758,6 @@ function safecss_filter_attr( $css, $deprecated = '' ) { /* * CSS attributes that accept rgb(a) color data types. - * */ $css_color_data_types = array( 'color', From 409cf7f565a268230b44365113a159a0b5adcab8 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:59:37 +0900 Subject: [PATCH 3/6] Fix PHPCS Error Co-authored-by: Weston Ruter --- src/wp-includes/kses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index a41b2fa31fb70..1ada9128530d6 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2864,7 +2864,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { } if ( $found && $color_attr ) { - $css_value = trim( $parts[1] ); + $css_value = trim( $parts[1] ); $comma_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; $space_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; From e8eff1399c954c5b4d0ae39742800380468006a9 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 27 Apr 2026 18:49:10 +0900 Subject: [PATCH 4/6] Remove obsolete test cases for disallowed RGB(A) colors --- tests/phpunit/tests/kses.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index decffe2d32358..a861316bb3a81 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1228,16 +1228,6 @@ public function data_safecss_filter_attr() { 'css' => 'background-color: rgba(0, 0, 0, .1)', 'expected' => 'background-color: rgba(0, 0, 0, .1)', ), - // RGB color values are not allowed. - array( - 'css' => 'color: rgb( 100, 100, 100 )', - 'expected' => '', - ), - // RGBA color values are not allowed. - array( - 'css' => 'color: rgb( 100, 100, 100, .4 )', - 'expected' => '', - ), // Allow min(). array( 'css' => 'width: min(50%, 400px)', From 0f201200d4109a3e3c2b29d81463aadd72632122 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 30 Apr 2026 16:41:42 +0900 Subject: [PATCH 5/6] KSES: Allow RGB(A) colors inside shorthand property values. Drop the `^`/`$` anchors on the rgb()/rgba() match in `safecss_filter_attr()` and switch to `preg_match_all`, mirroring how `url()` is handled. This lets shorthand values like `border: 1px solid rgb(0, 0, 0)` and `background: rgb(0, 0, 0) center` pass sanitization, while malformed colors still fail because the leftover `(` is rejected downstream. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/wp-includes/kses.php | 14 +++++++++----- tests/phpunit/tests/kses.php | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 11d3446c469a3..094b6c4724907 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2864,12 +2864,16 @@ function safecss_filter_attr( $css, $deprecated = '' ) { } if ( $found && $color_attr ) { - $css_value = trim( $parts[1] ); - $comma_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; - $space_syntax = '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i'; + // Simplified: matches the sequence `rgb(*)` or `rgba(*)`. + $comma_syntax = '/rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)/i'; + $space_syntax = '/rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)/i'; - if ( preg_match( $comma_syntax, $css_value ) || preg_match( $space_syntax, $css_value ) ) { - $css_test_string = str_replace( $css_value, '', $css_test_string ); + preg_match_all( $comma_syntax, $parts[1], $color_matches_comma ); + preg_match_all( $space_syntax, $parts[1], $color_matches_space ); + + foreach ( array_merge( $color_matches_comma[0], $color_matches_space[0] ) as $color_match ) { + // Remove the whole `rgb(*)` or `rgba(*)` bit that was matched above from the CSS. + $css_test_string = str_replace( $color_match, '', $css_test_string ); } } diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index a861316bb3a81..27ee30aa7d616 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1389,6 +1389,21 @@ public function data_safecss_filter_attr() { 'css' => 'border-color: rgba(100, 200, 300, 0.8)', 'expected' => 'border-color: rgba(100, 200, 300, 0.8)', ), + // RGB color in `border` shorthand. + array( + 'css' => 'border: 1px solid rgb(0, 0, 0)', + 'expected' => 'border: 1px solid rgb(0, 0, 0)', + ), + // RGBA color in `border` shorthand. + array( + 'css' => 'border: 1px solid rgba(0, 0, 0, 0.5)', + 'expected' => 'border: 1px solid rgba(0, 0, 0, 0.5)', + ), + // RGB color in `background` shorthand. + array( + 'css' => 'background: rgb(0, 0, 0) center', + 'expected' => 'background: rgb(0, 0, 0) center', + ), // Malformed RGB color, invalid number of values. array( 'css' => 'color: rgb(255, 128, 0, 0.5, 100)', From 129c006d59d887c0ccd245259d6989f7c14ddd93 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Thu, 30 Apr 2026 16:49:32 +0900 Subject: [PATCH 6/6] Tests: Use spec-compliant RGB(A) values in `safecss_filter_attr` tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rgb(255, 50%, 0)` mixes numbers and percentages within the legacy comma-separated syntax, which is invalid per CSS Color Level 4 — only the modern space-separated syntax permits mixing. Switch the case to `rgb(255 50% 0)`. Also bring `rgba(100, 200, 300, 0.8)` into the 0–255 range (`250`) so the fixtures reflect realistic, valid colors rather than relying on browser clamping. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/phpunit/tests/kses.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 27ee30aa7d616..3c9b87c86456d 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1362,8 +1362,8 @@ public function data_safecss_filter_attr() { 'expected' => 'color: rgb(100%, 0%, 50%)', ), array( - 'css' => 'color: rgb(255, 50%, 0)', - 'expected' => 'color: rgb(255, 50%, 0)', + 'css' => 'color: rgb(255 50% 0)', + 'expected' => 'color: rgb(255 50% 0)', ), // RGBA color. array( @@ -1386,8 +1386,8 @@ public function data_safecss_filter_attr() { ), // RGBA border color. array( - 'css' => 'border-color: rgba(100, 200, 300, 0.8)', - 'expected' => 'border-color: rgba(100, 200, 300, 0.8)', + 'css' => 'border-color: rgba(100, 200, 250, 0.8)', + 'expected' => 'border-color: rgba(100, 200, 250, 0.8)', ), // RGB color in `border` shorthand. array(