Skip to content

KSES: Allow picture, source and responsive img attributes in post content - #6184

Open
adamsilverstein wants to merge 3 commits into
WordPress:trunkfrom
adamsilverstein:ticket/29807
Open

adamsilverstein wants to merge 3 commits into
WordPress:trunkfrom
adamsilverstein:ticket/29807

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Feb 26, 2024 •

Copy link
Copy Markdown
Member

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

Third and last PR in the stack, rebased down from the earlier version of this branch (the old history is tagged locally if anyone needs it). It builds on #14811 (the srcset parser) and #14812 (wiring it into both KSES parsers by element), so the diff shows all three commits until those land. The change in this PR is only the last one.

With per-candidate srcset sanitization in place, this allows the responsive image markup in post content:

  • picture (no attributes)
  • source with height, media, sizes, src, srcset, type, width - src is there so video and audio sources keep their URL rather than surviving as a bare element
  • img gains decoding, fetchpriority, sizes and srcset, and the list is alphabetized

Everything the review on the previous version asked for that was about the parser or the "multi-URI" abstraction is handled in the first two PRs. Dropped entirely since trunk moved: the Tag Processor set_attribute() change (srcset was never added to wp_kses_uri_attributes(), so there is nothing to work around), wp_kses_multi_uri_attributes() and its filter.

