diff --git a/core/modules/update/src/UpdateProcessor.php b/core/modules/update/src/UpdateProcessor.php
index a07a47c8f5..5513a4f3cf 100644
--- a/core/modules/update/src/UpdateProcessor.php
+++ b/core/modules/update/src/UpdateProcessor.php
@@ -3,6 +3,7 @@
 namespace Drupal\update;
 
 use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
@@ -175,6 +176,9 @@ public function processFetchTask($project) {
       // @todo Purge release data we don't need. See
       //   https://www.drupal.org/node/238950.
       if (!empty($available)) {
+        // The title is escaped in the XML from Drupal.org and the fetch
+        // process will encode it again. Therefore, it is decoded here.
+        $available['title'] = Html::decodeEntities($available['title']) ?? '';
         // Only if we fetched and parsed something sane do we return success.
         $success = TRUE;
       }
diff --git a/core/modules/update/tests/fixtures/release-history/title_update_test.1_0.xml b/core/modules/update/tests/fixtures/release-history/title_update_test.1_0.xml
new file mode 100644
index 0000000000..638bf4504f
--- /dev/null
+++ b/core/modules/update/tests/fixtures/release-history/title_update_test.1_0.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project xmlns:dc="http://purl.org/dc/elements/1.1/">
+<title>Title Update test &amp;amp;&amp;lt;&amp;gt;</title>
+<short_name>title_update_test</short_name>
+<dc:creator>Drupal</dc:creator>
+<supported_branches>8.x-1.</supported_branches>
+<project_status>published</project_status>
+<link>http://example.com/project/title_update_test</link>
+  <terms>
+   <term><name>Projects</name><value>Modules</value></term>
+  </terms>
+<releases>
+  <release>
+    <!-- This release is not in a supported branch; therefore it should not be recommended. -->
+    <name>title_update_test 8.x-3.0</name>
+    <version>8.x-3.0</version>
+    <status>published</status>
+    <release_link>http://example.com/title_update_test-8-x-3-0-release</release_link>
+    <download_link>http://example.com/title_update_test-8.x-3.0.tar.gz</download_link>
+    <date>1250426521</date>
+    <terms>
+      <term><name>Release type</name><value>New features</value></term>
+      <term><name>Release type</name><value>Bug fixes</value></term>
+    </terms>
+  </release>
+ <release>
+  <name>title_update_test 8.x-1.0</name>
+  <version>8.x-1.0</version>
+  <status>published</status>
+  <release_link>http://example.com/title_update_test-8-x-1-0-release</release_link>
+  <download_link>http://example.com/title_update_test-8.x-1.0.tar.gz</download_link>
+  <date>1250424521</date>
+  <terms>
+   <term><name>Release type</name><value>New features</value></term>
+   <term><name>Release type</name><value>Bug fixes</value></term>
+  </terms>
+ </release>
+</releases>
+</project>
diff --git a/core/modules/update/tests/modules/title_update_test/title_update_test.info.yml b/core/modules/update/tests/modules/title_update_test/title_update_test.info.yml
new file mode 100644
index 0000000000..c4bf047f5f
--- /dev/null
+++ b/core/modules/update/tests/modules/title_update_test/title_update_test.info.yml
@@ -0,0 +1,4 @@
+name: 'Title Update test &<>'
+type: module
+description: 'Support module for testing wrongly escaped title.'
+package: Testing
diff --git a/core/modules/update/tests/src/Functional/UpdateContribTest.php b/core/modules/update/tests/src/Functional/UpdateContribTest.php
index aa753e7468..62c3774fad 100644
--- a/core/modules/update/tests/src/Functional/UpdateContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateContribTest.php
@@ -35,6 +35,7 @@ class UpdateContribTest extends UpdateTestBase {
     'aaa_update_test',
     'bbb_update_test',
     'ccc_update_test',
+    'title_update_test',
   ];
 
   /**
@@ -90,6 +91,11 @@ public function testUpdateContribBasic() {
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
+      'title_update_test' => [
+        'project' => 'title_update_test',
+        'version' => '8.x-1.0',
+        'hidden' => FALSE,
+      ],
     ];
     $this->mockInstalledExtensionsInfo($installed_extensions);
     $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
@@ -97,7 +103,8 @@ public function testUpdateContribBasic() {
       [
         'drupal' => '0.0',
         'aaa_update_test' => '1_0',
-      ]
+        'title_update_test' => '1_0',
+      ],
     );
     $this->standardTests();
     $this->assertSession()->pageTextContains('Up to date');
@@ -105,6 +112,11 @@ public function testUpdateContribBasic() {
     $this->assertSession()->pageTextNotContains('Update available');
     $this->assertSession()->linkExists('AAA Update test');
     $this->assertSession()->linkByHrefExists('http://example.com/project/aaa_update_test');
+    // Check that a wrongly escaped title it displayed correctly.
+    $this->assertSession()->pageTextNotContains("Update test &amp;&lt;&gt;");
+    $this->assertSession()->pageTextMatchesCount(2, '/Title Update test &<>/');
+    $this->assertSession()->linkExists('Title Update test');
+    $this->assertSession()->linkByHrefExists('http://example.com/project/title_update_test');
 
     // Since aaa_update_test is installed the fact it is hidden and in the
     // Testing package means it should not appear.
