WordPress Multisite Security: Capabilities, Roles, and Audit Logging

WordPress Multisite introduces a second permission tier — the Super Admin — and network-wide settings that affect every site in the network. Securing a multisite network requires thinking about cross-site data leakage, plugin/theme control, and the larger attack surface that comes from multiple WordPress installs sharing a single codebase.

Problem: WordPress Multisite introduces a second layer of capabilities — network admin, super admin, site admin — but the distinction between network-level and site-level capabilities is unclear, making it easy to grant overly broad access.

Solution: Use is_super_admin() only for true network-level operations. Grant site-specific access with add_user_to_blog() and appropriate roles. Gate network admin pages with current_user_can('manage_network'), not with role checks. Audit super admin accounts with get_super_admins() and restrict new super admin creation to existing super admins only.

The examples below restrict site creation to super admins, prevent user enumeration across sites, lock dangerous capabilities to super admin only, and audit network-wide plugin activations.

add( 'blog_title', __( 'You must be logged in to create a site.', 'myplugin' ) );
    }
    return $result;
} );

// Completely disable front-end site creation (manage via wp-admin/network only)
// In wp-config.php: define( 'NOBLOGREDIRECT', 'https://example.com' );
// In Network Settings admin: set "Allow new registrations" to "No registrations"

// ── PREVENT USER ENUMERATION ACROSS SITES ──
// In standard WordPress, user logins are the same across all network sites.
// Disable the REST API users endpoint at network level:
add_filter( 'rest_endpoints', function( array $endpoints ): array {
    if ( ! current_user_can( 'list_users' ) ) {
        unset( $endpoints['/wp/v2/users'] );
        unset( $endpoints['/wp/v2/users/(?P[\d]+)'] );
    }
    return $endpoints;
} );

// Disable author archive URL user enumeration: /?author=1
add_action( 'template_redirect', function() {
    if ( is_author() && ! is_user_logged_in() ) {
        wp_safe_redirect( home_url( '/' ), 301 );
        exit;
    }
    // Also block /?author=1 enumeration
    if ( isset( $_GET['author'] ) && ! is_user_logged_in() ) {
        wp_safe_redirect( home_url( '/' ), 301 );
        exit;
    }
} );

// ── LOCK SUPER-ADMIN-ONLY CAPS ──
// Prevent site admins from doing things only super admins should do:
add_filter( 'map_meta_cap', function( array $caps, string $cap, int $user_id ): array {
    $super_admin_only = [
        'install_plugins',
        'update_plugins',
        'delete_plugins',
        'install_themes',
        'update_themes',
        'update_core',
    ];
    if ( in_array( $cap, $super_admin_only, true ) && ! is_super_admin( $user_id ) ) {
        $caps[] = 'do_not_allow';
    }
    return $caps;
}, 10, 3 );

Audit network-wide plugin activations and enforce security policies:

user_login ?? 'unknown', $user_id
    ) );
}, 10, 2 );

// Log when a plugin is network-activated
add_action( 'activated_plugin', function( string $plugin, bool $network_wide ) {
    if ( $network_wide ) {
        error_log( sprintf(
            '[NETWORK AUDIT] Plugin network-activated: %s by user_id=%d',
            $plugin, get_current_user_id()
        ) );
    }
}, 10, 2 );

// ── SUNRISE.PHP FOR DOMAIN MAPPING SECURITY ──
// When using domain mapping, verify the mapped domain belongs to the expected blog
// In wp-config.php: define( 'SUNRISE', 'on' );
// In wp-content/sunrise.php: validate $HTTP_HOST against wp_blogs table to prevent
// domain confusion attacks where a crafted Host header accesses the wrong site.

NOTE: The most important Multisite security practice is keeping the Super Admin account count minimal — treat it like root on a Linux system. Use separate browser profiles for super admin work, enable two-factor authentication on all super admin accounts, and audit the wp_sitemeta option site_admins regularly with wp site meta get 1 site_admins.

Leave Comment

Your email address will not be published. Required fields are marked *