Skip to content

Icons Registry: improve SVG sanitizer - #75550

Open
t-hamano wants to merge 46 commits into
trunkfrom
icon-registry-sanitizer
Open

Icons Registry: improve SVG sanitizer#75550
t-hamano wants to merge 46 commits into
trunkfrom
icon-registry-sanitizer

Conversation

@t-hamano

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

Copy link
Copy Markdown
Contributor

What?

In the experimental icon registry, any string can be registered as SVG content. Unfortunately, there is still no core function to properly sanitize SVGs, so we've enhanced the private methods for sanitization and defined as many SVG-friendly tags and attributes as possible.

Testing Instructions

Please refer to the unit test to see what the function expects.

@t-hamano t-hamano self-assigned this Feb 15, 2026
@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.0 Editor Tasks Feb 15, 2026
@t-hamano t-hamano added [Type] Experimental Experimental feature or API. [Block] Icon Affects the Icon block labels Feb 15, 2026
@t-hamano
t-hamano force-pushed the icon-registry-sanitizer branch from e537f45 to 7bdd4c1 Compare February 15, 2026 08:58
Comment thread lib/experimental/class-wp-icons-registry.php Outdated
Comment on lines +66 to +67
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="icon" aria-hidden="true"><path d="M0 0" fill="currentColor" /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" width="24" height="24" class="icon" aria-hidden="true"><path d="M0 0" fill="currentColor" /></svg>',

@t-hamano t-hamano Feb 15, 2026

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.

I was surprised to see wp_kses() convert camel case attribute names (viewBox) to lower case (viewbox). Lower-case attribute names should be recognized correctly by most browsers, but I'm not sure if this should be allowed.

cc @dmsnell @sirreal

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.

As far as I've tested, lowercase viewbox is automatically recognized as viewBox by major browsers, so as long as we're using inline SVG as HTML content, we shouldn't have any problems.

<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100">
  <rect x="0" y="0" width="100%" height="100%" />
  <circle cx="50%" cy="50%" r="4" fill="white" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100" width="100">
  <rect x="0" y="0" width="100%" height="100%" />
  <circle cx="50%" cy="50%" r="4" fill="white" />
</svg>

</body>
</html>
viewbox

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.

yes, when the SVG is embedded within HTML, those attributes are identical across case variations. the HTML parser will convert a subset of attributes to their normalized camelCase form, but when parsing they are identical.

one of the security concerns with SVG revolves around subtleties like this, because embedded and external SVG parse differently, which can be a problem when plugins inline external files.

@t-hamano t-hamano changed the title Icons Regstry: improveme SVG sanitizer Icons Regstry: improve SVG sanitizer Feb 15, 2026
@t-hamano
t-hamano marked this pull request as ready for review February 15, 2026 09:09
@github-actions

github-actions Bot commented Feb 15, 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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: dmsnell <dmsnell@git.wordpress.org>

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

@mcsf mcsf changed the title Icons Regstry: improve SVG sanitizer Icons Registry: improve SVG sanitizer Feb 17, 2026
@dmsnell

dmsnell commented Feb 20, 2026

Copy link
Copy Markdown
Member

@t-hamano this seems like a good improvement since it retains the structure of the existing sanitization while adding extra attributes. did you verify that none of the newly-allowed attributes allows for script execution?

one note here is that SVG is complicated to parse, and even the search for <svg\b seems a bit suspicious. the HTML Processor can parse all valid inline SVGs, and one thing that might be useful here is to prepare the input using that and then dump the output into kses after normalization, where things that seem sneaky might be less prone to issues in kses.

$svg = '';
$processor = WP_HTML_Processor::create_fragment( $icon_content );
if ( ! $processor->next_token() || 'SVG' !== $processor->get_tag() ) {
	return '';
}

$svg .= $processor->serialize_token();
$svg_depth = $processor->get_current_depth();
while ( $processor->next_token() && $processor->get_current_depth() > $svg_depth ) {
	$svg .= $processor->serialize_token();
}

