diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php
index 90691e6a40..5e86d792dd 100644
--- a/src/wp-admin/includes/ajax-actions.php
+++ b/src/wp-admin/includes/ajax-actions.php
@@ -3653,28 +3653,29 @@ function wp_ajax_update_plugin() {
 		) );
 	}
 
-	$plugin      = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
-	$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
 
 	$status = array(
 		'update'     => 'plugin',
-		'plugin'     => $plugin,
 		'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
-		'pluginName' => $plugin_data['Name'],
 		'oldVersion' => '',
 		'newVersion' => '',
 	);
 
+	if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
+		$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
+		wp_send_json_error( $status );
+	}
+
+	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+	$status['plugin']     = $plugin;
+	$status['pluginName'] = $plugin_data['Name'];
+
 	if ( $plugin_data['Version'] ) {
 		/* translators: %s: Plugin version */
 		$status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
 	}
 
-	if ( ! current_user_can( 'update_plugins' ) ) {
-		$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
-		wp_send_json_error( $status );
-	}
-
 	include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
 
 	wp_update_plugins();
@@ -3748,24 +3749,29 @@ function wp_ajax_delete_plugin() {
 	check_ajax_referer( 'updates' );
 
 	if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
-		wp_send_json_error( array( 'errorCode' => 'no_plugin_specified' ) );
+		wp_send_json_error( array(
+			'slug'         => '',
+			'errorCode'    => 'no_plugin_specified',
+			'errorMessage' => __( 'No plugin specified.' ),
+		) );
 	}
 
-	$plugin      = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
-	$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+	$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
 
 	$status = array(
-		'delete'     => 'plugin',
-		'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
-		'plugin'     => $plugin,
-		'pluginName' => $plugin_data['Name'],
+		'delete' => 'plugin',
+		'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
 	);
 
-	if ( ! current_user_can( 'delete_plugins' ) ) {
+	if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
 		$status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
 		wp_send_json_error( $status );
 	}
 
+	$plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
+	$status['plugin']     = $plugin;
+	$status['pluginName'] = $plugin_data['Name'];
+
 	if ( is_plugin_active( $plugin ) ) {
 		$status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
 		wp_send_json_error( $status );
diff --git a/src/wp-admin/js/updates.js b/src/wp-admin/js/updates.js
index accf0382f7..b58a2267e0 100644
--- a/src/wp-admin/js/updates.js
+++ b/src/wp-admin/js/updates.js
@@ -447,7 +447,11 @@
 		errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
 
 		if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
-			$message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
+			if ( response.plugin ) {
+				$message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
+			} else {
+				$message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
+			}
 			$message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
 		} else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
 			$card = $( '.plugin-card-' + response.slug )
@@ -458,9 +462,13 @@
 				} ) );
 
 			$card.find( '.update-now' )
-				.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) )
 				.text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
 
+			if ( response.pluginName ) {
+				$card.find( '.update-now' )
+					.attr( 'aria-label', wp.updates.l10n.updateFailedLabel.replace( '%s', response.pluginName ) );
+			}
+
 			$card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
 
 				// Use same delay as the total duration of the notice fadeTo + slideUp animation.
@@ -814,14 +822,21 @@
 	 * @param {string}  response.errorMessage The error that occurred.
 	 */
 	wp.updates.deletePluginError = function( response ) {
-		var $plugin          = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' ),
+		var $plugin, $pluginUpdateRow,
 			pluginUpdateRow  = wp.template( 'item-update-row' ),
-			$pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' ),
 			noticeContent    = wp.updates.adminNotice( {
 				className: 'update-message notice-error notice-alt',
 				message:   response.errorMessage
 			} );
 
+		if ( response.plugin ) {
+			$plugin          = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
+			$pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
+		} else {
+			$plugin          = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
+			$pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
+		}
+
 		if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
 			return;
 		}
