BetterShield
Appearance
AI assistantsMCPApplication passwordsUser rolesSecurity audit

WordPress Abilities API: see what AI agents can reach and do

· · Updated · 16 min read

Your WordPress site already keeps a list of things an AI agent can do on it. Since WordPress 6.9, any plugin can add to that list by registering abilities, and agents reach them through more than one door. Some abilities only read. Some change your site, and the plugin that registered each one decides who may call it.

BetterShield shows both sides of that. Agents › Agent surface shows what agents can reach right now: every registered ability, the credentials agents sign in with and the AI keys stored on the site. Activity › Agent activity shows what they actually did, call by call, with the door each call came through. This guide walks through both, and what to do when a plugin opens more than it should.

Quick summary

  • An ability is a unit of work registered with WordPress, with a name, inputs, outputs and its own permission check. Abilities need WordPress 6.9.
  • Registered abilities shows “Everything any plugin on this site has made callable by an agent, read live from the registry.”
  • An ability that writes, is reachable over REST and is open to lower roles raises A plugin lets any signed-in user, even a Subscriber, make changes to the site, at High.
  • The same tab lists the Application passwords agents sign in with, the Connector credentials stored on the site, and two tripwires.
  • Agent activity records every call to any plugin’s abilities, refused ones too, with its outcome and its door: MCP, REST, WP-CLI or PHP.
  • BetterShield reports. When an ability is too open, the fix belongs to the plugin that registered it.

Your site already tells AI what it can do

The Abilities API arrived in WordPress 6.9 as one central registry of what a site can do. Before you install a single AI plugin, WordPress has already put a few read-only entries in it, such as core/get-site-info, core/get-user-info and core/get-environment-info. Every plugin that registers abilities adds its own.

Each ability has a name in the form namespace/ability-name, where the namespace usually matches the plugin, a description, a schema for its input and output, and an optional permission check that decides whether the current user may run it. A plugin can also make an ability callable through the REST API.

The permission check is each plugin’s own choice. The WordPress 6.9 developer note shows one that admits any account with the read capability, which every default role has, including Subscriber. That suits an ability that counts posts. It would be a poor fit for one that changes settings. Roles and capabilities lists what each role can do.

One registry, four doors

The new architecture has three parts. Plugins register what they can do once, in the abilities registry. On WordPress 7.0 or newer, Settings › Connectors holds the AI provider keys that plugins share. And calls reach the abilities through several doors: an MCP connection for an assistant, the REST API, WP-CLI, or plain PHP code running on the site. MCP is one door, not the whole house.

Register once, every door opens

Where agents reach your site, and where BetterShield looks

Plugins and WordPress register abilities in one registry, Connectors keep the AI keys beside it, and calls arrive through four doors. Choose a part to see what BetterShield shows about it. This map explains the screens; it does not change a site.

Abilities: what plugins can do

What agents get
Named actions that any plugin can register, each with its own permission check. WordPress registers a few read-only ones itself. A writing ability changes the site.
Where BetterShield shows it
Agents › Agent surface › Registered abilities lists every one: who registered it, read-only or writes, REST, and who can call it.
Your next check
Choose Open to lower roles. An empty list is the answer you want.

Doors: how a call arrives

What agents get
A call to an ability can come over MCP, over REST, through WP-CLI or straight from PHP code on the site.
Where BetterShield shows it
Activity › Agent activity records every call, refused ones too, with its outcome and the door it came through. Inputs are kept as a digest, never their contents.
Your next check
Filter by Came through and by Refused. Any call you cannot explain deserves a question.

Credentials: who an agent signs in as

What agents get
A tool or agent acts with the permissions of the account it signs in as, often through that account’s application password.
Where BetterShield shows it
Agents › Agent surface › Application passwords lists every account’s, marks the stale ones, and offers Limit… for routes and a network.
Your next check
Limit the passwords you keep, and retire the ones nothing uses.

Keys: whose AI account is used

What agents get
On WordPress 7.0 or newer, an AI provider key added under Settings › Connectors is there for every plugin that uses it.
Where BetterShield shows it
Agents › Agent surface › Connector credentials shows where each key is stored and how many active plugins can read it, never the key itself.
Your next check
A key kept in the database shows as stored unencrypted. Defining it in wp-config.php or an environment variable keeps it out of the database.
Two tripwires sit on the same Surface tab: a decoy credential and a decoy URL. They block nothing; they alert you when something touches a credential or a path that nothing should use.