// Parsing has stopped even though there might be additional content.
$filtered = wp_kses( ... )

This could also be done after filtering with wp_kses() as it’s likely to corrupt the SVG, but normalizing first should help avoid a lot of problems. For instance, wp_kses() will be unaware of this but a single <p> terminates the SVG element. In fact, many tags that appear within inline SVG will end it and jump back into HTML. If we first extract the SVG in its entirety with the HTML Processor then we wouldn’t have to consider this.

Unfortunately there could be issues with self-closing tags, which exist within the SVG content but otherwise not in the HTML. In HTML, <svg/> is an entire SVG element, even though HTML contains no self-closing tags (the <svg/> tag is actually an SVG tag so the self-closing rule applies).

sticking with wp_kses() is appropriate apart from all of this. I have proposed dmsnell/wordpress-develop#20 and several other changes that will feed up into wp_kses() (WordPress/wordpress-develop#7409, WordPress/wordpress-develop#9270, and WordPress/wordpress-develop#9851 for example), so ultimately that should become more reliable.

for gradual improvement I think this is good as long as we have a thorough review of the attributes and tags we’re allowing.

* @return string The sanitized icon SVG content.
*/
private function sanitize_icon_content( $icon_content ) {
// Core attributes applicable to most elements.

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.

This appears to be using the elements and attributes from my earlier comment, can you confirm?

Would be nice to validate those, as I haven't gone in-depth in that, and there are plenty of elements and attributes that SVGs can support. Also we know that any KSES-related changes shouldn't be taken lightly 😉

@t-hamano

Copy link
Copy Markdown
Contributor Author

Sorry for the late reply. I'm working with @mcsf to build the infrastructure to allow the Icon API to be extended on the Gutenberg plugin. If that works out, I'll come back to this PR.

@t-hamano t-hamano added [Type] Enhancement A suggestion for improvement. and removed [Type] Experimental Experimental feature or API. labels Feb 25, 2026
@t-hamano t-hamano added [Feature] Icons Related to Icon registration API and Icon REST API and removed [Block] Icon Affects the Icon block labels Mar 3, 2026
@t-hamano t-hamano moved this from 🔎 Needs Review to 🦵 Punted to 7.1 in WordPress 7.0 Editor Tasks Mar 3, 2026
@t-hamano
t-hamano force-pushed the icon-registry-sanitizer branch from 7bdd4c1 to 26a51db Compare March 12, 2026 08:51
Comment on lines +896 to +898
'#comment' === $token_type
|| '#doctype' === $token_type
|| ( '#text' === $token_type && '' === trim( $processor->get_modifiable_text() ) )

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.

What about #cdata-section?

This block ignores some allowed content before the <svg> tag or reject the entire content.

This really depends on how strict things are expected to be. The fragment parser starts in HTML where CDATA cannot appear. "CDATA sections" are actually comments at this point.

I don't know how valuable it is to be more strict at this point, we could do things like only allow the <?xml … ?> which the HTML API recognizes as COMMENT_AS_PI_NODE_LOOKALIKE-type comment with an xml tag name, but this just seems like noise.

I'm undecided, but I'd like this section to either become more strict or to become more relaxed.

A strict implementation would rigorously reject anything that doesn't conform to expectations:

<?xml … ?> (optional)
<!DOCTYPE svg …> (optional)
<svg></svg> (REQUIRED)
--- nothing else ---

Processing this requires the full HTML processor and needs to look for a tree like this:

├─#comment(COMMENT_AS_PI_NODE_LOOKALIKE)
│ └─xml  
├─DOCTYPE: svg
└─HTML
  ├─HEAD
  └─BODY
    └─svg:svg
      └─#text …

A more relaxed implementation could just call $processor->next_tag( 'SVG' ) and proceed, ignoring any surrounding content.

Right now, I'm leaning towards the relaxed implementation, but I can understand arguments for the strict version.

Comment on lines +906 to +908
if ( 'SVG' !== $processor->get_tag() ) {
return '';
}

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.

As implemented, I don't think this is a problem because a preceding <math> tag would be rejected. However, if we relax this to use ->next_tag( 'SVG' ) and proceed, it could find <math><svg> (an SVG element in the math namespace).

It would be good to check that this is an SVG tag in the svg namespace to prevent this kind of issue.

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.

Fixed in f203e7d

t-hamano and others added 2 commits June 23, 2026 16:12
The SVG root check matched on tag name alone, so an <svg> in a foreign
namespace (e.g. the MathML-namespaced <svg> inside <math><svg>) was
treated as the icon root. Also require the element to be in the SVG
namespace so such elements are rejected.

Co-Authored-By: Claude <noreply@anthropic.com>
Locate the icon root via next_tag( 'SVG' ) and ignore content surrounding
it (XML declaration, doctype, comments, sibling elements), so real-world
SVG files that wrap the root are accepted. Surrounding content is only
ignored, never emitted, so wp_kses still bounds what is output. A second
top-level SVG is still rejected to keep the icon unambiguous, and the
namespace check continues to reject a foreign-namespaced <svg>.

Reorganize the sanitizer data provider so groups follow a single
criterion (behavior, labeled by comment; element category in the key)
and add cases for multiple/nested/foreign-namespaced roots and ignored
surrounding content.

Co-Authored-By: Claude <noreply@anthropic.com>
@mcsf
mcsf removed their request for review June 23, 2026 15:35

@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.

hope I’m not too late to the party; I’ve been quite distracted by other work lately, so thanks for your patience with me and with this comment.

the function interface does not make clear whether this expects SVG content as read from a .svg file or if it expects SVG content from an inline HTML document.

of note, we really shouldn’t use the HTML Processor if we are reading external SVG files, as it will mis-parse XML (since HTML and XML are separate languages), even though we can probably get away with it in benign cases (but we care most about the malicious cases).

the questions surrounding CDATA sections are a good example of this nuance, as is the inclusion of HTML integration points within the SVG. an inline SVG containing <svg><foreignObject><img></foreignObject><svg>, for example, will create an SVG with an HTML IMG element. For an external SVG of the same content it will fail to parse.

so for external SVGs we actually might have better reliability with DOMDocument as that’s an XML parser. it has its own issues, of course, but it’s parsing the right language.


this is probably distracting the PR as proposed, but I feel like it’s good to back up a few steps and clarify what our intentions are, noting that this is historically a very security-sensitive area.

at a minimum it would be ideal to make the interface extremely clear what this is expecting. something like this…

/**
 * @see sanitize_external_svg()
 */
function sanitize_inline_svg( $html_containing_svg )

not that this is the right naming, but it would be best if someone came to this function and could not mistake that there’s another appropriate function for the purpose.

and it does get tricky because somehow we will file_get_contents() on an external SVG and end up sending it here and that will be a mistake because it crossed language boundaries.

while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) {
$svg .= $processor->serialize_token();
}
$svg .= '</svg>';

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.

while I think it’s fine the way this is, and possibly best this way, we could use at least a comment acknowledging a surprise here: the parser might have ended early due to finding an incomplete token. normally I would say we could fail here because of unsupported markup, but given that we have started on SVG I do not think it’s possible to encounter unsupported markup while remaining inside the SVG.

for example, this demo shows the string <svg><text>Just a </te being interrupted.

What I didn’t remember or expect was that we still found the closing </text> and </svg> tokens which weren’t in the document itself, but that might not be a guarantee; it could be dependent just on how the incomplete document ended.

For instance, the text <svg><te produces <svg></svg> as if the <te was not there. We might have a bug or just a surprising behavior here to address. Regardless, in these cases, no valid document really exists so it’s hard to make claims about what should be parsed.

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.

Comment on lines +66 to +67
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="icon" aria-hidden="true"><path d="M0 0" fill="currentColor" /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" width="24" height="24" class="icon" aria-hidden="true"><path d="M0 0" fill="currentColor" /></svg>',

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.

yes, when the SVG is embedded within HTML, those attributes are identical across case variations. the HTML parser will convert a subset of attributes to their normalized camelCase form, but when parsing they are identical.

one of the security concerns with SVG revolves around subtleties like this, because embedded and external SVG parse differently, which can be a problem when plugins inline external files.

