From b6bb9d70867bda47d3afb39f337786e43ec78e66 Mon Sep 17 00:00:00 2001 From: HasnainAshfaq Date: Sat, 8 Aug 2026 04:32:34 +0500 Subject: [PATCH 1/4] Formatting: Allow CSS transform and SVG shape functions in safecss_filter_attr(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #65457 added SVG presentation properties (transform, clip-path, fill, stroke, mask, marker-*) to the KSES CSS allowlist, but the two structures that govern their accepted value forms were not updated. This meant: - transform: rotate(45deg) — dropped (( character was rejected) - clip-path: url(#myClipper) — dropped (url() not validated for clip-path) - fill: url(#gradient1) — dropped (url() not validated for fill) Two changes: 1. Expand $css_url_data_types to include the SVG properties that accept url() references (clip-path, fill, stroke, mask, marker-*). This routes their values through the existing URL-validation path that already protects against javascript: and other bad protocols. 2. Extend the CSS function strip regex (the one that removes var(), calc(), etc. before the backslash/paren safety check) to also cover CSS transform functions (rotate, translate, scale, matrix, skew*, perspective) and CSS shape functions used in clip-path (inset, circle, ellipse, polygon, path). These are purely geometric/visual values with no script execution risk; the precedent is calc() (#46197), min/max (#55966), and CSS custom properties (#56353). Adds test cases for: - transform: rotate(), translate(), scale(), matrix(), skewX(), skewY() - Chained transform functions - clip-path: inset(), circle(), ellipse(), polygon() - clip-path: url(), fill: url(), mask: url(), marker-start/end: url() - Security regression: javascript: URLs in SVG url() references blocked Fixes #65832. Follow-up to #65457. --- src/wp-includes/kses.php | 12 +++++- tests/phpunit/tests/kses.php | 83 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 92f866b15daef..6bdd8a7abfc2a 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2906,6 +2906,16 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'list-style', 'list-style-image', + + // SVG presentation properties that accept url() references. + 'clip-path', + 'fill', + 'marker', + 'marker-end', + 'marker-mid', + 'marker-start', + 'mask', + 'stroke', ); /* @@ -3004,7 +3014,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { * Nested functions and parentheses are also removed, so long as the parentheses are balanced. */ $css_test_string = preg_replace( - '/\b(?:var|calc|min|max|minmax|clamp|repeat)(\((?:[^()]|(?1))*\))/', + '/\b(?:var|calc|min|max|minmax|clamp|repeat|rotate|rotateX|rotateY|rotateZ|rotate3d|translate|translateX|translateY|translateZ|translate3d|scale|scaleX|scaleY|scaleZ|scale3d|skew|skewX|skewY|matrix|matrix3d|perspective|inset|circle|ellipse|polygon|path)(\((?:[^()]|(?1))*\))/', '', $css_test_string ); diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index f560d88403524..2ac6b27ae4872 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1224,6 +1224,7 @@ public function test_wp_kses_attr_no_attributes_allowed_with_false() { * @ticket 64414 * @ticket 65457 * @ticket 64974 + * @ticket 65832 * * @dataProvider data_safecss_filter_attr * @@ -1766,6 +1767,88 @@ public function data_safecss_filter_attr() { 'css' => 'text-anchor: middle', 'expected' => 'text-anchor: middle', ), + // SVG transform functions (ticket #65832). + array( + 'css' => 'transform: rotate(45deg)', + 'expected' => 'transform: rotate(45deg)', + ), + array( + 'css' => 'transform: translate(10px, 20px)', + 'expected' => 'transform: translate(10px, 20px)', + ), + array( + 'css' => 'transform: scale(1.5)', + 'expected' => 'transform: scale(1.5)', + ), + array( + 'css' => 'transform: matrix(1, 0, 0, 1, 10, 20)', + 'expected' => 'transform: matrix(1, 0, 0, 1, 10, 20)', + ), + array( + 'css' => 'transform: skewX(30deg)', + 'expected' => 'transform: skewX(30deg)', + ), + array( + 'css' => 'transform: skewY(30deg)', + 'expected' => 'transform: skewY(30deg)', + ), + // Multiple transform functions chained. + array( + 'css' => 'transform: rotate(45deg) scale(1.5)', + 'expected' => 'transform: rotate(45deg) scale(1.5)', + ), + // transform: none is unchanged (regression control). + array( + 'css' => 'transform: none', + 'expected' => 'transform: none', + ), + // SVG clip-path shape functions (ticket #65832). + array( + 'css' => 'clip-path: inset(10px)', + 'expected' => 'clip-path: inset(10px)', + ), + array( + 'css' => 'clip-path: circle(50%)', + 'expected' => 'clip-path: circle(50%)', + ), + array( + 'css' => 'clip-path: ellipse(25% 40% at 50% 50%)', + 'expected' => 'clip-path: ellipse(25% 40% at 50% 50%)', + ), + array( + 'css' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', + 'expected' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', + ), + // SVG url() references for allowlisted properties (ticket #65832). + array( + 'css' => 'clip-path: url(#myClipper)', + 'expected' => 'clip-path: url(#myClipper)', + ), + array( + 'css' => 'fill: url(#gradient1)', + 'expected' => 'fill: url(#gradient1)', + ), + array( + 'css' => 'mask: url(#myMask)', + 'expected' => 'mask: url(#myMask)', + ), + array( + 'css' => 'marker-start: url(#arrowStart)', + 'expected' => 'marker-start: url(#arrowStart)', + ), + array( + 'css' => 'marker-end: url(#arrowEnd)', + 'expected' => 'marker-end: url(#arrowEnd)', + ), + // Disallow javascript: URLs in SVG url() references (security regression). + array( + 'css' => 'fill: url(javascript:alert(1))', + 'expected' => '', + ), + array( + 'css' => 'clip-path: url(javascript:alert(1))', + 'expected' => '', + ), ); } From cf8c967ec6da85c4220d7b2709d53ecb48d1b765 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:53:04 +0900 Subject: [PATCH 2/4] Add more test cases Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/phpunit/tests/kses.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 2ac6b27ae4872..02dc7768097f4 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1840,6 +1840,18 @@ public function data_safecss_filter_attr() { 'css' => 'marker-end: url(#arrowEnd)', 'expected' => 'marker-end: url(#arrowEnd)', ), + array( + 'css' => 'marker-mid: url(#arrowMid)', + 'expected' => 'marker-mid: url(#arrowMid)', + ), + array( + 'css' => 'marker: url(#marker1)', + 'expected' => 'marker: url(#marker1)', + ), + array( + 'css' => 'stroke: url(#strokeGradient)', + 'expected' => 'stroke: url(#strokeGradient)', + ), // Disallow javascript: URLs in SVG url() references (security regression). array( 'css' => 'fill: url(javascript:alert(1))', From 7dd8d6d2463376d789135470016aa592bf2901f7 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Sun, 9 Aug 2026 19:18:27 +0900 Subject: [PATCH 3/4] Docs: Update safecss_filter_attr() changelog for the new 7.1.0 support. Documents the transform functions, `clip-path` basic shapes, and SVG element reference URLs added in the previous commit. Follow-up to #65832. Co-Authored-By: Claude --- src/wp-includes/kses.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 6bdd8a7abfc2a..c33610b4f0f61 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -2638,6 +2638,8 @@ function kses_init() { * @since 6.6.0 Added support for `grid-column`, `grid-row`, and `container-type`. * @since 6.9.0 Added support for `white-space`. * @since 7.1.0 Extended gradient support to allow any single-level nested function. + * Added support for transform functions, `clip-path` basic shapes, + * and URLs in the SVG element reference properties. * * @param string $css A string of CSS rules, decoded from an HTML `style` attribute. * @param string $deprecated Not used. From d0b3cc664715574c808c745221178423c8da2f0b Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Sun, 9 Aug 2026 19:25:45 +0900 Subject: [PATCH 4/4] Formatting: Allow the remaining CSS basic shape functions in safecss_filter_attr(). Co-Authored-By: Claude --- src/wp-includes/kses.php | 13 ++++++++++++- tests/phpunit/tests/kses.php | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index c33610b4f0f61..9cd0f50f22487 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -3016,7 +3016,18 @@ function safecss_filter_attr( $css, $deprecated = '' ) { * Nested functions and parentheses are also removed, so long as the parentheses are balanced. */ $css_test_string = preg_replace( - '/\b(?:var|calc|min|max|minmax|clamp|repeat|rotate|rotateX|rotateY|rotateZ|rotate3d|translate|translateX|translateY|translateZ|translate3d|scale|scaleX|scaleY|scaleZ|scale3d|skew|skewX|skewY|matrix|matrix3d|perspective|inset|circle|ellipse|polygon|path)(\((?:[^()]|(?1))*\))/', + '/\b(?:' + // General purpose value functions. + . 'var|calc|min|max|minmax|clamp|repeat' + // Transform functions. + . '|matrix|matrix3d|perspective' + . '|rotate|rotate3d|rotateX|rotateY|rotateZ' + . '|scale|scale3d|scaleX|scaleY|scaleZ' + . '|skew|skewX|skewY' + . '|translate|translate3d|translateX|translateY|translateZ' + // Basic shape functions, as used by `clip-path`. + . '|circle|ellipse|inset|path|polygon|rect|shape|xywh' + . ')(\((?:[^()]|(?1))*\))/', '', $css_test_string ); diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 02dc7768097f4..b26bbd307d6aa 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1819,6 +1819,27 @@ public function data_safecss_filter_attr() { 'css' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', 'expected' => 'clip-path: polygon(50% 0%, 100% 100%, 0% 100%)', ), + array( + 'css' => "clip-path: path('M 0 0 L 100 0 L 50 100 Z')", + 'expected' => "clip-path: path('M 0 0 L 100 0 L 50 100 Z')", + ), + array( + 'css' => 'clip-path: rect(0 100% 100% 0)', + 'expected' => 'clip-path: rect(0 100% 100% 0)', + ), + array( + 'css' => 'clip-path: xywh(0 0 100% 100% round 10px)', + 'expected' => 'clip-path: xywh(0 0 100% 100% round 10px)', + ), + array( + 'css' => 'clip-path: shape(from 0 0, line to 100% 0, line to 50% 100%, close)', + 'expected' => 'clip-path: shape(from 0 0, line to 100% 0, line to 50% 100%, close)', + ), + // Nested functions within a basic shape are allowed. + array( + 'css' => 'clip-path: inset(calc(10px + 1em) round var(--radius))', + 'expected' => 'clip-path: inset(calc(10px + 1em) round var(--radius))', + ), // SVG url() references for allowlisted properties (ticket #65832). array( 'css' => 'clip-path: url(#myClipper)',