diff --git a/core/misc/drupal.es6.js b/core/misc/drupal.es6.js
index 48f0510346d016fcb24ba66b98b72add7eea8e3e..546527a34c6bf90dc09e17ae53a47d95afeaa916 100644
--- a/core/misc/drupal.es6.js
+++ b/core/misc/drupal.es6.js
@@ -239,9 +239,10 @@ window.Drupal = { behaviors: {}, locale: {} };
   Drupal.checkPlain = function (str) {
     str = str.toString()
       .replace(/&/g, '&')
-      .replace(/"/g, '"')
       .replace(/</g, '&lt;')
-      .replace(/>/g, '&gt;');
+      .replace(/>/g, '&gt;')
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&#39;');
     return str;
   };
 
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index e080caae65afc993d12e8051e18fcfeb59761773..39a74e4f51863ef988be9b8bc987ae4b4798aa6a 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -48,7 +48,7 @@ window.Drupal = { behaviors: {}, locale: {} };
   };
 
   Drupal.checkPlain = function (str) {
-    str = str.toString().replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+    str = str.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
     return str;
   };
 
diff --git a/core/modules/comment/src/Controller/CommentController.php b/core/modules/comment/src/Controller/CommentController.php
index a01106d77f611c6614eab9f22295bd1d5978a4fc..560233889cccc69f6e042f56a2d2ff927d2fd846 100644
--- a/core/modules/comment/src/Controller/CommentController.php
+++ b/core/modules/comment/src/Controller/CommentController.php
@@ -279,9 +279,12 @@ public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NUL
     // Check if the user has the proper permissions.
     $access = AccessResult::allowedIfHasPermission($account, 'post comments');
 
+    // If commenting is open on the entity.
     $status = $entity->{$field_name}->status;
     $access = $access->andIf(AccessResult::allowedIf($status == CommentItemInterface::OPEN)
-      ->addCacheableDependency($entity));
+      ->addCacheableDependency($entity))
+      // And if user has access to the host entity.
+      ->andIf(AccessResult::allowedIf($entity->access('view')));
 
     // $pid indicates that this is a reply to a comment.
     if ($pid) {
diff --git a/core/modules/comment/tests/src/Functional/CommentAccessTest.php b/core/modules/comment/tests/src/Functional/CommentAccessTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4094ec6130e7505bb271240c8c6436633ba48e28
--- /dev/null
+++ b/core/modules/comment/tests/src/Functional/CommentAccessTest.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Drupal\Tests\comment\Functional;
+
+use Drupal\comment\Entity\Comment;
+use Drupal\comment\Tests\CommentTestTrait;
+use Drupal\node\Entity\NodeType;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests comment administration and preview access.
+ *
+ * @group comment
+ */
+class CommentAccessTest extends BrowserTestBase {
+
+  use CommentTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'node',
+    'comment',
+  ];
+
+  /**
+   * Node for commenting.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $unpublishedNode;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $node_type = NodeType::create([
+      'type' => 'article',
+      'name' => 'Article',
+    ]);
+    $node_type->save();
+    $node_author = $this->drupalCreateUser([
+      'create article content',
+      'access comments',
+    ]);
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'edit own comments',
+      'skip comment approval',
+      'post comments',
+      'access comments',
+      'access content',
+    ]));
+
+    $this->addDefaultCommentField('node', 'article');
+    $this->unpublishedNode = $this->createNode([
+      'title' => 'This is unpublished',
+      'uid' => $node_author->id(),
+      'status' => 0,
+      'type' => 'article',
+    ]);
+    $this->unpublishedNode->save();
+  }
+
+  /**
+   * Tests commenting disabled for access-blocked entities.
+   */
+  public function testCannotCommentOnEntitiesYouCannotView() {
+    $assert = $this->assertSession();
+
+    $comment_url = 'comment/reply/node/' . $this->unpublishedNode->id() . '/comment';
+
+    // Commenting on an unpublished node results in access denied.
+    $this->drupalGet($comment_url);
+    $assert->statusCodeEquals(403);
+
+    // Publishing the node grants access.
+    $this->unpublishedNode->setPublished(TRUE)->save();
+    $this->drupalGet($comment_url);
+    $assert->statusCodeEquals(200);
+  }
+
+  /**
+   * Tests cannot view comment reply form on entities you cannot view.
+   */
+  public function testCannotViewCommentReplyFormOnEntitiesYouCannotView() {
+    $assert = $this->assertSession();
+
+    // Create a comment on an unpublished node.
+    $comment = Comment::create([
+      'entity_type' => 'node',
+      'name' => 'Tony',
+      'hostname' => 'magic.example.com',
+      'mail' => 'foo@example.com',
+      'subject' => 'Comment on unpublished node',
+      'entity_id' => $this->unpublishedNode->id(),
+      'comment_type' => 'comment',
+      'field_name' => 'comment',
+      'pid' => 0,
+      'uid' => $this->unpublishedNode->getOwnerId(),
+      'status' => 1,
+    ]);
+    $comment->save();
+
+    $comment_url = 'comment/reply/node/' . $this->unpublishedNode->id() . '/comment/' . $comment->id();
+
+    // Replying to a comment on an unpublished node results in access denied.
+    $this->drupalGet($comment_url);
+    $assert->statusCodeEquals(403);
+
+    // Publishing the node grants access.
+    $this->unpublishedNode->setPublished(TRUE)->save();
+    $this->drupalGet($comment_url);
+    $assert->statusCodeEquals(200);
+  }
+
+}
diff --git a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
index 5889d69deefc535366cd5fae0c965199315bc7d7..7580bb20307ae3a25607cfaa4a4664a5632940fa 100644
--- a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
@@ -450,6 +450,7 @@ public function testCommentFunctionality() {
       'post comments',
       'administer comment fields',
       'administer comment types',
+      'view test entity',
     ]);
     $this->drupalLogin($limited_user);
 
