 core/core.services.yml                             |   2 +-
 .../block/src/Tests/BlockViewBuilderTest.php       |   5 +-
 .../cacheability_safeguards.info.yml               |   7 ++
 .../src/CacheabilityBubblingNodeGrantStorage.php   | 124 +++++++++++++++++++++
 .../src/CacheabilitySafeguardsServiceProvider.php  |  50 +++++++++
 .../Tests/NodeAccessCacheabilitySafeguardTest.php  |  73 ++++++++++++
 .../node_access_test_auto_bubbling.info.yml        |   6 +
 .../node_access_test_auto_bubbling.routing.yml     |   6 +
 .../NodeAccessTestAutoBubblingController.php       |  62 +++++++++++
 .../config/src/Tests/ConfigImportAllTest.php       |   2 +-
 .../MenuLinkContentCacheabilityBubblingTest.php    |   3 +-
 .../modules/node/src/Tests/NodeListBuilderTest.php |   4 +-
 .../src/Tests/Entity/EntityViewBuilderTest.php     |  10 +-
 core/modules/system/system.install                 |   7 ++
 14 files changed, 347 insertions(+), 14 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index ed1341c..b3f4195 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -9,7 +9,7 @@ parameters:
     auto_reload: null
     cache: true
   renderer.config:
-    required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
+    required_cache_contexts: ['languages:language_interface', 'theme']
     auto_placeholder_conditions:
       max-age: 0
       contexts: ['session', 'user']
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index 7e3c39a..515793d 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -9,7 +9,6 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\simpletest\KernelTestBase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -160,7 +159,7 @@ protected function verifyRenderCacheHandling() {
 
     // Test that a cache entry is created.
     $build = $this->getBlockRenderArray();
-    $cid = 'entity_view:block:test_block:' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
+    $cid = 'entity_view:block:test_block:' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys($this->container->getParameter('renderer.config')['required_cache_contexts'])->getKeys());
     $this->renderer->renderRoot($build);
     $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
 
@@ -214,7 +213,7 @@ public function testBlockViewBuilderAlter() {
     // Advanced: cached block, but an alter hook adds an additional cache key.
     $alter_add_key = $this->randomMachineName();
     \Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key);
-    $cid = 'entity_view:block:test_block:' . $alter_add_key . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
+    $cid = 'entity_view:block:test_block:' . $alter_add_key . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys($this->container->getParameter('renderer.config')['required_cache_contexts'])->getKeys());
     $expected_keys = array_merge($default_keys, array($alter_add_key));
     $build = $this->getBlockRenderArray();
     $this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