Put together, your agent surface is everything an agent could reach on the site: the abilities it can call, the doors it can use, the account it signs in as, and the keys plugins can read. OWASP’s list of risks for AI applications calls the general problem excessive agency. Its root causes are too much functionality, too many permissions and too much autonomy, and the harm depends on which systems the agent is able to interact with.

BetterShield › Agents has four tabs: Connect, Requests, Permissions and Surface. The first three govern BetterShield’s own MCP connection, covered in WordPress MCP: connecting an AI assistant to your site safely. Surface looks past BetterShield, at what every plugin has opened, and Agent activity under Activity records what came through.

What agents can reach: Agents › Agent surface

Open BetterShield › Agents › Agent surface. It has six sections, in this order: Registered abilities, Application passwords, Retire unused application passwords, Decoy credential, Decoy URL and Connector credentials. The abilities list is a report and reading it changes nothing. Only the credential controls change anything, and only when you use them.

Registered abilities

Registered abilities comes first. On a site older than WordPress 6.9, the card says “Abilities require WordPress 6.9.” and stops there.

The table has three columns:

  • Ability: the registered name.
  • Registered by: the plugin that registered it, or WordPress. Each plugin’s abilities sit under its name with a count.
  • Declares · reachable over · who can call it: up to three chips. read-only or writes, as the ability declares itself. REST when it can be called through the REST API. lower roles, administrators or unknown for who its permission check admits.

Two tabs above the table switch the view, each with its count: All and Open to lower roles. Search ability names narrows either one. A long group shows “Showing … of …” and Show more.

BetterShield’s own abilities appear only while its read access is on, under Agents › Permissions as Let my agent read security information. That makes a quick test that the list is live: turn read access off, reload, and BetterShield’s group is gone.

The one combination that raises a finding

Beside the table, Which column matters answers in four words: “Who can call it.” The screen then spells out that an ability “is a finding, and appears in Findings with a severity of its own, when all three are true”:

  • “It changes things.”
  • “It is reachable over REST.”
  • “It is open to lower roles.”
Which column matters

Three answers make an ability a finding

Select a condition to see what it means and what to check next.

  1. It changes things

    The ability declares that it writes.

  2. It is reachable over REST

    A signed-in request can call it.

  3. It is open to lower roles

    Its permission check admits accounts below administrator.

It changes things

What this means
Every ability declares whether it only reads or also changes the site, and the Surface shows that as read-only or writes. The declaration is the registering plugin's own.
Your next check
Note which plugins registered writing abilities, and whether you expected each one.

It is reachable over REST

What this means
An ability shown in the REST API can be called over the network by a signed-in account, or by a tool or agent holding that account's credentials.
Your next check
Look further down the same tab at which accounts hold application passwords.

It is open to lower roles

What this means
The registering plugin decides who may call each of its abilities. With all three true, the audit raises A plugin lets any signed-in user, even a Subscriber, make changes to the site, at High.
Your next check
Choose Open to lower roles. An empty list there is the result to aim for.
BetterShield reports the exposure; the permission check lives in the plugin that registered the ability. A declaration is that plugin's own statement, so treat a surprising writing ability as a question for its author.

The finding is A plugin lets any signed-in user, even a Subscriber, make changes to the site, at High. Its explanation says a plugin “has registered an agent-callable ability that is not read-only, is reachable over the REST API, and whose own permission check admits accounts far below administrator.” Any signed-in low-privilege account can call it, and so can an agent holding that account’s credentials.

Its Before you act note is short: “Could break: nothing from this finding itself.” BetterShield does not change the ability for you. The fix belongs to the plugin that registered it.

The result to aim for is the empty one. With nothing open, Open to lower roles says: “None of the … registered abilities admits accounts below administrator. That is the answer this screen is for.”

When an ability is too open

  1. Note the plugin under Registered by, and read what the ability does in that plugin’s own documentation.
  2. Ask its author whether the permission check is meant to admit those roles. A narrower check is a change in that plugin, through its own settings or its code, not a setting in BetterShield.
  3. Until a fix ships, decide whether that plugin needs to stay active. The finding suggests exactly this.
  4. Read Agent activity for that namespace, as described below, to see whether anything has already called it.
  5. Look again after each update of that plugin. The list is read live from the registry, so it changes when plugins do.

It also helps to keep lower-role accounts few and current. WordPress user roles security walks through that review.

Application passwords