Comment on lines +896 to +898
'#comment' === $token_type
|| '#doctype' === $token_type
|| ( '#text' === $token_type && '' === trim( $processor->get_modifiable_text() ) )

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.

only allow the <?xml … ?>

external SVGs are not required to contain this or the DOCTYPE, so the strict implementation would reject valid SVGs, and probably most of them.

if ( 'svg' === $processor->get_namespace() ) {
return '';
}
}

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.

this is not directly a critique of the choice, but a question: why reject the first SVG when more are present? is this an attempt to apply extra rigor to an SVG?

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.

The reason for this is that the wp_get_icon() function assumes a single SVG and apply sizing accordingly.

if ( ! $processor->next_tag( 'svg' ) ) {
return '';
}
if ( is_numeric( $args['size'] ) ) {
$size = absint( $args['size'] );
$processor->set_attribute( 'width', (string) $size );
$processor->set_attribute( 'height', (string) $size );
}

# Conflicts:
#	lib/class-wp-icons-registry-gutenberg.php
#	phpunit/experimental/class-wp-icons-registry-gutenberg-test.php
@westonruter

Copy link
Copy Markdown
Member

so for external SVGs we actually might have better reliability with DOMDocument as that’s an XML parser. it has its own issues, of course, but it’s parsing the right language.

Note that core has not generally had a hard requirement to have the dom extension enabled. Most of the uses of DOMDocument are predicated on class_exists( 'DOMDocument', false ) checks.

The only two required extensions are json and mysqli: https://make.wordpress.org/hosting/handbook/server-environment/#required-extensions

t-hamano and others added 3 commits July 8, 2026 17:22
…ontract

The SVG sanitizer parses input with WP_HTML_Processor (an HTML parser), so
it only handles inline SVG as it appears in an HTML document, not raw XML
from a standalone `.svg` file. The previous name `sanitize_icon_content`
(a core override) hid this and made the HTML/XML language boundary easy to
miss, per review feedback.

Rename the method to `sanitize_inline_svg( $html_containing_svg )` and drop
the override: the only callers are this class's own register() and
get_content(), both of which already override their parent, so nothing
dispatches to it polymorphically. Document that raw `.svg` XML is out of
scope, and flag the external-file call site where XML currently flows into
the inline sanitizer as needing a dedicated XML sanitizer later.

Co-Authored-By: Claude <noreply@anthropic.com>
The extraction loop can stop early when the parser pauses on an incomplete
token. Since unsupported markup cannot appear once inside an SVG, an early
stop always means truncated input, for which the parser may synthesize
closing tags that were never written. Add a comment explaining why such
input is rejected rather than salvaged, per review feedback.

Co-Authored-By: Claude <noreply@anthropic.com>
@t-hamano

t-hamano commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@dmsnell Thanks for the review!

If I understand correctly, the main concern is that we parse what may be an external .svg file with an HTML parser. As @westonruter noted, though, DOMDocument isn't a required extension, so doing that properly also means handling the no-DOMDocument fallback — which feels too complex for this PR.

For now, I have focused solely on clarifying the intent by changing the method name and adding comments. Does this approach sound right?

@t-hamano t-hamano added [Type] Bug An existing feature does not function as intended and removed [Type] Enhancement A suggestion for improvement. labels Jul 14, 2026
@t-hamano

Copy link
Copy Markdown
Contributor Author

Hoping that this PR makes it into the 7.1 release, I am changing the status from enhancement to bug. This is because this PR adds two methods, both of which are private and internally fix the behavior of the sanitizer.

@t-hamano
t-hamano requested a review from desrosj as a code owner July 28, 2026 07:03
* `<foreignObject>` integration points) are mis-parsed. WP_HTML_Processor
* extracts the whole SVG element before wp_kses runs, so inner HTML tags
* like <p> don't terminate the SVG and self-closing tags are handled
* correctly.

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.

this is a helpful comment, and I appreciate the renamed methods.

where are these SVG sources coming from?

are theme authors adding them to theme.json? or are they coming in through the block editor as pastes into a block?

as a JSON block attribute, it is probably best to treat them as XML document. as a sourced attribute, it is probably best to treat them as HTML with inline SVG.

I feel bad harping on this, but the implications can be hefty, and the difference is that as a JSON string value, we have a definitive end-point to the SVG content. we know we have an entire document and can validate it properly. with HTML content though we have to infer the end point, which can be extremely complicated.

probably, people will not be passing in anything that would cause this to matter, copying from well-formed XML.

so there’s no problem with this method, however, we might have opportunity to clarify usage for people.

- The input must be an inline SVG as found in an HTML document, NOT raw XML
- from a standalone `.svg` file: parsed as HTML, XML-only constructs (CDATA,
+ The input SVG must have been extracted as HTML from a broader HTML document,
+ NOT as an entire XML document from an external file or JSON value.

something like that. this is territory where most people will probably be unfamiliar with the significant nuance at play. anything we can do to clarify will prevent unwanted outcomes. it might even be helpful to take this to a few people and ask them to explain the function and catches. do they know how to use it properly? if not, we can adjust the message and try again

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.

we might have opportunity to clarify usage for people.

Fixed in 6ddec02

where are these SVG sources coming from?

SVG sources do not come from theme.json or the block editor. They are injected from PHP via wp_register_icon or the register method.

wp_register_icon(
	'my-plugin/star-inline',
	array(
		'label'   => __( 'Star', 'my-plugin' ),
		'content' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 3l2.6 6.3 6.8.5-5.2 4.4 1.6 6.6L12 17.3 6.2 20.8l1.6-6.6L2.6 9.8l6.8-.5L12 3z" /></svg>',
	)
);
wp_register_icon(
	'my-plugin/star-file',
	array(
		'label'     => __( 'Star', 'my-plugin' ),
		'file_path' => plugin_dir_path( __FILE__ ) . 'icons/star.svg',
	)
);
WP_Icons_Registry::get_instance()->register(
	'my-plugin/heart-inline',
	array(
		'label'   => __( 'Heart', 'my-plugin' ),
		'content' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 20s-7-4.3-7-9a4 4 0 017-2.6A4 4 0 0119 11c0 4.7-7 9-7 9z" /></svg>',
	)
);
WP_Icons_Registry::get_instance()->register(
	'my-plugin/heart-file',
	array(
		'label'     => __( 'Heart', 'my-plugin' ),
		'file_path' => plugin_dir_path( __FILE__ ) . 'icons/heart.svg',
	)
);

Comment thread lib/class-wp-icons-registry-gutenberg.php Outdated
t-hamano and others added 2 commits August 1, 2026 10:20
The distinction between an inline SVG extracted from HTML and a complete
XML document is subtle, and getting it wrong has real consequences: with
HTML content the end of the SVG has to be inferred, while a full XML
document has a definitive end point that can be validated properly.
Reword the docblock so callers understand which of the two they are
expected to pass.

Co-Authored-By: Claude <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Improves sanitization for SVG content registered through the experimental icon registry.

Changes:

  • Adds SVG-specific parsing and KSES allowlists.
  • Expands sanitizer unit coverage.
  • Links the WordPress 7.1 backport.

Reviewed changes

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

File Description
lib/class-wp-icons-registry-gutenberg.php Implements expanded SVG sanitization.
phpunit/class-wp-icons-registry-gutenberg-test.php Tests valid and unsafe SVG inputs.
backport-changelog/7.1/12197.md Records the Core backport.
Suppressed comments (1)

lib/class-wp-icons-registry-gutenberg.php:860

  • <set> has the same SMIL bypass: arbitrary attributename and unchecked to can assign a dangerous URL after KSES has finished protocol checks. Remove <set> from the allowlist or constrain its target and value to a proven-safe set.
					'attributename',

}
}

