Skip to content

Instantly share code, notes, and snippets.

@dknauss
Last active September 19, 2025 04:25
Show Gist options
  • Select an option

  • Save dknauss/45d2185376aaca40fac5f92f3e3bbc85 to your computer and use it in GitHub Desktop.

Select an option

Save dknauss/45d2185376aaca40fac5f92f3e3bbc85 to your computer and use it in GitHub Desktop.
"Psudo" is a "pseudo sudo" mode for WordPress administrators: it requires reauthentication to perform sensitive admin tasks for a short window of time. (This overgrown gist is due to become a real plugin.)
<?php
/**
* == Psudo ==
*
* Plugin Name: Psudo
* Version: 1.0
* Author: Dan Knauss
* Contributors:
* Donate link: https://example.com/
* Tags: security, user management
* Requires at least:
* Tested up to:
* Stable tag:
* Requires PHP:
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: psudo
* Description: Psudo requires Administrators to log in again for temporary unrestricted access to Users, Tools, Settings, Themes, and Plugins.
*
* == Description ==
*
* Psudo requires Administrators to log into their accounts again after being forcibly logged out when they try to access Users, Tools, Settings, Themes, and Plugins — unless they've
* already reauthenticated within a period of time (1-30 minutes) defined in the plugin settings. (The default is 10 minutes.) By this method, Psudo effectively creates a "pseudo sudo"
* (Superuser Do) or "Psudo" session mode for Administrators.
*
* Psudo can help you harden the security of your WordPress site against the threat of compromised user accounts and make your Admins think twice about their privileges and workflows.
*
* == What problem does this plugin solve? ==
*
* Psudo acts as a last line of defense in scenarios where an Administrator account or privileges have been accessed by an attacker with a rogue, stolen, or hijacked account.
*
* For example:
*
* - An admin user has remained logged in on a public or stolen device, allowing an attacker to continue using their active session on that device.
* - An admin user logs into their account on a compromised device or network that allows attackers to harvest and use their active session.
* - An admin user falls victim to a Session Fixation or other Session Hijacking attack.
* - An attacker possesses a non-administrator account and exploits a vulnerability (e.g., broken access controls) in third-party code (a theme or plugin) that enables privilege escalation.
*
* Conceptually, Psudo is a highly granular application of the Principle of the Least Privilege (PotLP). Administrators generally do not require constant direct access to operate the theme/plugin
* installer/updater, or the core application settings for WordPress. When an admin user needs to use these capabilities, a quick re-authentication ensures they are who they say they are. This
* brief "window of trust" must constantly be re-opened even if the absolute session duration remains 2 to 14 days, which are the system defaults. This is a good security discipline for admin
* users. Psudo can help normalize user expectations about security for WordPress that resembles familiar UX patterns for reauthentication in the most widely used operating systems.
*
* Please take note, Psudo is not a robust, comprehensive, standalone security solution. Most WordPress sites that are breached are compromised by attackers through vulnerabilities in obsolete
* third-party plugins and other code that has broken access controls, is susceptible to abuse through unfiltered query strings, missing nonces, and/or insufficiently secured Admin-AJAX
* calls, XML-RPC and REST API endpoints. Psudo does not attempt to comprehensively address these common attack vectors. It's always imperative for security that you keep all your installed code
* updated within a reasonably hardened server and application environment.
*
* To defend these common attack vectors, take care to ensure the quality of any custom or third-party code you install with WordPress. Always make timely updates to WordPress core, plugins,
* themes, and extensions! You should also use Two-Factor/Multifactor Authentication (2FA/MFA), absolute session limits, limits on API connections, allowlists/device or IP restrictions for
* administrators, dashboard restriction for all users who don't need it, and activity logging and alerts.
*
* Recommended plugins for these purposes:
*
* - [Two-Factor](https://en-ca.wordpress.org/plugins/two-factor/)
* - [WebAuthn Provider for Two Factor](https://en-ca.wordpress.org/plugins/two-factor-provider-webauthn/)
* - [Remember Me Controls](https://en-ca.wordpress.org/plugins/remember-me-controls/)
* - [Remove Dashboard Access](https://en-ca.wordpress.org/plugins/remove-dashboard-access-for-non-admins/)
* - [Restricted Site Access](https://en-ca.wordpress.org/plugins/restricted-site-access/)
* - [WP fail2ban](https://wordpress.org/plugins/wp-fail2ban/)
* - [Disable REST API](https://wordpress.org/plugins/disable-json-api/)
* - [Disable XML-RPC-API](https://wordpress.org/plugins/disable-xml-rpc-api/)
* - [Stream](https://wordpress.org/plugins/stream/)
*
* You should also consider restricting the number of true Administrators to one on most sites — an account rarely used, as in "break-glass" emergency scenarios only. A custom role between Editor
* and Administrator who cannot `manage_options` may be adequate. Then, you could load all roles and privileges from a static file (possibly an `mu-plugin`) and lock or eliminate `wp_user_roles`
* in the `wp_options` table. — or monitor it for changes as a "canary" to detect an intrusion attempt.
*
* Psudo will be most effective (offering greater security benefits) if it's run as an mu-plugin (Must-Use Plugin) at the very beginning of the initialization seuquence. Consider using Felix
* Arntz's WP Plugin MU Loader](https://gist.github.com/felixarntz/daff4006112b60dfea677ca08fc0b31c).
*
* == Changelog ==
*
* = 1.0 =
* Initial release.
*
* == Upgrade Notice ==
* = 1.0 =
* Initial release.
*
**/
add_action( 'init', 'psudo_conditions' ); // Should run as early as possible or as an mu-plugin.
/**
* Defines and checks for conditions that will trigger a forced logout for the user if those conditions are met.
*
* This function checks if the user is logged in and has the 'manage_options' capability. If the user meets these criteria, it checks the user's last
* last forced logout time, which is a timestamp logged by Psudo in the user's metadata. If the current time has passed the Psudo session expiration —
* i.e., the limit specified in this function: 10 minutes after the last forced logout — then the user will be forcibly logged out if they access a
* restricted URL. (Restricted URLs are also specified in this function.) Then the user will be presented with the login form and a re-authentication
* request. Following re-authentication, the user will be redirected to the last (restricted) page they tried to access.
*
* Note that a user who is forcibly logged out and then delays logging back in may have little or no time to exercise their administrator privileges
* before they are forced to log in again. The last forced logout time starts the Psudo session clock and must be close in time to the next login for
* that new session to be viable for more than a few minutes of privileged admin actions.
*
* @since 1.0.0
* @return void
*/
function psudo_conditions() { // Force Logout Conditions function implementation.
if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) { // If the user is logged in and has admin capabilities, let's take a closer look.
return; // (Ignore everyone else.)
}
$user_id = get_current_user_id(); // Get the current user's ID.
$last_forced_logout = get_cached_user_meta($user_id, 'last_forced_logout'); // Get the current user's last forced logout time.
if ( ! $last_forced_logout ) { // If the user's last forced logout time doesn't exist, is NULL, or zero,
psudo_force_logout_and_redirect( $user_id ); // then force a logout.
}
$psudo_session_limit = get_option('psudo_session_limit', 10); // Get the Psudo session limit (default = 10) from settings saved in option table.
$psudo_limit_since_last_forced_logout = $last_forced_logout + $psudo_session_limit * MINUTE_IN_SECONDS;
// Define restricted URLs to trigger Psudo condition check.
$restricted_urls = array( 'plugins.php', 'themes.php', 'options-general.php', 'users.php', 'tools.php' );
$current_url = filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL ); // Clean and safe way to get current URL from PHP environment.
foreach ( $restricted_urls as $restricted_url ) { // Check for conditions where we should force a logout:
if ( strpos( $current_url, $restricted_url ) !== false && // - Has the user has requested a URL that is restricted?
time() > $psudo_limit_since_last_forced_logout ) { // - Has the current time passed the limit based on the user's last forced logout?
psudo_force_logout_and_redirect( $user_id, $current_url ); // If both conditions are true, force a logout.
}
}
}
/**
* Force a logout after logging the current time as the last_forced_logout time in user_meta. Display an explanatory message on the login screen. \
* When the user logs back in, redirect them to their last requested URL.
*
*
* @since 1.0.0
* @param int $user_id The ID of the user to force logout.
* @param string $current_url The URL to redirect to after logout.
* @return void
*/
function psudo_force_logout_and_redirect( $user_id, $current_url ) { // Force Logout and Redirect function implementation.
$new_value = time(); // Get current time as variable to pass to cache.
update_cached_user_meta($user_id, 'last_forced_logout', $new_value); // Update the cached last forced logout time with the current time.
wp_logout(); // Force logout.
wp_redirect( wp_login_url() . '?' . build_query(array( // Build the redirect so the user will return to their last URL after logging in.
'redirect_to' => $current_url, // Make the redirect_to URL the last URL the user requested before they were logged out.
'reauth_now' => 'true' // Set the reauth_now URL parameter as a flag for customizing the reauth login screen.
)) );
exit; // Terminate PHP script execution.
}
add_filter( 'login_message', 'psudo_reauth_message' ); // Re-authentication Message login form filter implementation.
/**
* Display a custom message above the login form when the proper key ('reauth') and value ('true') pair are included in the URL parameters.
* This happens when the user is forcibly logged out by Psudo in the previous function. It's intended to provide some information to
* the user as to why they've been logged out and what should happen next.
*
* Note we can't use get_query_var or WP global variables to get the current URL following a redirect+exit, so we must use PHP for this, just not superglobals.
*
* @since 1.0.0
* @param string $message The existing login message.
* @return string The modified login message.
*/
function psudo_reauth_message( $message ) { // Session Ended Message function implementation.
if ( filter_input(INPUT_GET, 'reauth_now', FILTER_SANITIZE_STRING) === 'true' ) { // If the "reauth" parameter exists in the current URL and is "true" then return the following message.
$message .= '<p class="message"><strong>' . esc_html__( 'With great power comes great responsibility.', 'psudo' ) .
'</strong><br/>' . esc_html__( 'Reauthenticate to continue where you were. 🥪', 'psudo'). '</p>';
}
return $message;
}
/**
* Retrieve cached user metadata.
*
* @param int $user_id The user ID.
* @param string $meta_key The metadata key.
* @param bool $force_refresh Whether to force refresh the cache or not.
* @return mixed The user metadata or false on failure.
*/
function get_cached_user_meta($user_id, $meta_key, $force_refresh = false) {
// Generate a unique key for the transient.
$transient_key = 'user_meta_' . $user_id . '_' . $meta_key;
// Try to get the value from the transient cache.
if (!$force_refresh) {
$cached_value = get_transient($transient_key);
if ($cached_value !== false) {
return $cached_value;
}
}
// If not found in cache, get the value from the database.
$meta_value = get_user_meta($user_id, $meta_key, true);
// Store the value in the transient cache for future requests.
set_transient($transient_key, $meta_value, 12 * HOUR_IN_SECONDS); // Cache for 12 hours.
return $meta_value;
}
/**
* Update user metadata and refresh cache.
*
* @param int $user_id The user ID.
* @param string $meta_key The metadata key.
* @param mixed $meta_value The new metadata value.
* @return bool True on successful update, false on failure.
*/
function update_cached_user_meta($user_id, $meta_key, $meta_value) {
// Update the metadata in the database.
$result = update_user_meta($user_id, $meta_key, $meta_value);
if ($result) {
// Generate a unique key for the transient.
$transient_key = 'user_meta_' . $user_id . '_' . $meta_key;
// Refresh the cache with the new value.
set_transient($transient_key, $meta_value, 12 * HOUR_IN_SECONDS); // Cache for 12 hours.
}
return $result;
}
/**
* Add settings page for Psudo plugin.
*/
function psudo_add_admin_menu() {
add_options_page(
'Psudo Settings', // Page title
'Psudo', // Menu title
'manage_options', // Capability
'psudo-settings', // Menu slug
'psudo_settings_page' // Callback function
);
}
add_action( 'admin_menu', 'psudo_add_admin_menu' );
/**
* Register settings for Psudo plugin.
*/
function psudo_register_settings() {
register_setting( 'psudo_options', 'psudo_session_limit', array(
'type' => 'integer',
'sanitize_callback' => 'psudo_sanitize_session_limit',
'default' => 10,
) );
add_settings_section(
'psudo_settings_section', // ID
'How long should a Psudo session last?', // Title
'__return_null', // Callback
'psudo-settings' // Page
);
add_settings_field(
'psudo_session_limit', // ID
'Psudo session limit in minutes', // Title
'psudo_session_limit_callback', // Callback
'psudo-settings', // Page
'psudo_settings_section' // Section
);
}
add_action( 'admin_init', 'psudo_register_settings' );
/**
* Sanitize callback for session limit.
*
* @param mixed $input The input value.
* @return int The sanitized input value.
*/
function psudo_sanitize_session_limit( $input ) {
$input = intval( $input );
if ( $input < 1 || $input > 30 ) {
add_settings_error(
'psudo_session_limit',
'psudo_session_limit_error',
'Psudo Session Limit must be between 1 and 30 minutes.',
'error'
);
return get_option( 'psudo_session_limit', 10 ); // Return the default value if input is invalid.
}
return $input;
}
/**
* Callback function for session limit field.
*/
function psudo_session_limit_callback() {
$psudo_session_limit = get_option( 'psudo_session_limit', 10 );
echo '<input type="number" id="psudo_session_limit" name="psudo_session_limit" value="' . esc_attr( $psudo_session_limit ) . '" min="1" max="30" />';
}
/**
* Settings page callback function.
*/
function psudo_settings_page() { ?>
<div class="wrap">
<h1>Psudo Settings</h1>
<p>Psudo requires users with Administrator privileges to reauthenticate for temporary unrestricted access to Settings, Themes, and Plugins.</p>
<p>Here you can define how long the temporary unrestricted Sudo-like (superuser) session will last.</p>
<p>Choose a whole number between 1 and 30 minutes.</p>
<p>The default is 10 minutes.</p>
<form method="post" action="options.php">
<?php
settings_fields( 'psudo_options' );
do_settings_sections( 'psudo-settings' );
submit_button();
?>
</form>
</div>
<?php } ?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment