Plugin Access: Who Can Install, Update and Delete on a Site
Plugin access is code execution with a settings screen. The capabilities that grant it, how multisite changes them, and how to audit who holds them.
Tomas Lindqvist · @tlindqvist
Hardening and operations
- Topic
- SEC
- Section
- Security & Hardening
- Published
- 20 OCT 2026
- Reading time
- 8 MIN
- Words
- 1798W
The request came from a marketing contractor who needed to add a tracking snippet. The site owner gave her an administrator account because it was the fastest way to unblock her, and she installed a plugin from a site that advertised a free trial. The plugin was not malicious. It was abandoned, it had an unpatched upload flaw, and it was the door used three months later. The contractor had not done anything unusual. The account had been given the ability to install code, and installing code is what administrators do.
Plugin access is usually discussed as an interface question: who sees the Plugins menu. It is a security question. A plugin runs with the same privileges as WordPress itself: it can read the database, write files, call external services, create users and modify content. Installing one is equivalent to running a script on the server with the web server's permissions. The capability that allows it is therefore closer to server access than to editing a post.
That reframing changes who should have it. Content editors do not need it. Agencies sometimes do. Developers need it on staging and often not on production. The default administrator role grants it to everyone, which is why so many sites have more people with code execution than they realize.
Disclosure: some links on this page are affiliate links, and WPSifero may earn a commission if you buy through them. The audit below uses WP-CLI and costs nothing; the products at the end are for teams that need central control or a host that enforces the policy.
Installing a plugin is running code on the server
A plugin can do anything the site can do. It can read every row in the database, including password hashes and customer records. It can write to wp-content, which means it can place a file that runs later. It can open an outbound connection and send data anywhere. It can register an administrator account or change another user's password. None of that requires an exploit; it is the normal API.
That is not an argument against plugins. It is an argument for treating the install capability as a privileged operation, like SSH. The person who can install a plugin can change the behaviour of the site in ways that no content review will catch, and can do it from a browser without leaving a trace in the post revision history.
The practical consequence: the decision to grant plugin access is a decision about trust and about blast radius. It should be made explicitly, recorded, and revisited when the contractor's project ends. The default is not a decision; it is an inheritance.
Five capabilities decide plugin access
WordPress splits plugin access across five capabilities. Most discussions name one or two, which is why restrictions often leave a path open. The table below is the set that matters for a single site.
| Capability | Grants | Risk if misused |
|---|---|---|
install_plugins | Upload and install a ZIP, install from the directory | Arbitrary code execution |
activate_plugins | Activate or deactivate any installed plugin | Re-enables dormant vulnerable code |
update_plugins | Apply updates to installed plugins | Installs code from the update server |
delete_plugins | Remove plugin files | Destroys the site's functionality |
edit_plugins | Edit plugin source in the dashboard editor | Direct code modification |
All five are granted to the administrator role on a default install. Removing one in isolation is usually pointless: a user who can install can install a plugin that grants the others, and a user who can activate can re-enable a vulnerable plugin that was deliberately left inactive. The set moves together, which is why the useful unit of control is the role, not the individual checkbox.
edit_plugins is the one most often left behind. It is disabled site-wide by DISALLOW_FILE_EDIT, which is a one-line change and worth making on every production site. It does not remove the capability; it removes the editor, which is enough to close the browser-based path.
The editor is not the only path
DISALLOW_FILE_EDIT hides the plugin and theme editors. It does not stop a plugin from writing files, and it does not stop anyone with SFTP or SSH. It is one layer, not the boundary.
The administrator role is broader than most people think
The administrator role is not "the person who manages the site". It is a bundle of capabilities that includes user management, plugin installation, theme changes, options, and on a default install the ability to edit files. Giving it to a content contractor grants all of that, plus the ability to create another administrator that outlives the contract.
Editors are not harmless either. The editor role can publish, and on a site where unfiltered HTML is allowed for editors, publishing is a path to stored scripts. The unfiltered_html capability is granted to administrators and, on single-site installs, to editors; on multisite it is restricted to super administrators. That difference is worth knowing before deciding that an editor account is safe.
Custom roles are the right tool for the contractor case. A role with read, edit_posts, publish_posts and upload_files can do the content work without touching code. Create it once, assign it, and remove it when the project ends:
add_role(
'content_editor',
'Content Editor',
[
'read' => true,
'edit_posts' => true,
'publish_posts' => true,
'upload_files' => true,
]
);
Two cautions. add_role() persists in the database, so run it once from a small plugin or a one-off script rather than on every request, and remove the role when it is no longer used. And a custom role does not remove capabilities that another plugin grants to all users, so the audit has to include plugins that call add_cap().
Multisite changes the answer
On a multisite network, the plugin menu is a super administrator tool. Site administrators cannot install, update or delete plugins by default, even though they can manage their own site's content and users. The capabilities that control this are network-level: manage_network_plugins for the network plugin screen, and install_plugins / update_plugins held by super administrators.
That default is a useful model for a single site with several editors, because it separates "can change the site" from "can change the platform". The equivalent on a single install is a custom role plus DISALLOW_FILE_MODS, or a managed host that removes the plugin screen entirely for non-owner accounts.
Multisite also changes activation. Network activation makes a plugin run on every site, which is a larger blast radius than a single-site activation and a larger cleanup if the plugin is compromised. Keep the network-activated list short, and review it whenever a site is added to the network.
Restricting access without locking yourself out
The restriction has to leave a way back in. The common pattern is a break-glass administrator: one account with plugin access, used only for installs and updates, protected by two-factor authentication and not used for daily editing. A second break-glass account in a password manager is the recovery path if the first is locked out.
Then decide where the restriction lives. A capability change in WordPress is the weakest option, because it can be reverted by anyone with database access or by a plugin update that resets roles. A host-level policy is stronger: the plugin screen is removed for non-owner accounts, or file modifications are blocked entirely. A deploy pipeline is stronger still, because production code only changes through a reviewed merge.
Be honest about the boundary. If a person has SSH, SFTP or database access, capabilities do not constrain them; they can write files or change the options table directly. Capability restrictions govern the WordPress interface, not the server. That is why the audit includes hosting accounts and SSH keys, not only WordPress users.
// wp-config.php: remove the editors and block installs from the dashboard.
define( 'DISALLOW_FILE_EDIT', true );
// Only when production is deployed from version control:
// define( 'DISALLOW_FILE_MODS', true );
DISALLOW_FILE_MODS blocks installs, updates and deletions from the dashboard, including automatic updates. That is correct for a site deployed from a repository and wrong for a site updated by its owner, because it turns the update screen into a dead end. Choose it deliberately, and document how updates happen instead.
Audit it in ten minutes
The audit is four lists and a decision for each name on them. Run it quarterly, and after anyone leaves a project.
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp cap list administrator
wp role list --fields=role,name
wp user application-password list admin
wp plugin list --status=active --fields=name,version,update
The first list is who can install code. The second is what the role grants, which is useful when a plugin has modified it. The third shows custom roles that may have been created for a project and never removed. The fourth is the set of credentials that bypass the login form; revoke anything without a named owner. The fifth is the code that is running, which is the surface all of this protects.
For each administrator, ask three questions: does this person still need the account, does this person need plugin access, and is two-factor enabled. If the answer to the second is no, move the account to a content role. If the answer to the first is no, delete it and reassign the content. If the answer to the third is no, fix that before anything else on this page.
When restricting access is the wrong move
There is a real cost to restriction. A site owner who cannot install a plugin without opening a ticket stops maintaining the site, and an unmaintained site is a bigger risk than a trusted editor with install rights. A two-person agency that shares one administrator account is not improved by a role matrix; it is improved by two named accounts, two-factor authentication and a backup.
The restriction pays when the number of people with access grows, when contractors come and go, or when the site handles payments or personal data. It pays less on a small site with one owner and no contributors, where the meaningful controls are a strong password, two-factor, updates and an off-host backup. The threat model decides, not the principle.
For teams that need the control without the maintenance, a managed host can enforce it from outside WordPress: Pressable separates site access from platform access and provides staging, which is where plugin tests belong. Jetpack's central dashboard lets an agency manage plugins across client sites without handing each client an administrator account, which is the version of this problem that agencies actually have.
The free path is a custom role, DISALLOW_FILE_EDIT, two named break-glass accounts and the audit above. The decision rule is one sentence: plugin access is granted to people who are trusted to run code on the server, and to nobody else. Most sites discover, on the day they run the audit, that the list is longer than they thought.
Tomas Lindqvist · @tlindqvist
Hardening and operations
Threat models plugins, audits capability and nonce handling, and builds the observability that tells you an integration broke before a customer does.
Continue