Not in this stack, on purpose: a tag/namespace-aware filter for extending URL sanitization (dmsnell's third item - worth its own ticket once the hard-coded version has settled), and swapping wp_kses_bad_protocol() for the HTML spec's sanitization algorithm javascript: detection, which would be a change confined to wp_kses_sanitize_srcset() later. imagesrcset on link is supported by the dispatcher but link is not allowed in post content, so nothing changes there.

Testing

npm run test:php -- --group kses. The new test_responsive_image_markup_in_post_content runs each case through wp_kses_post() under both parsers. For a manual check, as a Contributor paste into a post:

<picture><source srcset="a.avif 1x, b.avif 2x" type="image/avif"><img src="a.jpg" srcset="a.jpg 1x, javascript:alert(1) 2x" sizes="100vw" alt=""></picture>

The javascript: candidate should be gone and the picture structure intact. One behavior worth knowing: a srcset whose candidates are all disallowed comes out as srcset="", matching what an emptied href or src does today, rather than the attribute being dropped.

AI Use

The rebase, the tests and this description were done with 🤖 Claude Code, steered by me. I will review and test.

@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

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • 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.

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread tests/phpunit/tests/kses.php Outdated

This comment was marked as outdated.

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

This PR adds support for the HTML5 <picture> element and srcset attribute on <img> tags to WordPress's KSES filtering system. This enables proper handling of responsive images in content filtering.

  • Adds <picture> and <source> elements to allowed post tags
  • Adds srcset and sizes attributes to <img> and <source> elements
  • Refactors URI sanitization logic to handle multi-URI attributes like srcset

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 picture/source elements to allowed tags, adds srcset/sizes attributes, and creates new wp_kses_sanitize_uris function to handle multi-URI attributes
tests/phpunit/tests/kses.php Adds comprehensive test coverage for img srcset attributes, srcset sanitization, and picture element filtering

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
@vishalkakadiya

Copy link
Copy Markdown

@adamsilverstein Can you resolve conflicts on this PR, as the testing team has to apply this patch to QA it? Thanks!

https://core.trac.wordpress.org/ticket/29807#comment:53

@adamsilverstein

Copy link
Copy Markdown
Member Author

@adamsilverstein Can you resolve conflicts on this PR, as the testing team has to apply this patch to QA it? Thanks!

https://core.trac.wordpress.org/ticket/29807#comment:53

yes, I will work on that.

@adamsilverstein
adamsilverstein force-pushed the ticket/29807 branch 2 times, most recently from c402114 to f9e002c Compare February 10, 2026 00:48
@juanmaguitar

Copy link
Copy Markdown

@adamsilverstein thanks for working on this ticket!
Do you think you'll be able to address the requested changes in time for WP 7.0 RC1 (happening in ~1 week)?

@adamsilverstein

Copy link
Copy Markdown
Member Author

@adamsilverstein thanks for working on this ticket! Do you think you'll be able to address the requested changes in time for WP 7.0 RC1 (happening in ~1 week)?

I have resolved the merge conflicts and am looking at adding additional tests. I'll also review feedback on the Trac ticket. Also fine punting this to 7.1 early.

@adamsilverstein
adamsilverstein marked this pull request as ready for review March 13, 2026 17:49
@github-actions

github-actions Bot commented Mar 13, 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.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein, azaozz, westonruter, swissspidy, dmsnell, vishalkakadiya, juanmaguitar.

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


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@adamsilverstein

Copy link
Copy Markdown
Member Author

I added some fixes and additional tests.

@adamsilverstein

Copy link
Copy Markdown
Member Author

Refreshed against current trunk (clean merge, CI green). Given 7.0 RC1 has passed, now targeting 7.1.

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
adamsilverstein added a commit to adamsilverstein/wordpress-develop that referenced this pull request Jul 10, 2026
- Rename variables for clarity: $attr_name, $attr_value, $uri_attrs,
  $multi_uri_attrs, $matches.
- Add native type declarations for parameters and return value.
- Replace the indexed for loop with foreach.
- Use the null coalescing operator for the optional descriptor match.
- Restore alphabetical ordering of the img, source, and
  wp_kses_uri_attributes() attribute lists.

Addresses review feedback from @westonruter on PR WordPress#6184.

See #29807.
@adamsilverstein

Copy link
Copy Markdown
Member Author

I've refreshed the branch against current trunk and pushed three commits:

fc99930 - Review feedback. Addresses all of @westonruter's notes: alphabetized the img/source/wp_kses_uri_attributes() lists, renamed variables ($attr_name, $attr_value, $uri_attrs, $multi_uri_attrs, $matches), added native type declarations, and refactored the entry loop to foreach with ?? ''.

5ea4632 - Sanitize descriptor-less srcset entries individually. While addressing the feedback I found that the splitter only recognized URL descriptor, boundaries. Since descriptors are optional per the HTML spec, a value like safe.jpg, javascript:alert(1) was treated as a single URL and the bad protocol survived (wp_kses_bad_protocol() only inspects the start of the string). URLs in srcset must encode whitespace as %20, so a comma adjacent to whitespace always separates two entries - the splitter now also treats those commas as boundaries, while commas with no adjacent whitespace (the CDN resizer case from the Trac ticket) remain part of the URL, matching browser parsing. Tests added.

215b942 - HTML API: preserve srcset in set_attribute(). Registering srcset in wp_kses_uri_attributes() had a side effect on WP_HTML_Tag_Processor::set_attribute(), which runs URI-attribute values through esc_url(): a srcset like a.jpg 1x, b.jpg 2x came back as a.jpg%201x,%20b.jpg%202x - one broken URL. srcset now gets the standard attribute escaping there (its behavior before this PR), with protocol sanitization handled per-URL by KSES. No core caller sets srcset via the HTML API today, but plugins rewriting srcset (image CDNs) would have hit this. Test added.

Local runs: kses suite 412 tests green, HTML API suite 1,535 tests green, PHPCS clean on all touched files.

@adamsilverstein

Copy link
Copy Markdown
Member Author

Pushed two follow-up commits:

a86d336 - Adds wp_kses_multi_uri_attributes() (with a wp_kses_multi_uri_attributes filter) as the single source of truth for srcset-like attributes. WP_HTML_Tag_Processor::set_attribute() and the wp_kses_sanitize_uris() default now both consult it instead of hardcoding 'srcset', so plugins can register additional multi-URI attributes (e.g. imagesrcset) without esc_url() corruption. Covered by new tests on both the KSES and Tag Processor sides.

6d3d20a - Documents a known, accepted parsing limitation: a comma attached to an invalid descriptor (e.g. a.jpg 2q,javascript:...) is not treated as an entry separator, so the value is protocol-checked as a single URL and browsers may parse more candidates than KSES does. This is inert (srcset candidates are only ever fetched as images, never navigated or executed); a comment explains the reasoning and tests pin the behavior so future splitter changes are made consciously.

All three affected suites pass locally (kses: 421, html-api tag processor: 507, wpKsesHair: 67) and PHPCS is clean.

@swissspidy swissspidy left a comment

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.

I'm not super knowledgeable of KSES, so best if someone else also reviews, but the changes look reasonable to me.

@dmsnell dmsnell left a comment

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.

I’ve left some early thoughts. at the same time I’ve been working on a wp_kses() replacement using the HTML API. that’s not pushed yet, so there’s nothing you need to do, but I wanted you to be aware of it.

one way we could work more-independently here is by creating a new function whose purpose is to sanitize a comma-separated list of candidate image strings. given the conflation of HTML’s concept of that, and what I think is a new concept in WordPress of “multi URI attributes” (something HTML doesn’t talk about), and how we are attempting to specifically parse the one while being generic in naming, I think a separate method we call outside of wp_kses() could be a good start.

Comment thread src/wp-includes/kses.php Outdated
*
* @return string[] HTML attribute names whose value contains a list of URLs.
*/
function wp_kses_multi_uri_attributes() {

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.

HTML only defines a few global attributes, so while functions like esc_url() are stuck in legacy interfaces that prevent determining the type of an HTML attribute, we have the opportunity in new code to do better.

  • the attributes table shows the type of srcset on img and source elements, but on any other element it won’t be interpreted in the same way by the browser.
  • the srcset definition explains how to parse it.
  • the imagesrcset is just like it but exists on the link element.

to this point, I think the naming is a bit misleading. srcset is not a list of URLs or URIs, but a comma-separated list of image candidate strings. some attributes have space-separated lists, in contrast, and as you note, may contain width and pixel-density descriptors. this is not like ping on A or AREA elements, which only accept space-separated non-empty URLs.

I like the direction this is reaching for, and have wanted to improve Core’s handling of URL attributes myself, but I think we should be careful about trapping ourselves in a corner the way we have with esc_url().

@westonruter and I spoke about making semantic functions in the HTML API to handle each attribute byte, but obviously this requires substantial work to handle the varied types of those attributes.

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.

the sanitization spec mentions detecting javascript: URIs, which I think is our main concern. it would be interesting to see if we can align with this relatively new part of the HTML spec so that our behaviors mirror the browsers’

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for the feedback @dmsnell

Comment thread src/wp-includes/kses.php Outdated
* rejected here is also rejected as a scheme by browsers.
*/
$prefix = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $url );
$prefix = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $prefix, 2 );

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.

