Escape values interpolated into HTML attributes - #768
Closed
obenland wants to merge 1 commit into
Closed
Conversation
obenland
force-pushed
the
fix/attribute-escaping-sweep
branch
from
August 6, 2026 20:01
8a35e01 to
fa65bfd
Compare
…ated into HTML attributes. Several templates build attributes by interpolation without an escaper, or apply one that does not match the context: - Rosetta's front-page blog list escaped the printf() format string rather than the post title passed as its argument. - The Showcase gallery and screenshot tags interpolated post meta into single-quoted attributes with no escaper, and FancyZoom wrote an anchor's title into innerHTML while binding to every anchor on the page. - The support forums kept bbPress's priority-60 rel=nofollow rewriter on topic and reply content; it re-emits attributes unescaped after shortcode_parse_atts() has run stripcslashes() over them. add_rel_ugc() at priority 80 already adds nofollow safely. - Slack display names synced from an external source were printed raw on support profiles and locale team pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
obenland
force-pushed
the
fix/attribute-escaping-sweep
branch
from
August 6, 2026 20:15
fa65bfd to
6e5e604
Compare
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A sweep of templates that build HTML attributes by interpolation without an escaper, or with one that does not match the context. Each change is a straightforward output-escaping correction.
Rosetta
front-page.php's blog list appliedesc_attr__()to theprintf()format string — a hardcoded developer constant with nothing to escape — while the post title passed as the argument was interpolated raw. Reworked to escape the composed string.Showcase
single.php/404.php: the gallery loop interpolated the two halves of theimagecustom field into single-quotedhrefandtitleattributes with no escaper. Nowesc_url()/esc_attr(). The?w=155&h=155cache-buster was also being concatenated onto a URL that may already carry a query string, so it now goes throughadd_query_arg().functions.php:site_screenshot_tag()escapedaltbut notsrc,srcset,width,heightorclass.site_screenshot_src()echoed its URL raw.js/fancyzoom.js: the caption is a plain string, so it is now written withtextContentrather thaninnerHTML.prepZooms()also scanned every anchor in the document; it is now scoped to the theme's own.gallerycontainer, so unrelated links are not given zoom handlers.Support forums
bbPress registers
bbp_rel_nofollowonbbp_get_topic_contentandbbp_get_reply_contentat priority 60. It parses attributes withshortcode_parse_atts(), which runsstripcslashes()over every value, and then re-emits them with no escaping.class-hooks.phpalready removes this filter from the author-link hooks, and adds its ownadd_rel_ugc()at priority 80 — which builds attributes via core'swp_rel_callback()and escapes them. Removing the priority-60 filter from the two content hooks therefore loses no behaviour.This one is a mitigation, not the root fix: the defect is in
bbp_rel_nofollow_callback()in bbPress itself, which is unfixed upstream and affects every bbPress site. That needs a separate fix there —wp_kses_hair()in place ofshortcode_parse_atts(), plusesc_attr()on re-emission. Note an escaper alone is not sufficient upstream, since the decode has already happened by that point. Removing the filter here is worth doing regardless, because on WordPress.org it is pure redundancy.i18n teams / support profiles
Slack display names are synced from an external source and were printed raw into markup on support profiles and locale team pages. Escaped at the three
wp-i18n-teamssinks and at thewporg-support-2024profile sink.The getters are deliberately left returning the raw value.
sanitize_text_field()there would be the wrong tool — it is a sanitiser in a read position, and it is lossy for legitimate names, since it strips percent-encoded sequences (50%20off→50off). If a defence-in-depth layer is wanted it belongs at import, whereslack_users.profiledatais written.Notes
wporg-learn-2024'ssearch-results-contextblock has the same class of issue —esc_attr()used for an element-name position. It is mirrored here fromWordPress/Learn, so the fix goes there instead and will arrive on the next sync: Search Results Context: Allowlist the tagName attribute Learn#3586.wporg-showcaseappears superseded by wporg-showcase-2022 and has had no commits since June 2022; the fixes are included since the theme is still in the tree, but removing the theme would be the better close. That would also dropfancyzoom.js, which carries a licence requiring a per-domain fee for commercial use and forbidding sale of derived works../vendor/bin/phpcs; no new violations on the changed lines. Pre-existing violations in these files are left alone.🤖 Generated with Claude Code