diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php
index 06e1c1e..c964e56 100644
--- a/core/lib/Drupal/Core/Updater/Module.php
+++ b/core/lib/Drupal/Core/Updater/Module.php
@@ -51,10 +51,13 @@ public function isInstalled() {
    * Implements Drupal\Core\Updater\UpdaterInterface::canUpdateDirectory().
    */
   public static function canUpdateDirectory($directory) {
-    if (file_scan_directory($directory, '/.*\.module$/')) {
-      return TRUE;
+    $info_file = static::findInfoFile($directory);
+    $info = \Drupal::service('info_parser')->parse($info_file);
+    if (empty($info)) {
+      throw new UpdaterException(t('Unable to parse info file: %info_file.', ['%info_file' => $info_file]));
     }
-    return FALSE;
+
+    return (isset($info['type']) && $info['type'] == 'module');
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/Theme.php b/core/lib/Drupal/Core/Updater/Theme.php
index ea853c8..f1dea00 100644
--- a/core/lib/Drupal/Core/Updater/Theme.php
+++ b/core/lib/Drupal/Core/Updater/Theme.php
@@ -51,11 +51,13 @@ public function isInstalled() {
    * Implements Drupal\Core\Updater\UpdaterInterface::canUpdateDirectory().
    */
   static function canUpdateDirectory($directory) {
-    // This is a lousy test, but don't know how else to confirm it is a theme.
-    if (file_scan_directory($directory, '/.*\.module$/')) {
-      return FALSE;
+    $info_file = static::findInfoFile($directory);
+    $info = \Drupal::service('info_parser')->parse($info_file);
+    if (empty($info)) {
+      throw new UpdaterException(t('Unable to parse info file: %info_file.', ['%info_file' => $info_file]));
     }
-    return TRUE;
+
+    return (isset($info['type']) && $info['type'] == 'theme');
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/UpdaterInterface.php b/core/lib/Drupal/Core/Updater/UpdaterInterface.php
index 50a1409..66e2302 100644
--- a/core/lib/Drupal/Core/Updater/UpdaterInterface.php
+++ b/core/lib/Drupal/Core/Updater/UpdaterInterface.php
@@ -45,8 +45,6 @@ public function getInstallDirectory();
   /**
    * Determines if the Updater can handle the project provided in $directory.
    *
-   * @todo Provide something more rational here, like a project spec file.
-   *
    * @param string $directory
    *
    * @return bool
diff --git a/core/modules/update/src/Tests/UpdateUploadTest.php b/core/modules/update/src/Tests/UpdateUploadTest.php
index 0565ff0..61a8480 100644
--- a/core/modules/update/src/Tests/UpdateUploadTest.php
+++ b/core/modules/update/src/Tests/UpdateUploadTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\update\Tests;
 
+use Drupal\Core\Updater\Updater;
 use Drupal\Core\Url;
 
 /**
@@ -111,4 +112,18 @@ function testUpdateManagerCoreSecurityUpdateMessages() {
     $this->drupalGet('admin/update/ready');
     $this->assertNoText(t('There is a security update available for your version of Drupal.'));
   }
+
+  /**
+   * Tests that modules with only module info.yml files are detected without
+   * a module file.
+   * @covers Module::canUpdateDirectory().
+   */
+  function testUpdateDirectory() {
+    $type = Updater::getUpdaterFromDirectory(\Drupal::root() . '/core/modules/update/tests/modules/aaa_update_test');
+    $this->assertEqual($type, 'Drupal\\Core\\Updater\\Module', 'Detected a Module');
+
+    $type = Updater::getUpdaterFromDirectory(\Drupal::root() . '/core/modules/update/tests/themes/update_test_basetheme');
+    $this->assertEqual($type, 'Drupal\\Core\\Updater\\Theme', 'Detected a Theme.');
+  }
+
 }