@@ -835,7 +850,7 @@
 			$plugin.addClass( 'update' ).after(
 				pluginUpdateRow( {
 					slug:    response.slug,
-					plugin:  response.plugin,
+					plugin:  response.plugin || response.slug,
 					colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
 					content: noticeContent
 				} )
diff --git a/tests/phpunit/includes/testcase-ajax.php b/tests/phpunit/includes/testcase-ajax.php
index c4466aa3a2..787e6bf5fd 100644
--- a/tests/phpunit/includes/testcase-ajax.php
+++ b/tests/phpunit/includes/testcase-ajax.php
@@ -18,13 +18,13 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
 
 	/**
 	 * Last AJAX response.  This is set via echo -or- wp_die.
-	 * @var type
+	 * @var string
 	 */
 	protected $_last_response = '';
 
 	/**
 	 * List of ajax actions called via POST
-	 * @var type
+	 * @var array
 	 */
 	protected static $_core_actions_get = array(
 		'fetch-list', 'ajax-tag-search', 'wp-compression-test', 'imgedit-preview', 'oembed-cache',
@@ -39,7 +39,7 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
 
 	/**
 	 * List of ajax actions called via GET
-	 * @var type
+	 * @var array
 	 */
 	protected static $_core_actions_post = array(
 		'oembed_cache', 'image-editor', 'delete-comment', 'delete-tag', 'delete-link',
@@ -53,7 +53,9 @@ abstract class WP_Ajax_UnitTestCase extends WP_UnitTestCase {
 		'wp-remove-post-lock', 'dismiss-wp-pointer', 'send-attachment-to-editor', 'heartbeat', 'nopriv_heartbeat', 'get-revision-diffs',
 		'save-user-color-scheme', 'update-widget', 'query-themes', 'parse-embed', 'set-attachment-thumbnail',
 		'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'press-this-save-post',
-		'press-this-add-category', 'crop-image', 'generate-password',
+		'press-this-add-category', 'crop-image', 'generate-password', 'save-wporg-username', 'delete-plugin',
+		'search-plugins', 'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme',
+		'install-theme', 'get-post-thumbnail-html',
 	);
 
 	public static function setUpBeforeClass() {
diff --git a/tests/phpunit/tests/ajax/DeletePlugin.php b/tests/phpunit/tests/ajax/DeletePlugin.php
new file mode 100644
index 0000000000..4ab04f7c28
--- /dev/null
+++ b/tests/phpunit/tests/ajax/DeletePlugin.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Admin ajax functions to be tested
+ */
+require_once( ABSPATH . 'wp-admin/includes/ajax-actions.php' );
+
+/**
+ * Testing Ajax handler for deleting a plugin.
+ *
+ * @group ajax
+ */
+class Tests_Ajax_Delete_Plugin extends WP_Ajax_UnitTestCase {
+	/**
+	 * @expectedException WPAjaxDieStopException
+	 * @expectedExceptionMessage -1
+	 */
+	public function test_missing_nonce() {
+		$this->_handleAjax( 'delete-plugin' );
+	}
+
+	public function test_missing_plugin() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'delete-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'slug'         => '',
+				'errorCode'    => 'no_plugin_specified',
+				'errorMessage' => 'No plugin specified.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_missing_slug() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'foo/bar.php';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'delete-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'slug'         => '',
+				'errorCode'    => 'no_plugin_specified',
+				'errorMessage' => 'No plugin specified.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_missing_capability() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'foo/bar.php';
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'delete-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'delete'       => 'plugin',
+				'slug'         => 'foo',
+				'errorMessage' => 'Sorry, you are not allowed to delete plugins for this site.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_invalid_file() {
+		$this->_setRole( 'administrator' );
+
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = '../foo/bar.php';
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'delete-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'delete'       => 'plugin',
+				'slug'         => 'foo',
+				'errorMessage' => 'Sorry, you are not allowed to delete plugins for this site.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_delete_plugin() {
+		$this->_setRole( 'administrator' );
+
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'foo.php';
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'delete-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => true,
+			'data'    => array(
+				'delete'       => 'plugin',
+				'slug'         => 'foo',
+				'plugin'       => 'foo.php',
+				'pluginName'   => '',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+}
diff --git a/tests/phpunit/tests/ajax/UpdatePlugin.php b/tests/phpunit/tests/ajax/UpdatePlugin.php
new file mode 100644
index 0000000000..b75603fd36
--- /dev/null
+++ b/tests/phpunit/tests/ajax/UpdatePlugin.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Admin ajax functions to be tested
+ */
+require_once( ABSPATH . 'wp-admin/includes/ajax-actions.php' );
+
+/**
+ * Testing Ajax handler for updating a plugin.
+ *
+ * @group ajax
+ */
+class Tests_Ajax_Update_Plugin extends WP_Ajax_UnitTestCase {
+	/**
+	 * @expectedException WPAjaxDieStopException
+	 * @expectedExceptionMessage -1
+	 */
+	public function test_missing_nonce() {
+		$this->_handleAjax( 'update-plugin' );
+	}
+
+	public function test_missing_plugin() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'update-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'slug'         => '',
+				'errorCode'    => 'no_plugin_specified',
+				'errorMessage' => 'No plugin specified.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_missing_slug() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'foo/bar.php';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'update-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'slug'         => '',
+				'errorCode'    => 'no_plugin_specified',
+				'errorMessage' => 'No plugin specified.',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_missing_capability() {
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'foo/bar.php';
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'update-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'update'       => 'plugin',
+				'slug'         => 'foo',
+				'errorMessage' => 'Sorry, you are not allowed to update plugins for this site.',
+				'oldVersion'   => '',
+				'newVersion'   => '',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_invalid_file() {
+		$this->_setRole( 'administrator' );
+
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = '../foo/bar.php';
+		$_POST['slug']        = 'foo';
+
+		// Make the request
+		try {
+			$this->_handleAjax( 'update-plugin' );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'update'       => 'plugin',
+				'slug'         => 'foo',
+				'errorMessage' => 'Sorry, you are not allowed to update plugins for this site.',
+				'oldVersion'   => '',
+				'newVersion'   => '',
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+
+	public function test_update_plugin() {
+		$this->_setRole( 'administrator' );
+
+		$_POST['_ajax_nonce'] = wp_create_nonce( 'updates' );
+		$_POST['plugin']      = 'hello.php';
+		$_POST['slug']        = 'hello-dolly';
+
+		// Make the request
+		try {
+			// Prevent wp_update_plugins() from running
+			wp_installing( true );
+			$this->_handleAjax( 'update-plugin' );
+			wp_installing( false );
+		} catch ( WPAjaxDieContinueException $e ) {
+			unset( $e );
+		}
+
+		// Get the response.
+		$response = json_decode( $this->_last_response, true );
+
+		$expected = array(
+			'success' => false,
+			'data'    => array(
+				'update'       => 'plugin',
+				'slug'         => 'hello-dolly',
+				'plugin'       => 'hello.php',
+				'pluginName'   => 'Hello Dolly',
+				'errorMessage' => 'Plugin update failed.',
+				'oldVersion'   => 'Version 1.6',
+				'newVersion'   => '',
+				'debug'        => array( 'The plugin is at the latest version.' ),
+			),
+		);
+
+		$this->assertEqualSets( $expected, $response );
+	}
+}