diff --git a/core/modules/cacheability_safeguards/cacheability_safeguards.info.yml b/core/modules/cacheability_safeguards/cacheability_safeguards.info.yml
new file mode 100644
index 0000000..94f3498
--- /dev/null
+++ b/core/modules/cacheability_safeguards/cacheability_safeguards.info.yml
@@ -0,0 +1,7 @@
+name: Cacheability Safeguards
+type: module
+package: Core
+core: 8.x
+required: true
+hidden: true
+version: VERSION
diff --git a/core/modules/cacheability_safeguards/src/CacheabilityBubblingNodeGrantStorage.php b/core/modules/cacheability_safeguards/src/CacheabilityBubblingNodeGrantStorage.php
new file mode 100644
index 0000000..890e968
--- /dev/null
+++ b/core/modules/cacheability_safeguards/src/CacheabilityBubblingNodeGrantStorage.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cacheability_safeguards\CacheabilityBubblingNodeGrantStorage.
+ */
+
+namespace Drupal\cacheability_safeguards;
+
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\node\NodeGrantDatabaseStorageInterface;
+use Drupal\node\NodeInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+/**
+ * Decorator for node grants storage, which bubbles access grants cacheability.
+ *
+ * Code that performs a node_access query should explicitly add the
+ * 'user.node_grants:$op' cache context to the render element or
+ * \Drupal\Core\Cache\CacheableDependencyInterface object that is affected by
+ * the query. Doing so ensures that the context is attached to the most
+ * appropriate element or object. However, since cache contexts are new to
+ * Drupal 8, to safeguard route controllers or other code that forget to do
+ * this, this decorator also adds it to the current render context.
+ *
+ * The renderer is not injected to avoid initializing the render and theme
+ * system for REST routes. Instead, this service is container-aware.
+ *
+ * @see \Drupal\Core\Render\MetadataBubblingUrlGenerator
+ *
+ * @todo Remove before Drupal 9.0.0.
+ *
+ * @ingroup node_access
+ */
+class CacheabilityBubblingNodeGrantStorage implements NodeGrantDatabaseStorageInterface, ContainerAwareInterface {
+
+  use ContainerAwareTrait;
+  use DependencySerializationTrait;
+
+  /**
+   * The non-bubbling node grant storage.
+   *
+   * @var \Drupal\node\NodeGrantDatabaseStorageInterface
+   */
+  protected $nodeGrantStorage;
+
+  /**
+   * Constructs a CacheabilityBubblingNodeGrantStorage object.
+   *
+   * @param \Drupal\node\NodeGrantDatabaseStorageInterface $node_grant_storage
+   *   The non-bubbling node grant storage.
+   */
+  public function __construct(NodeGrantDatabaseStorageInterface $node_grant_storage) {
+    $this->nodeGrantStorage = $node_grant_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(NodeInterface $node, $operation, $langcode, AccountInterface $account) {
+    return $this->nodeGrantStorage->access($node, $operation, $langcode, $account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkAll(AccountInterface $account) {
+    return $this->nodeGrantStorage->checkAll($account);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table) {
+    // Bubble the 'user.node_grants:$op' cache context to the current render
+    // context.
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    if ($renderer->hasRenderContext()) {
+      $build = ['#cache' => ['contexts' => ['user.node_grants:' . $op]]];
+      $renderer->render($build);
+    }
+
+    return $this->nodeGrantStorage->alterQuery($query, $tables, $op, $account, $base_table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
+    return $this->nodeGrantStorage->write($node, $grants, $realm, $delete);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete() {
+    return $this->nodeGrantStorage->delete();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function writeDefault() {
+    return $this->nodeGrantStorage->writeDefault();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return $this->nodeGrantStorage->count();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteNodeRecords(array $nids) {
+    return $this->nodeGrantStorage->deleteNodeRecords($nids);
+  }
+
+}
diff --git a/core/modules/cacheability_safeguards/src/CacheabilitySafeguardsServiceProvider.php b/core/modules/cacheability_safeguards/src/CacheabilitySafeguardsServiceProvider.php
new file mode 100644
index 0000000..78a8b37
--- /dev/null
+++ b/core/modules/cacheability_safeguards/src/CacheabilitySafeguardsServiceProvider.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cacheability_safeguards\CacheContextSafeguardsServiceProvider.
+ */
+
+namespace Drupal\cacheability_safeguards;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceModifierInterface;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * …
+ */
+class CacheabilitySafeguardsServiceProvider implements ServiceModifierInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    // Allow sites to opt out from the cacheability safeguards.
+    if ($container->hasParameter('cacheability_safeguards') && $container->getParameter('cacheability_safeguards') === FALSE) {
+      return;
+    }
+
+    // Also make the 'user.permissions' cache context required.
+    if ($container->hasParameter('renderer.config')) {
+      $renderer_config = $container->getParameter('renderer.config');
+      if (!in_array('user.permissions', $renderer_config['required_cache_contexts'])) {
+        $renderer_config['required_cache_contexts'][] = 'user.permissions';
+        sort($renderer_config['required_cache_contexts']);
+      }
+      $container->setParameter('renderer.config', $renderer_config);
+    }
+
+    // Automatically bubble the 'user.node_grants:$op' cache context.
+    if ($container->has('node.grant_storage')) {
+      $container->setDefinition('node.grant_storage.non_bubbling', $container->getDefinition('node.grant_storage'))
+        ->setPublic(FALSE);
+      $container->register('node.grant_storage')
+        ->setClass('\Drupal\cacheability_safeguards\CacheabilityBubblingNodeGrantStorage')
+        ->addArgument(new Reference('node.grant_storage.non_bubbling'))
+        ->addMethodCall('setContainer', [new Reference('service_container')]);
+    }
+  }
+
+}
+
diff --git a/core/modules/cacheability_safeguards/src/Tests/NodeAccessCacheabilitySafeguardTest.php b/core/modules/cacheability_safeguards/src/Tests/NodeAccessCacheabilitySafeguardTest.php
new file mode 100644
index 0000000..97628f1
--- /dev/null
+++ b/core/modules/cacheability_safeguards/src/Tests/NodeAccessCacheabilitySafeguardTest.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cacheability_safeguards\Tests\NodeAccessCacheabilitySafeguardTest.
+ */
+
+namespace Drupal\cacheability_safeguards\Tests;
+
+use Drupal\Core\Url;
+use Drupal\node\Tests\NodeTestBase;
+
+/**
+ * Tests the node access cacheability safeguard.
+ *
+ * @group cacheability_safeguards
+ * @group node
+ * @group Cache
+ */
+class NodeAccessCacheabilitySafeguardTest extends NodeTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node_access_test', 'node_access_test_auto_bubbling'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    node_access_rebuild();
+
+    // Create some content.
+    $this->drupalCreateNode();
+    $this->drupalCreateNode();
+    $this->drupalCreateNode();
+    $this->drupalCreateNode();
+  }
+
+  /**
+   * Tests that the node grants cache context is auto-added, only when needed.
+   *
+   * @see \Drupal\cacheability_safeguards\CacheabilityBubblingNodeGrantStorage
+   */
+  public function testNodeAccessCacheabilitySafeguard() {
+    $this->dumpHeaders = TRUE;
+
+    // The node grants cache context should be added automatically.
+    $this->drupalGet(new Url('node_access_test_auto_bubbling'));
+    $this->assertCacheContext('user.node_grants:view');
+
+    // The root user has the 'bypass node access' permission, which means the
+    // node grants cache context is not necessary.
+    $this->drupalLogin($this->rootUser);
+    $this->drupalGet(new Url('node_access_test_auto_bubbling'));
+    $this->assertNoCacheContext('user.node_grants:view');
+    $this->drupalLogout();
+
+    // Uninstall the module with the only hook_node_grants() implementation.
+    $this->container->get('module_installer')->uninstall(['node_access_test']);
+    $this->rebuildContainer();
+
+    // Because there are no node grants defined, there also is no need for the
+    // node grants cache context to be bubbled.
+    $this->drupalGet(new Url('node_access_test_auto_bubbling'));
+    $this->assertNoCacheContext('user.node_grants:view');
+  }
+
+}
diff --git a/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml
new file mode 100644
index 0000000..49a990d
--- /dev/null
+++ b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.info.yml
@@ -0,0 +1,6 @@
+name: 'Node module access automatic cacheability bubbling tests'
+type: module
+description: 'Support module for node permission testing. Provides a route which does a node access query without explicitly specifying the corresponding cache context.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.routing.yml b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.routing.yml
new file mode 100644
index 0000000..34fd420
--- /dev/null
+++ b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/node_access_test_auto_bubbling.routing.yml
@@ -0,0 +1,6 @@
+node_access_test_auto_bubbling:
+  path: '/node_access_test_auto_bubbling'
+  defaults:
+    _controller: '\Drupal\node_access_test_auto_bubbling\Controller\NodeAccessTestAutoBubblingController::latest'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/src/Controller/NodeAccessTestAutoBubblingController.php b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/src/Controller/NodeAccessTestAutoBubblingController.php
new file mode 100644
index 0000000..c7788d0
--- /dev/null
+++ b/core/modules/cacheability_safeguards/tests/node_access_test_auto_bubbling/src/Controller/NodeAccessTestAutoBubblingController.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node_access_test_auto_bubbling\Controller\NodeAccessTestAutoBubblingController.
+ */
+
+namespace Drupal\node_access_test_auto_bubbling\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns a node ID listing.
+ */
+class NodeAccessTestAutoBubblingController extends ControllerBase implements ContainerInjectionInterface {
+
+  /**
+   * The entity query factory service.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQuery;
+
+  /**
+   * Constructs a new NodeAccessTestAutoBubblingController.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
+   *   The entity query factory.
+   */
+  public function __construct(QueryFactory $entity_query) {
+    $this->entityQuery = $entity_query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.query')
+    );
+  }
+
+  /**
+   * Lists the three latest published node IDs.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function latest() {
+    $nids = $this->entityQuery->get('node')
+      ->condition('status', NODE_PUBLISHED)
+      ->sort('created', 'DESC')
+      ->range(0, 3)
+      ->addTag('node_access')
+      ->execute();
+    return ['#markup' => $this->t('The three latest nodes are: !nids.', ['!nids' => implode(', ', $nids)])];
+  }
+
+}
diff --git a/core/modules/config/src/Tests/ConfigImportAllTest.php b/core/modules/config/src/Tests/ConfigImportAllTest.php
index 6e1aaea..25099a9 100644
--- a/core/modules/config/src/Tests/ConfigImportAllTest.php
+++ b/core/modules/config/src/Tests/ConfigImportAllTest.php
@@ -102,7 +102,7 @@ public function testInstallUninstall() {
 
     // Ensure that only core required modules and the install profile can not be uninstalled.
     $validation_reasons = \Drupal::service('module_installer')->validateUninstall(array_keys($all_modules));
-    $this->assertEqual(['standard', 'system', 'user'], array_keys($validation_reasons));
+    $this->assertEqual(['cacheability_safeguards', 'standard', 'system', 'user'], array_keys($validation_reasons));
 
     $modules_to_uninstall = array_filter($all_modules, function ($module) use ($validation_reasons) {
       // Filter required and not enabled modules.
diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentCacheabilityBubblingTest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentCacheabilityBubblingTest.php
index 825f493..22fd283 100644
--- a/core/modules/menu_link_content/src/Tests/MenuLinkContentCacheabilityBubblingTest.php
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentCacheabilityBubblingTest.php
@@ -8,7 +8,6 @@
 namespace Drupal\menu_link_content\Tests;
 
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Menu\MenuTreeParameters;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\menu_link_content\Entity\MenuLinkContent;
@@ -69,7 +68,7 @@ public function testOutboundPathAndRouteProcessing() {
     $default_menu_cacheability = (new BubbleableMetadata())
       ->setCacheMaxAge(Cache::PERMANENT)
       ->setCacheTags(['config:system.menu.tools'])
-      ->setCacheContexts(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']);
+      ->setCacheContexts($this->container->getParameter('renderer.config')['required_cache_contexts']);
 
     User::create(['uid' => 1, 'name' => $this->randomString()])->save();
     User::create(['uid' => 2, 'name' => $this->randomString()])->save();
diff --git a/core/modules/node/src/Tests/NodeListBuilderTest.php b/core/modules/node/src/Tests/NodeListBuilderTest.php
index 2d23604..c62e048 100644
--- a/core/modules/node/src/Tests/NodeListBuilderTest.php
+++ b/core/modules/node/src/Tests/NodeListBuilderTest.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\node\Tests;
 
-use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Cache\Cache;
 use Drupal\simpletest\KernelTestBase;
 
 /**
@@ -39,7 +39,7 @@ public function testCacheContexts() {
     $build = $list_builder->render();
     $this->container->get('renderer')->renderRoot($build);
 
-    $this->assertEqual(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions'], $build['#cache']['contexts']);
+    $this->assertEqual(Cache::mergeContexts($this->container->getParameter('renderer.config')['required_cache_contexts'], ['url.query_args.pagers:0', 'user.node_grants:view']), $build['#cache']['contexts']);
   }
 
 }
diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
index 169af5f..5351f75 100644
--- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
@@ -7,9 +7,7 @@
 
 namespace Drupal\system\Tests\Entity;
 
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
-use Drupal\Core\Cache\Cache;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 
@@ -65,7 +63,7 @@ public function testEntityViewBuilderCache() {
     // Get a fully built entity view render array.
     $entity_test->save();
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys($this->container->getParameter('renderer.config')['required_cache_contexts'])->getKeys());
     $cid = implode(':', $cid_parts);
     $bin = $build['#cache']['bin'];
 
@@ -97,6 +95,8 @@ public function testEntityViewBuilderCache() {
    * Tests entity render cache with references.
    */
   public function testEntityViewBuilderCacheWithReferences() {
+    $required_cache_contexts = $this->container->getParameter('renderer.config')['required_cache_contexts'];
+
     /** @var \Drupal\Core\Render\RendererInterface $renderer */
     $renderer = $this->container->get('renderer');
     $cache_contexts_manager = \Drupal::service("cache_contexts_manager");
@@ -117,7 +117,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array for the referenced entity.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys($required_cache_contexts)->getKeys());
     $cid_reference = implode(':', $cid_parts);
     $bin_reference = $build['#cache']['bin'];
 
@@ -136,7 +136,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
+    $cid_parts = array_merge($build['#cache']['keys'], $cache_contexts_manager->convertTokensToKeys($required_cache_contexts)->getKeys());
     $cid = implode(':', $cid_parts);
     $bin = $build['#cache']['bin'];
 
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ce06b7a..29a28c7 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1268,3 +1268,10 @@ function system_update_8004() {
     $manager->updateEntityType($manager->getEntityType($entity_type_id));
   }
 }
+
+/**
+ * Install the cache_context_safeguards module.
+ */
+function system_update_8005() {
+  \Drupal::service('module_installer')->install(['cache_context_safeguards']);
+}