diff --git a/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
index a7fe6b5e072942f5daa48ecadd29eb6cc2775df5..72a941f9c5a454e52a7389f97553743671f9231e 100644
--- a/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
@@ -144,6 +144,7 @@ public function testCommentTokenReplacement() {
 
     // Create a user and a comment.
     $user = User::create(['name' => 'alice']);
+    $user->activate();
     $user->save();
     $this->postComment($user, 'user body', 'user subject', TRUE);
 
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index 6469245c49bc47f484b4599936724c7a2e361e85..9b0cda8e56777fd4731613fc12f85334340a743e 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -255,3 +255,15 @@ function node_update_8400() {
   $schema['fields']['realm']['description'] = 'The realm in which the user must possess the grant ID. Modules can define one or more realms by implementing hook_node_grants().';
   Database::getConnection()->schema()->changeField('node_access', 'realm', 'realm', $schema['fields']['realm']);
 }
+
+/**
+ * Run a node access rebuild, if required.
+ */
+function node_update_8401() {
+  // Get the list of node access modules.
+  $modules = \Drupal::moduleHandler()->getImplementations('node_grants');
+  // If multilingual usage, then rebuild node access.
+  if (count($modules) > 0 && \Drupal::languageManager()->isMultilingual()) {
+    node_access_needs_rebuild(TRUE);
+  }
+}
diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
index f6ae1f6c1343982a8aa06b240038002722d28997..b4947c9f123e4b46acb36b5b26d4efee5363d91d 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
@@ -211,6 +211,7 @@ public function write(NodeInterface $node, array $grants, $realm = NULL, $delete
       $query = $this->database->insert('node_access')->fields(['nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete']);
       // If we have defined a granted langcode, use it. But if not, add a grant
       // for every language this node is translated to.
+      $fallback_langcode = $node->getUntranslated()->language()->getId();
       foreach ($grants as $grant) {
         if ($realm && $realm != $grant['realm']) {
           continue;
@@ -227,7 +228,7 @@ public function write(NodeInterface $node, array $grants, $realm = NULL, $delete
             $grant['nid'] = $node->id();
             $grant['langcode'] = $grant_langcode;
             // The record with the original langcode is used as the fallback.
-            if ($grant['langcode'] == $node->language()->getId()) {
+            if ($grant['langcode'] == $fallback_langcode) {
               $grant['fallback'] = 1;
             }
             else {
diff --git a/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php b/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4beacc34235aea041b7735a6baa93d0f12b53755
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/NodeAccessLanguageFallbackTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Tests\node\Functional;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests that the node_access system stores the proper fallback marker.
+ *
+ * @group node
+ */
+class NodeAccessLanguageFallbackTest extends NodeTestBase {
+
+  /**
+   * Enable language and a non-language-aware node access module.
+   *
+   * @var array
+   */
+  public static $modules = ['language', 'node_access_test', 'content_translation'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // After enabling a node access module, the {node_access} table has to be
+    // rebuilt.
+    node_access_rebuild();
+
+    // Add Hungarian, Catalan, and Afrikaans.
+    ConfigurableLanguage::createFromLangcode('hu')->save();
+    ConfigurableLanguage::createFromLangcode('ca')->save();
+    ConfigurableLanguage::createFromLangcode('af')->save();
+
+    // Enable content translation for the current entity type.
+    \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE);
+  }
+
+  /**
+   * Tests node access fallback handling with multiple node languages.
+   */
+  public function testNodeAccessLanguageFallback() {
+    // The node_access_test module allows nodes to be marked private. We need to
+    // ensure that system honors the fallback system of node access properly.
+    // Note that node_access_test_language is language-sensitive and does not
+    // apply to the fallback test.
+
+    // Create one node in Hungarian and marked as private.
+    $node = $this->drupalCreateNode([
+      'body' => [[]],
+      'langcode' => 'hu',
+      'private' => [['value' => 1]],
+      'status' => 1,
+    ]);
+
+    // There should be one entry in node_access, with fallback set to hu.
+    $this->checkRecords(1, 'hu');
+
+    // Create a translation user.
+    $admin = $this->drupalCreateUser([
+      'bypass node access',
+      'administer nodes',
+      'translate any entity',
+      'administer content translation',
+    ]);
+    $this->drupalLogin($admin);
+    $this->drupalGet('node/' . $node->id() . '/translations');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Create a Catalan translation through the UI.
+    $url_options = ['language' => \Drupal::languageManager()->getLanguage('ca')];
+    $this->drupalGet('node/' . $node->id() . '/translations/add/hu/ca', $url_options);
+    $this->assertSession()->statusCodeEquals(200);
+    // Save the form.
+    $this->getSession()->getPage()->pressButton('Save (this translation)');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Check the node access table.
+    $this->checkRecords(2, 'hu');
+
+    // Programmatically create a translation. This process lets us check that
+    // both forms and code behave in the same way.
+    $storage = \Drupal::entityTypeManager()->getStorage('node');
+    // Reload the node.
+    $node = $storage->load(1);
+    // Create an Afrikaans translation.
+    $translation = $node->addTranslation('af');
+    $translation->title->value = $this->randomString();
+    $translation->status = 1;
+    $node->save();
+
+    // Check the node access table.
+    $this->checkRecords(3, 'hu');
+
+    // For completeness, edit the Catalan version again.
+    $this->drupalGet('node/' . $node->id() . '/edit', $url_options);
+    $this->assertSession()->statusCodeEquals(200);
+    // Save the form.
+    $this->getSession()->getPage()->pressButton('Save (this translation)');
+    $this->assertSession()->statusCodeEquals(200);
+    // Check the node access table.
+    $this->checkRecords(3, 'hu');
+  }
+
+  /**
+   * Queries the node_access table and checks for proper storage.
+   *
+   * @param int $count
+   *   The number of rows expected by the query (equal to the translation
+   *   count).
+   * @param $langcode
+   *   The expected language code set as the fallback property.
+   */
+  public function checkRecords($count, $langcode = 'hu') {
+    $select = \Drupal::database()
+      ->select('node_access', 'na')
+      ->fields('na', ['nid', 'fallback', 'langcode', 'grant_view'])
+      ->condition('na.realm', 'node_access_test', '=')
+      ->condition('na.gid', 8888, '=');
+    $records = $select->execute()->fetchAll();
+    // Check that the expected record count is returned.
+    $this->assertEquals(count($records), $count);
+    // The fallback value is 'hu' and should be set to 1. For other languages,
+    // it should be set to 0. Casting to boolean lets us run that comparison.
+    foreach ($records as $record) {
+      $this->assertEquals((bool) $record->fallback, $record->langcode === $langcode);
+    }
+  }
+
+}
diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
index 7475dab64a66b0c073755742082395a2986d276f..cc87d283b57efa5418c5bbf4f6a26b73f6f02599 100644
--- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
+++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php
@@ -73,7 +73,11 @@ protected function setUp() {
    *
    * @dataProvider providerTestBlocks
    */
-  public function testBlocks($theme, $block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
+  public function testBlocks($theme, $block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item, $permissions) {
+    if ($permissions) {
+      $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), $permissions);
+    }
+
     $web_assert = $this->assertSession();
     $page = $this->getSession()->getPage();
     $this->enableTheme($theme);
@@ -174,6 +178,7 @@ public function providerTestBlocks() {
           'label_selector' => 'h2',
           'button_text' => 'Save Powered by Drupal',
           'toolbar_item' => '#toolbar-item-user',
+          NULL,
         ],
         "$theme: block-branding" => [
           'theme' => $theme,
@@ -183,6 +188,7 @@ public function providerTestBlocks() {
           'label_selector' => "a[rel='home']:last-child",
           'button_text' => 'Save Site branding',
           'toolbar_item' => '#toolbar-item-administration',
+          ['administer site configuration'],
         ],
         "$theme: block-search" => [
           'theme' => $theme,
@@ -192,6 +198,7 @@ public function providerTestBlocks() {
           'label_selector' => 'h2',
           'button_text' => 'Save Search form',
           'toolbar_item' => NULL,
+          NULL,
         ],
         // This is the functional JS test coverage accompanying
         // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
@@ -203,6 +210,7 @@ public function providerTestBlocks() {
           'label_selector' => NULL,
           'button_text' => NULL,
           'toolbar_item' => NULL,
+          NULL,
         ],
         // This is the functional JS test coverage accompanying
         // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
@@ -214,6 +222,7 @@ public function providerTestBlocks() {
           'label_selector' => NULL,
           'button_text' => NULL,
           'toolbar_item' => NULL,
+          NULL,
         ],
       ];
     }
@@ -551,6 +560,71 @@ protected function isLabelInputVisible() {
     return $this->getSession()->getPage()->find('css', static::LABEL_INPUT_SELECTOR)->isVisible();
   }
 
+  /**
+   * Tests access to block forms with related configuration is correct.
+   */
+  public function testBlockConfigAccess() {
+    $page = $this->getSession()->getPage();
+    $web_assert = $this->assertSession();
+
+    // Confirm that System Branding block does not expose Site Name field
+    // without permission.
+    $block = $this->placeBlock('system_branding_block');
+    $this->drupalGet('user');
+    $this->enableEditMode();
+    $this->openBlockForm($this->getBlockSelector($block));
+    // The site name field should not appear because the user doesn't have
+    // permission.
+    $web_assert->fieldNotExists('settings[site_information][site_name]');
+    $page->pressButton('Save Site branding');
+    $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
+    $web_assert->assertWaitOnAjaxRequest();
+    // Confirm we did not save changes to the configuration.
+    $this->assertEquals('Drupal', \Drupal::configFactory()->getEditable('system.site')->get('name'));
+
+    $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['administer site configuration']);
+    $this->drupalGet('user');
+    $this->openBlockForm($this->getBlockSelector($block));
+    // The site name field should appear because the user does have permission.
+    $web_assert->fieldExists('settings[site_information][site_name]');
+
+    // Confirm that the Menu block does not expose menu configuration without
+    // permission.
+    // Add a link or the menu will not render.
+    $menu_link_content = MenuLinkContent::create([
+      'title' => 'This is on the menu',
+      'menu_name' => 'main',
+      'link' => ['uri' => 'route:<front>'],
+    ]);
+    $menu_link_content->save();
+    $this->assertNotEmpty($menu_link_content->isEnabled());
+    $menu_without_overrides = \Drupal::configFactory()->getEditable('system.menu.main')->get();
+    $block = $this->placeBlock('system_menu_block:main');
+    $this->drupalGet('user');
+    $web_assert->pageTextContains('This is on the menu');
+    $this->openBlockForm($this->getBlockSelector($block));
+    // Edit menu form should not appear because the user doesn't have
+    // permission.
+    $web_assert->pageTextNotContains('Edit menu');
+    $page->pressButton('Save Main navigation');
+    $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
+    $web_assert->assertWaitOnAjaxRequest();
+    // Confirm we did not save changes to the menu or the menu link.
+    $this->assertEquals($menu_without_overrides, \Drupal::configFactory()->getEditable('system.menu.main')->get());
+    $menu_link_content = MenuLinkContent::load($menu_link_content->id());
+    $this->assertNotEmpty($menu_link_content->isEnabled());
+    // Confirm menu is still on the page.
+    $this->drupalGet('user');
+    $web_assert->pageTextContains('This is on the menu');
+
+    $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['administer menu']);
+    $this->drupalGet('user');
+    $web_assert->pageTextContains('This is on the menu');
+    $this->openBlockForm($this->getBlockSelector($block));
+    // Edit menu form should appear because the user does have permission.
+    $web_assert->pageTextContains('Edit menu');
+  }
+
   /**
    * Test that validation errors appear in the off-canvas dialog.
    */
@@ -634,6 +708,7 @@ public function testOverriddenBlock() {
   public function testOverriddenConfigurationRemoved() {
     $web_assert = $this->assertSession();
     $page = $this->getSession()->getPage();
+    $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['administer site configuration', 'administer menu']);
 
     // Confirm the branding block does include 'site_information' section when
     // the site name is not overridden.
diff --git a/core/modules/system/src/Form/SystemBrandingOffCanvasForm.php b/core/modules/system/src/Form/SystemBrandingOffCanvasForm.php
index 35e683080dc2414ce4402aebfb622251e9a2d819..120a6580062a7320c3df0b99ee8c20d5bb1a0516 100644
--- a/core/modules/system/src/Form/SystemBrandingOffCanvasForm.php
+++ b/core/modules/system/src/Form/SystemBrandingOffCanvasForm.php
@@ -7,6 +7,7 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginFormBase;
+use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -30,14 +31,24 @@ class SystemBrandingOffCanvasForm extends PluginFormBase implements ContainerInj
    */
   protected $configFactory;
 
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
   /**
    * SystemBrandingOffCanvasForm constructor.
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The config factory.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
    */
-  public function __construct(ConfigFactoryInterface $config_factory) {
+  public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $current_user) {
     $this->configFactory = $config_factory;
+    $this->currentUser = $current_user;
   }
 
   /**
@@ -45,7 +56,8 @@ public function __construct(ConfigFactoryInterface $config_factory) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('config.factory')
+      $container->get('config.factory'),
+      $container->get('current_user')
     );
   }
 
@@ -68,7 +80,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
       '#type' => 'details',
       '#title' => t('Site details'),
       '#open' => TRUE,
-      '#access' => AccessResult::allowedIf(!$site_config_immutable->hasOverrides('name') && !$site_config_immutable->hasOverrides('slogan')),
+      '#access' => $this->currentUser->hasPermission('administer site configuration') && !$site_config_immutable->hasOverrides('name') && !$site_config_immutable->hasOverrides('slogan'),
     ];
     $form['site_information']['site_name'] = [
       '#type' => 'textfield',
diff --git a/core/modules/system/src/Form/SystemMenuOffCanvasForm.php b/core/modules/system/src/Form/SystemMenuOffCanvasForm.php
index 82390bcbccdafd3650d33ea9cf65ca2801390797..a561a5bd9a5aac6baf28f3d47483d885451b4e97 100644
--- a/core/modules/system/src/Form/SystemMenuOffCanvasForm.php
+++ b/core/modules/system/src/Form/SystemMenuOffCanvasForm.php
@@ -3,7 +3,6 @@
 namespace Drupal\system\Form;
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
-use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
@@ -98,7 +97,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
       '#type' => 'details',
       '#title' => $this->t('Edit menu %label', ['%label' => $this->menu->label()]),
       '#open' => TRUE,
-      '#access' => AccessResult::allowedIf(!$this->hasMenuOverrides()),
+      '#access' => !$this->hasMenuOverrides() && $this->menu->access('edit'),
     ];
     $form['entity_form'] += $this->getEntityForm($this->menu)->buildForm([], $form_state);