there are dangers in writing these decodes ad-hoc because of complicated rules for unexpected formatting. coming from wp_kses_hair(), these should actually be decoded already except for the big five syntax character <&>'"

however, we have WP_HTML_Decoder::decode_attribute() for this, as well as WP_HTML_Decoder::attribute_starts_with()

@dmsnell dmsnell left a comment

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.

we are still passing around the terminology of “multi-URI attributes” and I don’t want that to make it more difficult to properly handle and sanitize as we expand support.

everything surrounding wp_kses_uri_attributes() exhibits what’s a bit sludgy here:

  • which HTML element the attribute is on matters, but isn’t used to determine if the attribute contains URLs
  • the filter replaces the fixed set of URL-containing attributes rather than augments it, making it possible to remove one that a browser will still interpret as a URL

we’re also conflating HTML attribute sanitization and attribute value parsing, which makes it hard to ensure consistency for either of them. I think there’s a case to be made where we have a separate function or set of functions that split these activities and for which we can test and assert and improve their behavior.

switch ( $attr_name ) {
	case 'imagesrcset':
	case 'srcset':
		$urls = wp_html_attribute_get_comma_separated_tokens( $value );
		break;

	case 'href':
	case 'src':
		$urls = array( wp_html_attribute_get_whitespace_surrounded_token( $value ) );
		break;
}

and so on. the naming and location of these functions is still murky, which is why they still aren’t in place. we should examine what the browsers do to verify them against the spec. for example, is a “comma-separated list of image candidate strings” the same as a comma-separated list of tokens, whose tokens are image candidate strings? or do browsers parse this one specially?

we might even consider adding a new kind of dispatcher function which returns an attribute’s type, and then have another function which is like wp_get_semantic_attribute_value( $tag_name, $attribute_name, $namespace = 'html' ): mixed. I think this is not the right time or PR for this, but it’s probably the right direction to move towards.


from a performance perspective I think we can do more to analyze the URLs in place without allocating so much. It seems like all this does is check for “bad protocols” and yet we’re creating all sorts of chunks and copies of strings repeatedly, and then string-building the result.

one approach I like to use is to track a $was_at value which represents the last part of the input that was copied into the output. if we reach the end and $was_at is 0, we can return the input directly and that will be reference-equal at the call-site.

otherwise, we can now only extract and allocate the scheme parts, and if those come back fine then we advance $was_at but avoid appending content into the $result.

it’s only once we encounter a changed value that we need to flush

$result .= substr( $attr_value, $was_at, $item_at - $was_at );
$result .= $url;
$result .= substr( $attr_value, $descriptors_at, $descriptor_length );
$was_at  = $at;

some details are omitted for brevity, but generally this will avoid allocations, avoid memory copies, and preserve the reference-equality of strings and interned strings by never copying unless necessary.

