# Multisite Security

> >

- **Type:** Skill
- **Install:** `agentstack add skill-wpultimatesecurity-wordpress-security-skills-multisite-security`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [wpultimatesecurity](https://agentstack.voostack.com/s/wpultimatesecurity)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [wpultimatesecurity](https://github.com/wpultimatesecurity)
- **Source:** https://github.com/wpultimatesecurity/WordPress-Security-Skills/tree/dev/skills/multisite-security

## Install

```sh
agentstack add skill-wpultimatesecurity-wordpress-security-skills-multisite-security
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Multisite security

## When to use this skill

Use this skill whenever code runs on a WordPress multisite network:

- Using `switch_to_blog()` to access another site's data.
- Adding network admin pages (`network_admin_menu`).
- Enumerating sites with `get_sites()`.
- Reading a `blog_id` from a request parameter or URL.
- Performing actions that affect the whole network vs. a single site.

On multisite, site administrator and network administrator are different roles.
Network-level actions require `manage_network` or `manage_network_options`, and
`switch_to_blog()` must always be paired with `restore_current_blog()`.

Related: see the `capability-permission-checks` skill for single-site capability
patterns.

## Core principles (and why they matter)

1. **Site admin != network admin.** A site administrator can do almost anything on their
   own site, but they must not manage network settings or access other sites' data.
   Network actions need `manage_network` / `manage_network_options`.
2. **Always pair `switch_to_blog()` with `restore_current_blog()`.** Forgetting to restore
   leaves the rest of the request running in the wrong site context, leaking or corrupting
   data.
3. **Re-check capabilities after switching.** Capabilities are evaluated in the current
   site context. After `switch_to_blog()`, the capability context changes; re-verify
   `current_user_can()` or `user_can()` if you act on the new site.
4. **Never trust a `blog_id` from input.** Validate it against allowed sites (e.g., sites
   the current user belongs to, or the network allowlist) before switching.
5. **Keep per-site data isolated.** Do not store site A's data in site B's options or
   tables unless the user explicitly authorized cross-site access.
6. **`is_super_admin()` is for network-level checks only.** Prefer `current_user_can( 'manage_network' )`
   for capabilities.

## Step-by-step implementation

1. Check `is_multisite()` before running multisite-specific code.
2. For network admin pages, gate with `current_user_can( 'manage_network_options' )`.
3. For site actions that read a `blog_id` from input:
   - Cast to integer.
   - Confirm the site exists (`get_site()`).
   - Confirm the current user may act on it (`user_can( $user_id, 'manage_sites' )` or
     membership checks).
4. Wrap the operation in `switch_to_blog()` / `restore_current_blog()`.
5. After switching, re-check capabilities if the action is privileged.
6. Return errors rather than silently failing or defaulting to the current site.

## Common AI mistakes / anti-patterns

### Mistake 1 — `switch_to_blog()` with an unvalidated blog id

```php
// ❌ Insecure: any blog id can be requested, including sites the user does not own.
$blog_id = (int) $_GET['blog_id'];
switch_to_blog( $blog_id );
$posts = get_posts();
restore_current_blog();
```

```php
// ✅ Secure: validate the blog id and the user's relationship to it.
$blog_id = isset( $_GET['blog_id'] ) ? absint( $_GET['blog_id'] ) : 0;
$site    = get_site( $blog_id );
if ( ! $site || ! is_user_member_of_blog( get_current_user_id(), $blog_id ) ) {
    wp_die( esc_html__( 'Invalid site.', 'my-plugin' ), 403 );
}

switch_to_blog( $blog_id );
$posts = get_posts();
restore_current_blog();
```

### Mistake 2 — Forgetting `restore_current_blog()`

```php
// ❌ Risky: subsequent code runs in the switched context.
foreach ( $blog_ids as $blog_id ) {
    switch_to_blog( $blog_id );
    do_something();
}
```

```php
// ✅ Secure: restore after each switch.
foreach ( $blog_ids as $blog_id ) {
    switch_to_blog( $blog_id );
    do_something();
    restore_current_blog();
}
```

### Mistake 3 — Using `manage_options` for a network action

```php
// ❌ Insecure: site admins have manage_options but cannot manage the network.
if ( current_user_can( 'manage_options' ) ) {
    update_site_option( 'my_plugin_network_key', $value );
}
```

```php
// ✅ Secure: network actions require manage_network / manage_network_options.
if ( ! current_user_can( 'manage_network_options' ) ) {
    wp_die( esc_html__( 'You do not have permission to manage network options.', 'my-plugin' ), 403 );
}
update_site_option( 'my_plugin_network_key', $value );
```

### Mistake 4 — Leaking data after a switch

```php
// ❌ Insecure: data fetched under site B is echoed on site A without re-checking.
switch_to_blog( $blog_id );
$title = get_bloginfo( 'name' );
restore_current_blog();
echo $title;
```

```php
// ✅ Secure: validate the user may read the target site, then escape output.
if ( ! is_user_member_of_blog( get_current_user_id(), $blog_id ) ) {
    return;
}
switch_to_blog( $blog_id );
$title = get_bloginfo( 'name' );
restore_current_blog();
echo esc_html( $title );
```

### Mistake 5 — Assuming `is_super_admin()` covers every case

```php
// ❌ Less precise: is_super_admin checks the super-admin list, not a capability.
if ( is_super_admin() ) {
    // do network thing
}
```

```php
// ✅ Secure: prefer the explicit network capability.
if ( current_user_can( 'manage_network' ) ) {
    // do network thing
}
```

## Correct code examples

A complete network-safe routine that validates a blog id, switches context, and restores
it is in [`references/secure-multisite-operations.php`](references/secure-multisite-operations.php).

## Checklist

- [ ] Multisite-specific code checks `is_multisite()` first.
- [ ] Network admin actions use `manage_network` / `manage_network_options`.
- [ ] `blog_id` values from input are cast to integers and validated.
- [ ] The user is confirmed to be a member of / allowed on the target site before switching.
- [ ] Every `switch_to_blog()` has a matching `restore_current_blog()`.
- [ ] Capabilities are re-checked after switching sites if the action is privileged.
- [ ] Per-site data is not leaked into another site's context.
- [ ] `is_super_admin()` is used only where the super-admin list is the correct gate.

## Official references

- [`is_multisite()`](https://developer.wordpress.org/reference/functions/is_multisite/)
- [`is_super_admin()`](https://developer.wordpress.org/reference/functions/is_super_admin/)
- [`current_user_can()`](https://developer.wordpress.org/reference/functions/current_user_can/)
- [`user_can()`](https://developer.wordpress.org/reference/functions/user_can/)
- [`switch_to_blog()`](https://developer.wordpress.org/reference/functions/switch_to_blog/)
- [`restore_current_blog()`](https://developer.wordpress.org/reference/functions/restore_current_blog/)
- [`get_sites()`](https://developer.wordpress.org/reference/functions/get_sites/)
- [`get_current_blog_id()`](https://developer.wordpress.org/reference/functions/get_current_blog_id/)
- [`network_admin_menu` hook](https://developer.wordpress.org/reference/hooks/network_admin_menu/)
- [`is_user_member_of_blog()`](https://developer.wordpress.org/reference/functions/is_user_member_of_blog/)
- [Multisite Network Administration](https://developer.wordpress.org/advanced-administration/multisite/)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [wpultimatesecurity](https://github.com/wpultimatesecurity)
- **Source:** [wpultimatesecurity/WordPress-Security-Skills](https://github.com/wpultimatesecurity/WordPress-Security-Skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-wpultimatesecurity-wordpress-security-skills-multisite-security
- Seller: https://agentstack.voostack.com/s/wpultimatesecurity
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