Application passwords is introduced as “The credential connected tools and AI agents actually sign in with.” WordPress keeps them per user, and this section is the one place they are visible across the site.

  • Each account that holds one is listed with its role and count. A password nothing has used for ninety days is marked stale: “a live key with no live purpose.”
  • Under Sign-in & accounts, a password reads Reaches everything its account can, from anywhere until you use Limit…. REST routes it may use takes route prefixes, and Network it may be used from takes an address or a range. A request outside the limit is refused.
  • Retire unused application passwords removes unused ones on a daily sweep. It is off by default, and a retired password cannot be restored.

These connect directly to the finding above. A lower-role account that holds an application password is exactly the agent credential it describes. Holding that password to the routes its tool needs keeps it away from every other route. The full walkthrough is in WordPress application passwords: find, limit and revoke them, and the form is explained in Application password limits.

Connector credentials

Connector credentials appears on WordPress 7.0 or newer. For each supported connector it shows whether a key is set and where it lives: “in this site’s database”, “in an environment variable”, “in a PHP constant” or “not set”. It never shows the key itself.

Its summary line is the point of the section: ”… connector credentials are stored on this site, and every one of the … active plugins can read them.” Connectors put every AI key in one place, which is convenient, and WordPress does not keep stored keys apart per plugin. A value in the database, an environment variable and a PHP constant can all be read by code running on the site.

Where a key lives still matters. A key saved from the dashboard sits in the database, shown here as stored unencrypted, and travels with every database export and backup. A key defined in wp-config.php or a server environment variable shows as outside the database. Our advice: keep connector keys out of the database where your host allows it, keep only necessary, trusted plugins active on a site that stores them, prefer restricted keys from the provider, and rotate a key after any suspected exposure. See Connector credentials.

Decoy credential and decoy URL

Two controls on the tab watch for misuse rather than prevent it.

Keep a decoy credential on my account is off by default. It creates an application password that belongs to nothing: no tool has it and no configuration names it, so the only way it can be used is that someone read it from the site’s database or a backup. Any use is refused, and you are told immediately. Security teams call a planted secret like this a honeytoken. It shows on your profile as BetterShield canary (do not use). Leave it alone, because using it yourself sets off the alarm. While all is quiet, the section reads “Planted … Nothing has touched it.”

Decoy URL watches an Unused path of 8 to 80 lowercase letters, digits or hyphens, such as private-export-review. The path returns your ordinary 404 page, and a request to it raises an urgent alert. As the screen says, “It does not prove compromise.” A hit can be probing, a scanner or an accidental visit. Only requests that reach WordPress are seen, since server rules and edge caches can bypass it. Test matcher without alerting checks the path without sending an alert.

Details are in Decoy credential and Decoy URL.

What agents did: Activity › Agent activity

The Surface answers what agents could do. BetterShield › Activity › Agent activity answers what they did. It records every time an ability was called on the site, whichever plugin registered it and whichever door the call came through, including calls that were refused. Inputs are kept as a digest, never as their contents.

Each row names the Ability, its Outcome and When. The outcomes are Completed, Refused, Input rejected, Input unreadable, Output rejected, Answered early, Failed and Did not finish, and each has its own view with a count. Two filters narrow the list:

  • Namespace: one plugin’s family of abilities, or Any namespace.
  • Came through: Any door, or just MCP, REST, WP-CLI or PHP.

From a row you can open Everything this account did, which narrows the list to calls made as that account. Show every account widens it again.

Read namespaces with one caution, in the screen’s own words: “WordPress does not record which plugin registered an ability, so a namespace is a naming convention rather than a guarantee of who is behind it.” A namespace tells you where to look, not who is responsible.

Recording is on by default, under Agents › Permissions as Record what agents do on this site, and it is separate from read and change access. It needs WordPress 7.1 or newer, because it relies on the ability lifecycle that arrived there. On an older version the screen says “Agent activity is not recorded on this version.” Rows are kept for 30 days in the free plugin. What assistants did, on Agents › Connect and Agents › Permissions, opens the same history.

The docs suggest where to start: review Refused and Failed calls first, and compare an unexpected call with the connection and application password that could have reached the site. That is where the two screens meet. The Surface shows the doors that are open; Agent activity shows who walked through them. See Agent activity and Filter and review agent activity.

Look freely, ask before touching

Every ability can declare itself read-only, and a careful client uses that: it reads without asking and asks a person before it changes anything. That is how agents should work with a site. Let them look freely. Make them ask before they touch.