Comment thread src/wp-includes/kses.php Outdated
* @since 7.2.0
*
* @param string $attr_name The attribute name to test.
* @param string $attr_value The attribute value to sanitize.

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.

there is no indication on whether these values are decoded or raw, and that could be incredibly important.

ideally, we should require already-decoded attribute values here so that this function can focus on the actual logic of URL parsing and sanitization, and not jumble in HTML-decoding logic as well.

Comment thread src/wp-includes/kses.php Outdated
while ( $at < $length ) {
// The whitespace and commas separating this candidate from the previous one.
$separator_length = strspn( $attr_value, "{$whitespace},", $at );
$separator = substr( $attr_value, $at, $separator_length );

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.

especially with kses it should not be essential to preserve the separator, and in fact, ideal to normalize it to a single space. that should mean we can skip right past the whitespace, which is common in HTML API code.

$at += strspn( $attr_value, " \t\f\r\n" );

Comment thread src/wp-includes/kses.php Outdated
if ( ! $dropped ) {
$result .= $separator;
}
break;

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.

it looks like we’re intentionally preserving trailing whitespace here, but does it make more sense to trim off the final whitespace by unconditionally breaking here without appending the separator?

Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
Comment thread src/wp-includes/kses.php Outdated
++$at;
}
}
$descriptors = substr( $attr_value, $descriptor_start, $at - $descriptor_start );

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.

note that if all we care about sanitizing is the URL, then we can skip the descriptors if we first segment the comma-separated list, which we need to do anyway. we can ignore the descriptors here, or if we elect, apply additional constraints (but that raises new questions, such as what to do when the syntax is right but the semantics are wrong)

Comment thread src/wp-includes/kses.php Outdated
* rejected here is also rejected as a scheme by browsers.
*/
$prefix = preg_replace( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $url );
$prefix = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/i', $prefix, 2 );

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.

these are patterns we want to avoid entirely.

this goes back into asking what kind of content this function expects. by translating or even recognizing these here, we are implicitly declaring that this function expects raw and possibly-encoded HTML text

a problem with that, however, is that we are basing our parsing of the comma-separated image candidate strings based on raw bytes. the code is internally incoherent because there is no way to properly pass values into this function.

I recommend that we require callers to pass already-decoded HTML text here, but if we go the other route, we have to instruct callers to re-encode HTML syntax characters before sending inputs here, and then we have to decode the entire attribute value at the start, not mid-way through.

every other option presents mis-parsing issues that will bypass sanitization and cause other corruption.

Comment thread src/wp-includes/kses.php Outdated
if ( isset( $prefix[1] ) ) {
$scheme = wp_kses_decode_entities( $prefix[0] );
$scheme = preg_replace( '/\s/', '', $scheme );
$scheme = wp_kses_no_null( $scheme );

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.

we should really really avoid calling this function. it is far better to replace unwanted or scary bytes with the Unicode replacement character (U+FFFD �) than it is to remove them.

needless to say, if we require decoded inputs we can skip all of these.

IMO we should also avoid things like skipping the whitespace. for one, it should not be possible to reach this point in the code and find whitespace. for two, there is no plausible reason to accept http s://wordpress.org. if something isn’t normative and has no definitive resolution, we can reject it entirely.

there are, after all, untrusted inputs and the srcset is not that important that we need to over-stress to try and safely resolve invalid inputs.

Comment thread src/wp-includes/kses.php Outdated
break;
}

// The URL is the next run of non-whitespace characters…

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.

I found Parsing a srcset attribute and it answered my question: is this the same as a comma-separated list of tokens — it’s not. I see that the code here follows that algorithm and link to the spec is already above.

one thing remains though, this parsing is applied to all $multi_uri_attrs even though the parsing spec only applies to a srcset attribute, meaning that without additional code to guard against it, we are inviting mis-parses for other attributes people might add to the multi-url attributes list

@adamsilverstein

Copy link
Copy Markdown
Member Author

@dmsnell Thanks for all of the detailed feedback. With all of the progress you are making on https://make.wordpress.org/core/2026/10/07/progress-report-wp_kses/ I'm hesitant to make any changes here. Besides being nervous about touching kses in general, I expect your approach to be vastly more accurate/precise and much less likely to introduce unintended side effects.

What do you think, should I still try to land this change?

@dmsnell

dmsnell commented Oct 9, 2026

Copy link
Copy Markdown
Member

@adamsilverstein you’re right; touching wp_kses() right now may be a bit sensitive because if anything major turns up before it’s proverbially written in stone, then there could be major code conflicts to update.