return wp_kses( $svg, $allowed_tags );

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.

Fixed in a57e993

Perhaps wp_kses_uri_attributes() should allow xlink:href itself, but since wp_kses() does not allow SVG by default anyway, I don't know if it's worth doing.

$this->get_allowed_attribute_list(
'accumulate',
'additive',
'attributename',

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.

Fixed in e43b7a6.

attributeName could be validated against a safe set of targets, but wp_kses()'s allowed-tag format has no way to express a value allow-list, so that would need a separate check outside wp_kses(). I dropped <animate> and <set> instead. DOMPurify removes both by default for the same reason (svgDisallowed).

Comment on lines +881 to +892
$svg = $processor->serialize_token();
$depth = $processor->get_current_depth();
while ( $processor->next_token() && $processor->get_current_depth() >= $depth ) {
$svg .= $processor->serialize_token();
}
// An early stop inside an SVG means truncated input, not unsupported
// markup. Reject it: the parser can synthesize closing tags that were
// never written, so no valid document remains to trust.
if ( null !== $processor->get_last_error() || $processor->paused_at_incomplete_token() ) {
return '';
}
$svg .= '</svg>';

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.

Fixed in 7b2c89c

t-hamano and others added 3 commits August 1, 2026 11:34
Core's wp_kses_uri_attributes() does not list `xlink:href`, so wp_kses()
never passes its value to wp_kses_bad_protocol(). Values such as
`xlink:href="javascript:alert(1)"` therefore survived sanitization intact
on the elements where the attribute is allowed (`a`, `use`, `image`,
gradients, `pattern`, `textPath`).

Add `xlink:href` to the URI attribute list for the duration of this
wp_kses() call so it receives the same protocol check as `href`.

Co-Authored-By: Claude <noreply@anthropic.com>
Both take an `attributeName` that targets an arbitrary attribute at
runtime, so `<animate attributeName="href" to="javascript:alert(1)">`
rewrites a link target after wp_kses() has protocol-checked the static
markup. Neither the allowed tag list nor the URI attribute check can
express that constraint, so the elements themselves have to go. This is
the same line DOMPurify draws: `animate` and `set` are in its
`svgDisallowed` set.

`<animateTransform>` and `<animateMotion>` stay. The former only animates
<transform-list> attributes and the latter takes no `attributeName`, so
neither can reach a URI attribute.

Co-Authored-By: Claude <noreply@anthropic.com>
The HTML parser honors XML self-closing syntax in foreign content, so
`<svg />` has no descendants and emits no closing tag. The descendant
scan stopped at a depth lower than the root, which for such a root is
only reached after consuming the following sibling: `<svg /><svg>…</svg>`
was returned as both roots concatenated plus an unbalanced `</svg>`, and
the multiple-root check that follows never saw the sibling because the
cursor had already passed it.

Skip the scan and the closing tag unless the root `expects_closer()`.
Leaving the cursor on the root is what lets the multiple-root check find
the sibling — narrowing the depth comparison instead would stop the loop
on the sibling token, which `next_tag()` would then skip past.

Co-Authored-By: Claude <noreply@anthropic.com>
@t-hamano

t-hamano commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Punting this to 7.2 as the 7.1 RC1 release is approaching.

@t-hamano t-hamano moved this from 🔎 Needs Review to 🦵 Punted to 7.2 in WordPress 7.1 Editor Tasks Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Icons Related to Icon registration API and Icon REST API [Type] Bug An existing feature does not function as intended

Projects

Status: 🦵 Punted to 7.2

Development

Successfully merging this pull request may close these issues.

6 participants