diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 29317f0a8bf9b..899918a67066d 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -4903,7 +4903,7 @@ function get_the_taxonomies( $post = 0, $args = array() ) { $links = array(); foreach ( $terms as $term ) { - $links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name ); + $links[] = wp_sprintf( $t['term_template'], esc_url( get_term_link( $term ) ), $term->name ); } if ( $links ) { $taxonomies[ $taxonomy ] = wp_sprintf( $t['template'], $t['label'], $links, $terms ); diff --git a/tests/phpunit/tests/taxonomy.php b/tests/phpunit/tests/taxonomy.php index 39a1a6d90ed5b..fb1a45f0d5620 100644 --- a/tests/phpunit/tests/taxonomy.php +++ b/tests/phpunit/tests/taxonomy.php @@ -83,6 +83,25 @@ public function test_get_the_taxonomies_term_template() { $this->assertSame( 'Categories: Uncategorized.', $taxes['category'] ); } + /** + * The term URL fills an `href`, so it must be escaped with esc_url() to + * enforce the protocol allowlist, matching get_the_term_list(). A term_link + * filter returning a javascript: URL must not survive into the markup. + */ + public function test_get_the_taxonomies_escapes_term_url() { + $post_id = self::factory()->post->create(); + + add_filter( 'term_link', array( $this, 'filter_term_link_to_js' ) ); + $taxes = get_the_taxonomies( $post_id ); + remove_filter( 'term_link', array( $this, 'filter_term_link_to_js' ) ); + + $this->assertStringNotContainsString( 'javascript:', $taxes['category'] ); + } + + public function filter_term_link_to_js() { + return 'javascript:alert(1)'; + } + public function test_the_taxonomies() { $post_id = self::factory()->post->create();