Launch offer
20% off every Pro tier — and every renewal — for a limited time. Use the code at checkout:
Permissions
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
Preview, capability, snapshot, verification, rollback — in that order.
The plugin, an Application Password, and the config your client wants.
The protocol, the safety model, and how the plugin draws its line.