Skip to content

Formatting: Allow CSS transform and SVG shape functions in safecss_filter_attr() - #12942

Open
HasnainAshfaq wants to merge 2 commits into
WordPress:trunkfrom
HasnainAshfaq:feature/65832-kses-svg-css-functions
Open

Formatting: Allow CSS transform and SVG shape functions in safecss_filter_attr()#12942
HasnainAshfaq wants to merge 2 commits into
WordPress:trunkfrom
HasnainAshfaq:feature/65832-kses-svg-css-functions

Conversation

@HasnainAshfaq

Copy link
Copy Markdown

Follow-up to #65457 / #12169. Fixes #65832.

The problem

#65457 added 20+ SVG presentation properties to the KSES CSS allowlist, but left two internal structures unchanged. This means the new properties are allowlisted in name only — their functional values are still stripped:

Declaration Before this PR Expected
transform: rotate(45deg) stripped preserved
transform: translate(10px, 20px) stripped preserved
clip-path: url(#myClipper) stripped preserved
fill: url(#gradient1) stripped preserved
mask: url(#myMask) stripped preserved
transform: none preserved ✓ preserved ✓
fill: #ff0000 preserved ✓ preserved ✓

The failure mode is the ( character check at kses.php:3015. After stripping known-safe functions (var, calc, etc.), any remaining ( causes the whole declaration to be dropped. Transform functions like rotate(45deg) were never added to that strip list.

The second failure: clip-path: url(#id) and fill: url(#gradient) need the URL-validation code path, which only runs for properties listed in $css_url_data_types. Those SVG properties were never added there either.

The fix

1. Expand $css_url_data_types (9 lines added) to include clip-path, fill, stroke, mask, marker, marker-start, marker-mid, marker-end. This routes their url() values through the existing URL validator — which already blocks javascript:, vbscript:, and other bad protocols via wp_kses_bad_protocol(). No new security surface; the same gate that protects background-image: url(...) now protects these too.

2. Extend the CSS function strip regex to cover:

  • CSS transform functions: rotate, rotateX/Y/Z, rotate3d, translate, translateX/Y/Z, translate3d, scale, scaleX/Y/Z, scale3d, skew, skewX, skewY, matrix, matrix3d, perspective
  • CSS shape functions (for clip-path): inset, circle, ellipse, polygon, path

These are purely geometric/visual — no script execution risk. Precedent: calc() added in #46197 (5.8), min()/max() in #55966 (6.1), CSS custom properties in #56353 (6.1).

Tests

Added 20 cases to data_safecss_filter_attr():

  • Transform functions: rotate(), translate(), scale(), matrix(), skewX(), skewY()
  • Chained: rotate(45deg) scale(1.5)
  • Regression control: transform: none (function-free value, was already passing)
  • clip-path shapes: inset(), circle(), ellipse(), polygon()
  • SVG url() references: clip-path: url(#id), fill: url(#id), mask: url(#id), marker-start: url(#id), marker-end: url(#id)
  • Security regressions: fill: url(javascript:alert(1))"", clip-path: url(javascript:alert(1))""

Full kses group: 462 tests, 1537 assertions — all passing.


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


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.

…lter_attr().

#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.
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Aug 7, 2026

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.

@irozum irozum left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This looks like a clean, well-targeted fix — it directly implements the fix that ticket #65832 itself proposed, completing the two structures ($css_url_data_types and the function-strip regex) that #65457/#12169 left unsynced with the new SVG allowlist.

Checked out the branch and ran the full kses group locally (462 tests, 1537 assertions, all green), plus phpcs lint:errors on both changed files (clean). Also reproduced the bug by hand via wp_kses_post(): on trunk, <p style="transform:rotate(45deg)">x</p> has its entire style attribute stripped; with this patch it's preserved, while fill:url(javascript:alert(1)) still correctly resolves to an empty declaration. I also grepped the other core callers of safecss_filter_attr() (block supports, WP_Theme_JSON, several core blocks) — the change only loosens previously fail-closed values, so there's no back-compat concern for existing callers.

One small non-blocking note: stroke and bare marker/marker-mid are added to $css_url_data_types but don't get a dedicated url() test case (only fill, clip-path, mask, marker-start, marker-end do). The code path is shared and clearly correct from the other cases, so this isn't blocking — just worth rounding out for completeness.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Updates safecss_filter_attr() (KSES safe inline-style sanitizer) so that newly allowlisted SVG/CSS presentation properties can retain common functional values, specifically transform/shape functions and url() references, and adds PHPUnit coverage for these cases.

Changes:

  • Route additional SVG presentation properties through the existing url() validation logic by expanding $css_url_data_types.
  • Extend the CSS function-stripping regex to include transform and clip-path shape functions so balanced parentheses don’t cause declarations to be dropped.
  • Add new PHPUnit data-provider cases covering transform functions, clip-path shapes, and SVG url() references (including javascript: regressions).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/wp-includes/kses.php Adds SVG properties to URL-validated list and expands the function-stripping regex in safecss_filter_attr().
tests/phpunit/tests/kses.php Adds new data_safecss_filter_attr() cases for transforms, clip-path shapes, and SVG url() references.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/phpunit/tests/kses.php
Comment thread src/wp-includes/kses.php
*/
$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))*\))/',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The observation is correct, but I'm not sure it should be addressed here. The pattern has always been case-sensitive. Whether to accept uppercase function names is out of scope for this PR and would be better handled in its own ticket.

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.

Agreed that it's a separate issue and shouldn't block this one, but before merging, let's make sure this is raised as a separate ticket. Solution may be simple (just adding the i regex flag) but we still need to add some tests and ensure it doesn't break anything.

@t-hamano t-hamano left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the follow-up! It looks good to me.

Comment thread src/wp-includes/kses.php
* @since 6.5.0 Added support for `background-repeat`.
* @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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @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.

Let's update the docblock.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@t-hamano
t-hamano requested review from mcsf, tyxla and westonruter August 8, 2026 06:58
Comment thread src/wp-includes/kses.php
*/
$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))*\))/',

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.

Some of the functions I don't see here are: rect(), xywh(), and shape(). Should we add those too? Sounds like we should have a more thorough lookup to ensure we're not missing any others.

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.

5 participants