diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index 3020bbe..0d69db2 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -193,6 +193,12 @@ public static function checkPlain($text) {
    *       printed as an HTML attribute value and therefore formatted with
    *       self::checkPlain() as part of that.
    *     - Some other special reason for suppressing sanitization.
+   *   - :variable: Reserved for URL values only. For now, the sanitization
+   *     behavior is the same as !variable, so only use it for URLs that have
+   *     already been filtered for dangerous protocols or are otherwise known
+   *     to not contain them (e.g., if generated from a route). See
+   *     https://www.drupal.org/node/2565895 about adding automatic protocol
+   *     sanitization.
    *
    * @return string
    *   The formatted string, which is marked as safe unless sanitization of an
@@ -225,6 +231,7 @@ public static function format($string, array $args) {
           break;
 
         case '!':
+        case ':':
           // Pass-through.
           if (!static::isSafe($value)) {
             $safe = FALSE;
diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
index fae15bb..a8e82cc 100644
--- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
@@ -221,6 +221,24 @@ function providerFormat() {
     $tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'SafeMarkup::format replaces verbatim string as-is.', FALSE);
     $tests[] = array('Verbatim text: !value', array('!value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'SafeMarkup::format replaces verbatim string as-is.', TRUE);
 
+    // @todo For now, :url is implemented the same as !value. See
+    //   https://www.drupal.org/node/2565895 about changing that.
+    //   Change the following expectations if/when that behavior is changed.
+
+    // For now, protocol filtering is the caller's responsibility.
+    $tests['url-placeholder-with-xss'] = array('URL: :url', array(':url' => 'javascript:xss()'), 'URL: javascript:xss()', 'SafeMarkup::format replaces URL as-is.', FALSE);
+
+    // Within a URL fragment, '&' and ';' have no special meaning, so '&lt;'
+    // does not refer to a '<'. The last argument being FALSE ensures that if
+    // the resulting string is output to HTML, then the entire string  will get
+    // escaped, which will preserve the correct meaning of the URL fragment.
+    $tests['url-placeholder-with-fragment-with-&;'] = array('URL: :url', array(':url' => 'http://example.com#&lt;'), 'URL: http://example.com#&lt;', 'SafeMarkup::format replaces URL as-is.', FALSE);
+
+    // Same URL as above, but already escaped for HTML by the caller. The last
+    // argument being TRUE ensures that it does not get re-escaped when output
+    // to HTML.
+    $tests['url-placeholder-with-fragment-with-&;-escaped-already'] = array('URL: :url', array(':url' => SafeMarkupTestSafeString::create('http://example.com#&amp;lt;')), 'URL: http://example.com#&amp;lt;', 'SafeMarkup::format replaces URL as-is.', TRUE);
+
     return $tests;
   }
 
