diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php
index 6c5aaa3..fde75f6 100644
--- a/core/lib/Drupal/Core/Template/TwigEnvironment.php
+++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php
@@ -84,6 +84,10 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension
     // Ensure autoescaping is always on.
     $options['autoescape'] = TRUE;
 
+    $policy = new TwigSandboxPolicy();
+    $sandbox = new \Twig_Extension_Sandbox($policy, TRUE);
+    $this->addExtension($sandbox);
+
     $this->loader = $loader;
     parent::__construct($this->loader, $options);
   }
diff --git a/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php
new file mode 100644
index 0000000..dd4044e
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Template\TwigSandboxPolicy.
+ */
+
+namespace Drupal\Core\Template;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Default sandbox policy for Twig templates.
+ *
+ * Twig's sandbox extension is usually used to evaluate untrusted code by
+ * limiting access to potentially unsafe attributes or methods. Since we do not
+ * use ViewModels when passing objects to Twig templates, we limit what those
+ * objects can do by whitelisting certain objects, methods, and attributes.
+ */
+class TwigSandboxPolicy implements \Twig_Sandbox_SecurityPolicyInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkSecurity($tags, $filters, $functions) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkPropertyAllowed($obj, $property) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkMethodAllowed($obj, $method) {
+    if ($obj instanceof Attribute) {
+      // Allow any operations on the Attribute object as it is intended to be
+      // changed from a Twig template, for example calling addClass().
+      return TRUE;
+    }
+
+    // Only allow idempotent methods to be called on objects.
+    $whitelist = [
+      'id' => TRUE,
+      'label' => TRUE,
+      'bundle' => TRUE,
+      'get' => TRUE,
+      '__toString' => TRUE,
+    ];
+    if (isset($whitelist[$method])) {
+      return TRUE;
+    }
+
+    // If the method name starts with a whitelisted prefix, allow it.
+    if (preg_match('/^(get|has|is)[A-Za-z]/', $method)) {
+      return TRUE;
+    }
+
+    throw new \Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj)));
+  }
+
+}
diff --git a/core/modules/node/templates/node.html.twig b/core/modules/node/templates/node.html.twig
index 5ed0775..d793f3a 100644
--- a/core/modules/node/templates/node.html.twig
+++ b/core/modules/node/templates/node.html.twig
@@ -4,12 +4,10 @@
  * Default theme implementation to display a node.
  *
  * Available variables:
- * - node: Full node entity.
- *   - id: The node ID.
- *   - bundle: The type of the node, for example, "page" or "article".
- *   - authorid: The user ID of the node author.
- *   - createdtime: Time the node was published formatted in Unix timestamp.
- *   - changedtime: Time the node was changed formatted in Unix timestamp.
+ * - node: The node entity with limited access to object properties and methods.
+     Only "getter" methods (method names starting with "get", "has", or "is")
+     and a few common attributes such as "id" and "label" are available. Calling
+     other methods (such as node.delete) will result in an exception.
  * - label: The title of the node.
  * - content: All node items. Use {{ content }} to print them all,
  *   or print a subset such as {{ content.field_example }}. Use