But a declaration is a sign, not a lock. It asks a client to be careful; it cannot make it. So BetterShield does not rely on the client for its own abilities:

  • Its MCP server offers twenty-five bounded, read-only abilities, and reading is off until you turn it on.
  • Changing is a second switch, off by default. Sign-in changes, removing a protection and putting a changed file back need a plan and a person’s one-time approval on Requests, where Allow once expires 15 minutes after the plan was made.
  • A connection may make 5 changes of one kind per hour. Calls beyond that are refused, not queued.
  • Some things are never possible through a connection, such as creating accounts or credentials, changing recovery options or deleting log rows.

Other plugins make their own choices, which is why the Surface and Agent activity look at every plugin, not just BetterShield. Details are in Review change requests from agents.

Let your assistant read the surface too

BetterShield 1.1.1 added read-only tools for agent information, among them one that reports the agent surface. With read access on, an assistant you connect can describe the same picture back to you.

Read access is off by default. Turn it on under Agents › Permissions with Let my agent read security information. These abilities never return credentials, connector keys or file contents. The docs give the safest order: start with read access only, and confirm what appears under Agents › Agent surface before allowing changes. See Let an AI assistant read security information.

A worked example

Worked example (fictional): a site owner installs a booking plugin. On Registered abilities, Open to lower roles now lists one entry, example-booking/cancel-booking, with writes, REST and lower roles, and the High finding appears in Findings.

In Agent activity, they set Namespace to that plugin’s namespace and Came through to REST. The screen says “Nothing matches these filters.” Nothing has used it yet. They write to the plugin’s author with the ability’s name, check which lower-role accounts hold application passwords on the Surface, and limit the one a customer integration uses to the routes it needs. Bookings are the plugin’s whole job, so it stays active while they wait for a fix. When the update ships, Open to lower roles is empty again.

A ten-minute review after each plugin install or update

  1. Open Registered abilities and choose Open to lower roles. An empty list is the answer you want.
  2. Scan Registered by for a plugin you just added. New abilities arrive with new plugins.
  3. In Agent activity, look at Refused and Failed first, then at any door you do not expect, such as REST calls on a site where you only connect assistants over MCP.
  4. In Application passwords, look for anything stale, and for passwords that still reach everything their account can.
  5. In Connector credentials, note where each key is stored and how many active plugins can read it.
  6. Check that the decoys you rely on are still on, and quiet.

Common mistakes

  • Reading read-only as harmless. A read-only ability still shares information with whoever calls it.
  • Treating a declaration as a guarantee. read-only and writes are what the registering plugin declares. A surprising writing ability is a question for its author.
  • Treating a namespace as an identity. It is a naming convention, not proof of which plugin is behind a call.
  • Fixing it in the wrong place. BetterShield reports the exposure. The permission check lives in the plugin that registered the ability.
  • Turning recording off because nothing is connected. Keep it on whenever abilities are registered. Refused calls are useful evidence.
  • Limiting a password and ending your session before testing. Test the tool first, so a wrong limit is easy to remove.
  • Using the decoy credential yourself. It sets off the alarm, by design.
  • Reading a decoy URL hit as proof of a break-in. It can be a scanner or a stray visit.

Frequently asked questions

Which WordPress version do I need?

Abilities need WordPress 6.9, and Registered abilities says so on older versions. Connector credentials appears on WordPress 7.0 or newer. Recording Agent activity needs WordPress 7.1 or newer.

Does the Surface change anything on my site?

No. It reads the live registry and reports. The controls on the same tab, password limits, the retirement sweep and the decoys, change things only when you use them.

Does Agent activity store what an agent sent?

No. Each row keeps the ability, its outcome, its door and when it happened. Inputs are kept as a digest, never as their contents.

How long is agent activity kept?

Rows are kept for 30 days in the free plugin.

Does BetterShield show my AI provider keys?

No. Connector credentials shows where a key is stored and how many active plugins can read it, never the key.

Can an AI assistant read the Surface?

Yes, with read access on. BetterShield’s read-only abilities include one that reports the agent surface, without credentials or keys.

Conclusion

Register once and every door opens. That makes a WordPress site far easier for agents to work with, and it makes two questions part of owning one: what could an agent do here, and what did it do? Agents › Agent surface answers the first, Activity › Agent activity answers the second, and the finding speaks up when a plugin opens a writing ability to lower roles. Look after each plugin install or update, keep Open to lower roles empty, and read the refused calls first.

BetterShield is free on WordPress.org. To connect an assistant, read WordPress MCP: connecting an AI assistant to your site safely.

Close the open doors today

Install the free plugin. The first audit runs when you activate it, and all but one of the fixes wait for your choice.

Requires WordPress 6.7 or newer and PHP 8.0 or newer.

Get BetterShield