Limited-time launch offer 20% off Pro — first payment and every renewal — with code See pricing

Permissions

An application password is the wrong unit of permission.

It is not that Application Passwords are insecure. It is that the thing they grant — a whole user's capabilities across the whole REST surface — is far larger than the thing you meant to allow.

SiteHelm still uses an Application Password to authenticate. What changes is what happens after authentication, which is the part that was never under your control before.

 · 4 min read

An Application Password is a good piece of engineering. It exists so that you can authenticate a program without giving it your account password, so that you can see which applications hold credentials, and so that you can revoke one without disturbing the rest. Nothing in this piece is an argument against them.

The argument is about what happens next. Once a request authenticates as a user, WordPress decides what it may do by asking what that user can do. Not what the application was for. Not what you had in mind when you created the credential. The unit of permission is the user, and the surface is every REST route that user can reach — including the routes registered by plugins you installed for unrelated reasons and have never thought about as an automation surface.

For a human that is fine, because a human has intent. For an agent it means the gap between “I wanted it to draft posts” and “it can do anything an editor can do, everywhere” is a gap you are holding open with nothing but hope.

What a credential cannot express

Think about what you actually wanted to allow. Something like: read the content, propose changes to these pages, never touch the menus, never touch anything commerce, and show me the change before it happens.

Now try to write that as a credential. You cannot. A credential has no field for “only these objects”, no field for “propose, do not commit”, and no field for “show me first”. The only dial available is the role, which is coarse, global, and phrased in terms of capabilities rather than tasks. So people reach for the role that definitely works, which is usually a role far larger than the job, and then hope the client only calls what it was asked to.

Four things are missing, and none of them can be added at the credential layer:

  • Per-action review. There is no natural point between “the agent decided” and “WordPress did it”.
  • A preview. The REST API applies a write when it receives one. There is no dry run.
  • Rollback. Some things can be recovered from revisions or the trash. Menus, taxonomy assignments, options and page-builder documents largely cannot.
  • One place to read afterwards. Requests land in whatever your host logs, which is not an account of what an agent attempted or why something was refused.

Operations as the unit instead

The alternative is to stop granting a credential and start granting a list. The gateway sits between the agent and WordPress, and the only things that exist are typed operations that somebody wrote deliberately. There is no route-shaped surface for the agent to explore, because it is not talking to the REST API — it is talking to a catalogue.

Once the unit of permission is an operation rather than a credential, the four missing things become expressible:

Each operation declares the capability it needs, and re-checks it. Not once at connection time — in the handler, against the user the request authenticated as, every call. If that user cannot do it, the answer is Forbidden, and the refusal does not name the capability, because a refusal that teaches you which lock to pick is a worse refusal.

Writes are previewed before they apply. The first call returns a plan. Reading that plan is the review step a credential could not give you. The write happens on a second call carrying the single-use token the plan produced, with identical arguments; change them, reuse the token, or wait too long and it is refused as StalePlan.

Writes are snapshotted, and rollback lives in wp-admin. Not “restore from your host’s backup”, which is a different scale of event. The specific change, put back.

Every call is written to an audit ledger — what was attempted, by which user, what it changed, and the refusals too. That is the record you go and read when something looks wrong three days later.

And underneath all of it, operations and whole modules can be switched off from the admin console. An operation that is off is not enumerated and not callable, which is a far more honest way to say “the agent may not touch commerce” than choosing a role and hoping.

The part a gateway does not solve

Least privilege still matters. Give the agent its own WordPress user, with the narrowest role that does the job, rather than reusing an administrator because it was to hand. Revoke Application Passwords you have stopped using — they are per-application precisely so that this is painless. Take stock of what your plugins expose over REST, because that surface is the thing a credential opens and it is bigger than most site owners assume.

A gateway does not replace any of that. It changes what a mistake costs once the agent is legitimately allowed to act. Both halves are load-bearing: the role decides what could ever happen, and the operation layer decides whether the thing that happens is the thing you read first.

The short version

Handing an agent an Application Password is not reckless because the credential is weak. It is reckless because the credential is the wrong shape: it grants a user, and you wanted to grant a job. Everything that makes agent access survivable — a preview you can reject, an authority to write that is bound to the preview you read, a snapshot behind the change, a ledger you can read afterwards — has to live somewhere that understands what an individual action is.

A credential does not. It only knows who you are.

Questions

The questions this raises

Are WordPress Application Passwords insecure?

No. They are a sound authentication mechanism: per-application, individually revocable, and usable without ever handing over the account password. The problem is not authentication, it is authorisation — an Application Password carries the full capability set of the user it belongs to, across every REST route that user can reach.

Does SiteHelm still need an Application Password?

Yes. The plugin authenticates the request as a real WordPress user, because that user's capabilities are what every operation is checked against. The difference is that the credential no longer decides what may be attempted: the catalogue of typed operations does, and each one re-checks its own capability against that user when it runs.

Can I just give the AI a low-privilege role instead?

You should, and it helps, but it solves a different problem. A narrow role limits what the agent could ever do; it does nothing about mistakes inside what it is legitimately allowed to do. Editing the wrong page is a permitted action performed on the wrong object. That is what a preview step catches and a role cannot.

What happens if I stop using an agent?

Revoke the Application Password in the user's profile screen. It is per-application by design, so revoking one does not disturb anything else, and a credential nobody uses is the one most likely to still be sitting in a config file somewhere.

Do plugins really expose REST routes I do not know about?

Routinely. Plugins register their own REST namespaces, and whether a request to one succeeds is decided by the acting user's capabilities. That is why a credential-shaped permission model is hard to reason about: the surface it opens is not a list you wrote, it is the sum of everything installed.

Next

Read the thing itself.