diff --git a/core/modules/system/src/Tests/Theme/TwigWhiteListTest.php b/core/modules/system/src/Tests/Theme/TwigWhiteListTest.php
new file mode 100644
index 0000000..a48debe
--- /dev/null
+++ b/core/modules/system/src/Tests/Theme/TwigWhiteListTest.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Theme\TwigWhiteListTest.php
+ */
+
+namespace Drupal\system\Tests\Theme;
+
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\simpletest\KernelTestBase;
+use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\Entity\Vocabulary;
+
+/**
+ * Tests white-listing of entity properties.
+ *
+ * @group Theme
+ */
+class TwigWhiteListTest extends KernelTestBase {
+
+  /**
+   * Term for referencing.
+   *
+   * @var \Drupal\taxonomy\TermInterface
+   */
+  protected $term;
+
+  /**
+   * Twig environment.
+   *
+   * @var \Drupal\Core\Template\TwigEnvironment
+   */
+  protected $twig;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node', 'taxonomy', 'user', 'system', 'text', 'field', 'entity_reference'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installSchema('system', array('router', 'sequences'));
+    $this->installEntitySchema('node');
+    $this->installEntitySchema('user');
+    $this->installEntitySchema('taxonomy_term');
+    NodeType::create([
+      'type' => 'page',
+      'name' => 'Basic page',
+      'display_submitted' => FALSE,
+    ])->save();
+    // Add a vocabulary so we can test different view modes.
+    $vocabulary = Vocabulary::create([
+      'name' => $this->randomMachineName(),
+      'description' => $this->randomMachineName(),
+      'vid' => $this->randomMachineName(),
+      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+      'help' => '',
+    ]);
+    $vocabulary->save();
+
+    // Add a term to the vocabulary.
+    $this->term = Term::create([
+      'name' => 'Sometimes people are just jerks',
+      'description' => $this->randomMachineName(),
+      'vid' => $vocabulary->id(),
+      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+    ]);
+    $this->term->save();
+
+    // Create a field.
+    $handler_settings = array(
+      'target_bundles' => array(
+        $vocabulary->id() => $vocabulary->id(),
+      ),
+      'auto_create' => TRUE,
+    );
+    // Add the term field.
+    FieldStorageConfig::create(array(
+      'field_name' => 'field_term',
+      'type' => 'entity_reference',
+      'entity_type' => 'node',
+      'cardinality' => 1,
+      'settings' => array(
+        'target_type' => 'taxonomy_term',
+      ),
+    ))->save();
+    FieldConfig::create(array(
+      'field_name' => 'field_term',
+      'entity_type' => 'node',
+      'bundle' => 'page',
+      'label' => 'Terms',
+      'settings' => array(
+        'handler' => 'default',
+        'handler_settings' => $handler_settings,
+      ),
+    ))->save();
+
+    // Show on default display and teaser.
+    entity_get_display('node', 'page', 'default')
+      ->setComponent('field_term', array(
+        'type' => 'entity_reference_label',
+      ))
+      ->save();
+    // Boot twig environment.
+    $this->twig = \Drupal::service('twig');
+  }
+
+  /**
+   * Tests white-listing of methods doesn't interfere with chaining.
+   */
+  public function testWhiteListChaining() {
+    $node = Node::create([
+      'type' => 'page',
+      'title' => 'Some node mmk',
+      'status' => 1,
+      'field_term' => $this->term->id(),
+    ]);
+    $node->save();
+    $this->setRawContent(twig_render_template(drupal_get_path('theme', 'test_theme') . '/templates/node.html.twig', ['node' => $node]));
+    $this->assertText('Sometimes people are just jerks');
+  }
+
+}
diff --git a/core/modules/system/tests/themes/test_theme/templates/node.html.twig b/core/modules/system/tests/themes/test_theme/templates/node.html.twig
new file mode 100644
index 0000000..be94984
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/node.html.twig
@@ -0,0 +1,13 @@
+{#
+/**
+ * @file
+ * Minimal template to ensure chained property access works with white-listing.
+ */
+#}
+<article{{ attributes }}>
+
+  <div>
+    {{ node.field_term.entity.label }}
+  </div>
+
+</article>
diff --git a/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php b/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php
new file mode 100644
index 0000000..1ce2a44
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Template\TwigSandboxTest.
+ */
+
+namespace Drupal\Tests\Core\Template;
+
+use Drupal\Core\Template\TwigSandboxPolicy;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the twig sandbox policy.
+ *
+ * @group Template
+ *
+ * @coversDefaultClass \Drupal\Core\Template\TwigSandboxPolicy
+ */
+class TwigSandboxTest extends UnitTestCase {
+
+  /**
+   * The Twig environment loaded with the sandbox extension.
+   *
+   * @var \Twig_Environment
+   */
+  protected $twig;
+
+  protected function setUp() {
+    parent::setUp();
+
+    $loader = new \Twig_Loader_String();
+    $this->twig = new \Twig_Environment($loader);
+    $policy = new TwigSandboxPolicy();
+    $sandbox = new \Twig_Extension_Sandbox($policy, TRUE);
+    $this->twig->addExtension($sandbox);
+  }
+
+  /**
+   * Tests that dangerous methods cannot be called in entity objects.
+   *
+   * @dataProvider getTwigEntityDangerousMethods
+   * @expectedException \Twig_Sandbox_SecurityError
+   */
+  public function testEntityDangerousMethods($template) {
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $this->twig->render($template, ['entity' => $entity]);
+  }
+
+  /**
+   * Data provider for ::testEntityDangerousMethods.
+   *
+   * @return array
+   */
+  public function getTwigEntityDangerousMethods() {
+    return [
+      ['{{ entity.delete }}'],
+      ['{{ entity.save }}'],
+      ['{{ entity.create }}'],
+    ];
+  }
+
+  /**
+   * Tests that valid methods can be called from within Twig templates.
+   */
+  public function testEntitySafeMethods() {
+    // Test availability of get*, has*, and is* methods.
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('hasLinkTemplate')
+      ->with('test')
+      ->willReturn(TRUE);
+    $result = $this->twig->render('{{ entity.hasLinkTemplate("test") }}', ['entity' => $entity]);
+    $this->assertTrue((bool)$result, 'Sandbox policy allows has* functions to be called.');
+
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('isNew')
+      ->willReturn(TRUE);
+    $result = $this->twig->render('{{ entity.isNew }}', ['entity' => $entity]);
+    $this->assertTrue((bool)$result, 'Sandbox policy allows is* functions to be called.');
+
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('getEntityType')
+      ->willReturn('test');
+    $result = $this->twig->render('{{ entity.getEntityType }}', ['entity' => $entity]);
+    $this->assertEquals($result, 'test', 'Sandbox policy allows get* functions to be called.');
+
+    // Test availability of whitelisted functions: id, label, bundle, and get.
+    $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $entity->expects($this->atLeastOnce())
+      ->method('get')
+      ->with('title')
+      ->willReturn('test');
+    $result = $this->twig->render('{{ entity.get("title") }}', ['entity' => $entity]);
+    $this->assertEquals($result, 'test', 'Sandbox policy allows get() to be called.');
+
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('id')
+      ->willReturn('1234');
+    $result = $this->twig->render('{{ entity.id }}', ['entity' => $entity]);
+    $this->assertEquals($result, '1234', 'Sandbox policy allows get() to be called.');
+
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('label')
+      ->willReturn('testing');
+    $result = $this->twig->render('{{ entity.label }}', ['entity' => $entity]);
+    $this->assertEquals($result, 'testing', 'Sandbox policy allows get() to be called.');
+
+    $entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('bundle')
+      ->willReturn('testing');
+    $result = $this->twig->render('{{ entity.bundle }}', ['entity' => $entity]);
+    $this->assertEquals($result, 'testing', 'Sandbox policy allows get() to be called.');
+  }
+
+}
diff --git a/core/themes/bartik/templates/node.html.twig b/core/themes/bartik/templates/node.html.twig
index 7231922..6caf9f0 100644
--- a/core/themes/bartik/templates/node.html.twig
+++ b/core/themes/bartik/templates/node.html.twig
@@ -4,12 +4,10 @@
  * Bartik's theme implementation to display a node.
  *
  * Available variables:
- * - node: Full node entity.
- *   - id: The node ID.
- *   - bundle: The type of the node, for example, "page" or "article".
- *   - authorid: The user ID of the node author.
- *   - createdtime: Time the node was published formatted in Unix timestamp.
- *   - changedtime: Time the node was changed formatted in Unix timestamp.
+ * - node: The node entity with limited access to object properties and methods.
+     Only "getter" methods (method names starting with "get", "has", or "is")
+     and a few common attributes such as "id" and "label" are available. Calling
+     other methods (such as node.delete) will result in an exception.
  * - label: The title of the node.
  * - content: All node items. Use {{ content }} to print them all,
  *   or print a subset such as {{ content.field_example }}. Use
diff --git a/core/themes/classy/templates/content/node.html.twig b/core/themes/classy/templates/content/node.html.twig
index 6afe7b5..7f976e6 100644
--- a/core/themes/classy/templates/content/node.html.twig
+++ b/core/themes/classy/templates/content/node.html.twig
@@ -4,12 +4,10 @@
  * Theme override to display a node.
  *
  * Available variables:
- * - node: Full node entity.
- *   - id: The node ID.
- *   - bundle: The type of the node, for example, "page" or "article".
- *   - authorid: The user ID of the node author.
- *   - createdtime: Time the node was published formatted in Unix timestamp.
- *   - changedtime: Time the node was changed formatted in Unix timestamp.
+ * - node: The node entity with limited access to object properties and methods.
+     Only "getter" methods (method names starting with "get", "has", or "is")
+     and a few common attributes such as "id" and "label" are available. Calling
+     other methods (such as node.delete) will result in an exception.
  * - label: The title of the node.
  * - content: All node items. Use {{ content }} to print them all,
  *   or print a subset such as {{ content.field_example }}. Use
