diff --git a/core/modules/image/config/image.settings.yml b/core/modules/image/config/image.settings.yml
index c6dadc8ee59a8e535c56c4a1e7b33e32f89da279..f0f123feab70b2f02e8d90052c1ad99347c20de9 100644
--- a/core/modules/image/config/image.settings.yml
+++ b/core/modules/image/config/image.settings.yml
@@ -1 +1,2 @@
 preview_image: core/modules/image/sample.png
+allow_insecure_derivatives: false
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index d7b02d036f273ba61422997c0dc82a8e31340f1a..b2816de0ff0449297acad449a4a40948ba80aa4a 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -37,6 +37,11 @@
  */
 define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
 
+/**
+ * The name of the query parameter for image derivative tokens.
+ */
+define('IMAGE_DERIVATIVE_TOKEN', 'itok');
+
 // Load all Field module hooks for Image.
 require_once DRUPAL_ROOT . '/core/modules/image/image.field.inc';
 
@@ -587,16 +592,25 @@ function image_style_options($include_empty = TRUE) {
  *   The image style
  */
 function image_style_deliver($style, $scheme) {
-  // Check that the style is defined and the scheme is valid.
-  if (!$style || !file_stream_wrapper_valid_scheme($scheme)) {
-    drupal_exit();
-  }
-
   $args = func_get_args();
   array_shift($args);
   array_shift($args);
   $target = implode('/', $args);
 
+  // Check that the style is defined, the scheme is valid, and the image
+  // derivative token is valid. (Sites which require image derivatives to be
+  // generated without a token can set the
+  // 'image.settings:allow_insecure_derivatives' configuration to TRUE to bypass
+  // the latter check, but this will increase the site's vulnerability to
+  // denial-of-service attacks.)
+  $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme);
+  if (!config('image.settings')->get('allow_insecure_derivatives')) {
+    $valid = $valid && isset($_GET[IMAGE_DERIVATIVE_TOKEN]) && $_GET[IMAGE_DERIVATIVE_TOKEN] === image_style_path_token($style->name, $scheme . '://' . $target);
+  }
+  if (!$valid) {
+    throw new AccessDeniedHttpException();
+  }
+
   $image_uri = $scheme . '://' . $target;
   $derivative_uri = image_style_path($style->id(), $image_uri);
 
@@ -784,6 +798,10 @@ function image_style_flush($style) {
  */
 function image_style_url($style_name, $path) {
   $uri = image_style_path($style_name, $path);
+  // The token query is added even if the
+  // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so that
+  // the emitted links remain valid if it is changed back to the default FALSE.
+  $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($path)));
 
   // If not using clean URLs, the image derivative callback is only available
   // with the script path. If the file does not exist, use url() to ensure
@@ -791,10 +809,33 @@ function image_style_url($style_name, $path) {
   // actual file path, this avoids bootstrapping PHP once the files are built.
   if ($GLOBALS['script_path'] && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
     $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath();
-    return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE));
+    return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query));
   }
 
-  return file_create_url($uri);
+  $file_url = file_create_url($uri);
+  // Append the query string with the token.
+  return $file_url . (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
+}
+
+/**
+ * Generates a token to protect an image style derivative.
+ *
+ * This prevents unauthorized generation of an image style derivative,
+ * which can be costly both in CPU time and disk space.
+ *
+ * @param string $style_name
+ *   The name of the image style.
+ * @param string $uri
+ *   The URI of the image for this style, for example as returned by
+ *   image_style_path().
+ *
+ * @return string
+ *   An eight-character token which can be used to protect image style
+ *   derivatives against denial-of-service attacks.
+ */
+function image_style_path_token($style_name, $uri) {
+  // Return the first eight characters.
+  return substr(drupal_hmac_base64($style_name . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
 }
 
 /**
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
index cbf49e2c7f096ad31100cfcc4fca0f7d84ae551a..f755ba0dfbd502fa0f73bf9a24339a5b9fae410f 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
@@ -84,9 +84,16 @@ function testImageStyleUrlAndPathPrivateUnclean() {
   }
 
   /**
-   * Test image_style_url().
+   * Tests image_style_url() with a file URL that has an extra slash in it.
    */
-  function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) {
+  function testImageStyleUrlExtraSlash() {
+    $this->_testImageStyleUrlAndPath('public', TRUE, TRUE);
+  }
+
+  /**
+   * Tests image_style_url().
+   */
+  function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) {
     $script_path_original = $GLOBALS['script_path'];
     $GLOBALS['script_path'] = $clean_url ? '' : 'index.php/';
 
@@ -110,13 +117,28 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) {
     $this->assertNotIdentical(FALSE, $original_uri, 'Created the generated image file.');
 
     // Get the URL of a file that has not been generated and try to create it.
-    $generated_uri = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/'. drupal_basename($original_uri);
+    $generated_uri = image_style_path($this->style_name, $original_uri);
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $generate_url = image_style_url($this->style_name, $original_uri);
 
+    // Ensure that the tests still pass when the file is generated by accessing
+    // a poorly constructed (but still valid) file URL that has an extra slash
+    // in it.
+    if ($extra_slash) {
+      $modified_uri = str_replace('://', ':///', $original_uri);
+      $this->assertNotEqual($original_uri, $modified_uri, 'An extra slash was added to the generated file URI.');
+      $generate_url = image_style_url($this->style_name, $modified_uri);
+    }
+
     if ($GLOBALS['script_path']) {
       $this->assertTrue(strpos($generate_url, $GLOBALS['script_path']) !== FALSE, 'When using non-clean URLS, the system path contains the script name.');
     }
+    // Add some extra chars to the token.
+    $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
+    $this->assertResponse(403, 'Image was inaccessible at the URL wih an invalid token.');
+    // Change the parameter name so the token is missing.
+    $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $generate_url));
+    $this->assertResponse(403, 'Image was inaccessible at the URL wih a missing token.');
 
     // Fetch the URL that generates the file.
     $this->drupalGet($generate_url);
@@ -162,6 +184,11 @@ function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) {
         $this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
       }
     }
+    elseif (!$GLOBALS['script_path']) {
+      // Add some extra chars to the token.
+      $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
+      $this->assertResponse(200, 'Existing image was accessible at the URL wih an invalid token.');
+    }
 
     $GLOBALS['script_path'] = $script_path_original;
   }
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 4c1239ab2493023451b19536424017bf94dc161d..1356d79da9908013fd22173d23ba6ea7e5f0f535 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -286,11 +286,16 @@
 /**
  * Twig debugging:
  *
- * When enabled, you can use the 'dump' function in Twig templates to output
- * information about variables, and templates are automatically recompiled
- * whenever the source code changes.
- *
- * @see http://drupal.org/node/1906392
+ * When debugging is enabled:
+ * - The markup of each Twig template is surrounded by HTML comments which
+ *   contain theming information such as template file name suggestions.
+ * - The 'dump' function can be used in Twig templates to output information
+ *   about template variables.
+ * - Twig templates are automatically recompiled whenever the source code
+ *   changes (see twig_auto_reload below).
+ *
+ * For more information about debugging Twig templates, see
+ * http://drupal.org/node/1906392.
  *
  * Not recommended in production environments (Default: FALSE).
  */