but I think more broadly this is (a) really good to pursue, and (b) best to try and build gradually towards. I think the terminology matters significantly if we want to avoid the troubles of the past. that is, we don’t need to ship perfect code or cover every future possibility, but we should ensure that the designs support the right things and that our interfaces support knowable needs.

for instance, multi_url_attribute is only going to be confusing because multiple attributes contain multiple URLs but in different ways. it will be much clearer if we can layer the abstractions and keep focus on each layer of transformation.

sadly, as your work taught me, comma separated list of candidate image strings actually has no intermediate steps between decode HTML text and analyze the URLs so there’s not much generalizable utility in that.

at a minimum we are going to need to pass the tag name, and optionally the namespace, into the sanitization function (and any corresponding filter, unlike $allowed_html which doesn’t let us distinguish HTML from SVG, for example).

I think it’s also worth considering that we only allow extending the attribute set, and never remove those which are fixed by the HTML spec or browsers.


because I’m long-winded, I’m going to try and summarize There may be multiple things in this PR that could be addressed separately:

  • an expanded system for sanitizing URLs which lives in wp_kses(), wp_kses_hair(), and wp_kses_one_attr()
  • a srcset parser, which would mostly be your existing parsing code lifted out into a something specific to srcset, and possibly which is also able to parse the descriptors
  • a new filter which allows extending URL sanitization to more element/attribute/namespace combinations, which would benefit all of Core

as a final note, I think for some of these things, especially within the purview of KSES, it’s probably okay to hard-code them without new filters as a first step, because this is sensitive and if people don’t need to extend them, then it’s fine leaving this spec-oriented.

hope this makes sense.

@adamsilverstein

Copy link
Copy Markdown
Member Author

makes sense @dmsnell - I'll work on this again soon, thanks for the feedback!

I'll see if I can split up the code into the parts you mention can be addressed separately, maybe in the form of several stacked PRs.

A srcset value is a comma-separated list of image candidate strings,
not a single URL, so passing it through wp_kses_bad_protocol() or
esc_url() either misses candidates or corrupts the list. This function
segments the value following the HTML specification's srcset parsing
algorithm and removes candidates whose URL uses a disallowed protocol.

It takes decoded attribute text and returns decoded text, leaving HTML
decoding and encoding to the caller, and returns the input string
itself when every candidate is allowed. Nothing calls it yet; wiring
it into KSES by tag and attribute follows separately.

See https://core.trac.wordpress.org/ticket/29807
Both KSES parsers decide how to sanitize a URL attribute from the
attribute name alone, which cannot express that srcset on img and
source, and imagesrcset on link, hold a list of image candidates
rather than one URL. Route URL attributes through a private dispatcher
that takes the element name as well, sending those attributes to
wp_kses_sanitize_srcset() and everything in wp_kses_uri_attributes()
to wp_kses_bad_protocol() as before.

wp_kses_hair() gains an optional $element parameter so wp_kses_attr()
can pass the element through; existing callers without one keep the
attribute-name-only behavior. wp_kses_one_attr() decodes the value
before sanitizing and re-encodes it only when sanitization changed it,
so encoded separators cannot hide a candidate.

No allowed-HTML change yet, so srcset is still stripped from post
content; that follows separately.

See https://core.trac.wordpress.org/ticket/29807
Post content has had no way to carry responsive images: wp_kses_post()
strips srcset and sizes from img, and removes picture and source
entirely, so authors without unfiltered_html lose the markup the
editor and themes generate. Now that srcset is sanitized per candidate
by element, allow picture, source and the img attributes srcset,
sizes, decoding and fetchpriority in the post context.

source also allows src, width and height so video and audio sources
survive as more than a bare element.

Fixes https://core.trac.wordpress.org/ticket/29807
@adamsilverstein

Copy link
Copy Markdown
Member Author

First piece of the split is up: #14811 adds wp_kses_sanitize_srcset() on its own - decoded text in and out, segmented the way the spec's srcset parsing algorithm does it, nothing calling it yet. Next is wiring it into both KSES parsers by tag and attribute (hard-coded, no new filter for now), and then this PR gets rebased down to just the allowed-HTML additions for picture, source and srcset. @dmsnell whenever you have a chance, feedback on the parser PR would be great, since the other two build on it.

@adamsilverstein adamsilverstein changed the title wp_kses: add support for picture element and srcset attribute on img tags KSES: Allow picture, source and responsive img attributes in post content Oct 24, 2026
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.

8 participants