Skip to content

KSES: Add support for rgb(a) color#10923

Open
t-hamano wants to merge 8 commits intoWordPress:trunkfrom
t-hamano:fix/kses-allow-background-color
Open

KSES: Add support for rgb(a) color#10923
t-hamano wants to merge 8 commits intoWordPress:trunkfrom
t-hamano:fix/kses-allow-background-color

Conversation

@t-hamano
Copy link
Copy Markdown
Contributor

@t-hamano t-hamano commented Feb 13, 2026

Note

This PR is the same as #3097. I submitted this new PR because I accidentally closed #3097 and it can no longer be reopened.

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

Related gutenberg issue: WordPress/gutenberg#39402
Related gutenberg PR: WordPress/gutenberg#39488


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
Copy link
Copy Markdown

github-actions Bot commented Feb 13, 2026

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 wildworks, 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.

@t-hamano t-hamano force-pushed the fix/kses-allow-background-color branch from 3e19b4e to 797ab0e Compare February 13, 2026 11:33
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php
Comment on lines 2714 to 2878
'cursor',
'direction',
'float',
'list-style-type',
'object-fit',
'object-position',
'opacity',
'overflow',
'vertical-align',
'writing-mode',

'position',
'top',
'right',
'bottom',
'left',
'z-index',
'box-shadow',
'aspect-ratio',
'container-type',

// Custom CSS properties.
'--*',
)
);

/*
* CSS attributes that accept URL data types.
*
* This is in accordance to the CSS spec and unrelated to
* the sub-set of supported attributes above.
*
* See: https://developer.mozilla.org/en-US/docs/Web/CSS/url
*/
$css_url_data_types = array(
'background',
'background-image',

'cursor',
'filter',

'list-style',
'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.
*
*/
$css_gradient_data_types = array(
'background',
'background-image',
);

if ( empty( $allowed_attr ) ) {
return $css;
}

$css = '';
foreach ( $css_array as $css_item ) {
if ( '' === $css_item ) {
continue;
}

$css_item = trim( $css_item );
$css_test_string = $css_item;
$found = false;
$url_attr = false;
$color_attr = false;
$gradient_attr = false;
$is_custom_var = false;

if ( ! str_contains( $css_item, ':' ) ) {
$found = true;
} else {
$parts = explode( ':', $css_item, 2 );
$css_selector = trim( $parts[0] );

// Allow assigning values to CSS variables.
if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) {
$allowed_attr[] = $css_selector;
$is_custom_var = true;
}

if ( in_array( $css_selector, $allowed_attr, true ) ) {
$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 ) {
$css_value = trim( $parts[1] );
$url_attr = str_starts_with( $css_value, 'url(' );
$gradient_attr = str_contains( $css_value, '-gradient(' );
}
}

if ( $found && $url_attr ) {
// Simplified: matches the sequence `url(*)`.
preg_match_all( '/url\([^)]+\)/', $parts[1], $url_matches );

foreach ( $url_matches[0] as $url_match ) {
// Clean up the URL from each of the matches above.
preg_match( '/^url\(\s*([\'\"]?)(.*)(\g1)\s*\)$/', $url_match, $url_pieces );

if ( empty( $url_pieces[2] ) ) {
$found = false;
break;
}

$url = trim( $url_pieces[2] );

if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) {
$found = false;
break;
} else {
// Remove the whole `url(*)` bit that was matched above from the CSS.
$css_test_string = str_replace( $url_match, '', $css_test_string );
}
}
}

if ( $found && $gradient_attr ) {
$css_value = trim( $parts[1] );
if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient\(([^()]|rgb[a]?\([^()]*\))*\)$/', $css_value ) ) {
// Remove the whole `gradient` bit that was matched above from the CSS.
$css_test_string = str_replace( $css_value, '', $css_test_string );
}
}

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.
Copy link
Copy Markdown
Member

@westonruter westonruter Apr 27, 2026

Choose a reason for hiding this comment

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

Note that border and background won't work in cases like:

  • border: 1px solid rgb(0,0,0)
  • background: rgb(0,0,0) center

Because the regex has a below has ^ and $ anchors.

These anchors could be removed to match url() handling:

preg_match_all( '/url\([^)]+\)/', $parts[1], $url_matches );

(Props Claude Code Opus 4.7 for insight.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. Fixed in 0f20120

Comment on lines +1364 to +1367
array(
'css' => 'color: rgb(255, 50%, 0)',
'expected' => 'color: rgb(255, 50%, 0)',
),
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.

Mixing integers with a percentage here isn't valid:

body {
   background: rgb(255, 50%, 0);
}
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for noticing, I have corrected the invalid values in 129c006

t-hamano and others added 3 commits April 30, 2026 16:33
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) <noreply@anthropic.com>
`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) <noreply@anthropic.com>
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