Sudo – Research Prototype WP Registry grade D. High-severity findings · 43 findingsWP RegistryHigh-severity findings · 43 findingsOpen the reportD archived releases
Concluded research prototype — do not install. Final finding: github.com/dknauss/Sudo/blob/main/docs/finding.md
Install
The author publishes release zips, so WP-CLI can install straight from GitHub:
wp plugin install https://github.com/dknauss/wp-sudo/releases/download/v4.9.2/wp-sudo.zipSudo — concluded

So full of cracks, the barrier gatehouse of Fuwa lets both rain and moonlight in — quietly exposed, yet enduring.
— Abutsu-ni, Diary of the Waning Moon
This 13th-century poem was chosen at the start of this project, for its gate metaphor. It turned out to describe the project all too well: Sudo's barrier gate had cracks too — seven of them, all verified — and what endures is not the gate but the record of where the light came through.
[!CAUTION] Do not install this plugin. Not on production, not on staging, not on any site with real users, credentials, or data. It contains seven verified high-severity bypasses of its own central claim. They are documented rather than fixed, because they are the result.
Sudo was a six-month research prototype investigation of action-gated reauthentication in WordPress exploring one question, initially: What if WordPress requires a fresh proof of intent before consequential operations, regardless of role? It took its name and its symbol from the gate: 門, the radical that runs through East Asian writing, evoking the fortified pass where everyone and everything attempting to cross the gate is examined rather than trusted. The project is finished. This repository is archived and read-only.
What was tried, and what happened
WordPress asks for your password once, at login, and then never again. A valid session cookie is permission to do anything — install a plugin, which is arbitrary code execution; change another user's email and then their password; make every new signup an administrator. Steal the cookie, and you have the site.
The idea was to put a gate in front of the dangerous operations: notice the request, demand the password again, and only then let it through.
The gate could not reliably tell which requests were dangerous. Not because the list of dangerous operations was incomplete — that is the limitation everyone expects — but for a subtler reason.
To decide whether a request is about to delete a user, the plugin has to work out what WordPress is going to do with it. WordPress works that out too, in its own code, using its own rules. So two pieces of software are answering nearly the same question, separately, and never comparing answers. When they disagree, the plugin waves through something WordPress treats as a deletion.
They disagreed seven times under well-calibrated independent adversarial testing
after passing many adversarial reviews before. WordPress read a value from $_REQUEST; the plugin
read it from $_POST. WordPress matched a URL case-insensitively; the plugin
did not. WordPress accepted PATCH; the plugin listed only PUT — a set of
small, ordinary discrepancies, each one a complete bypass of the gate for the
operations it covered.
Nothing in WordPress core or Sudo could have detected these gaps. Neither side can see the other's rules, and WordPress is under no obligation to keep its own rules stable.
Sudo's own tests could not find the holes either. Thanks to test-driven development, there were over 1,600 automated tests, plus static analysis and a mandatory adversarial review process from the beginning. A test looked like: build a request that means "delete a user", hand it to the gate, check the gate stops it. But "a request that means delete a user" was built from the plugin's own understanding — the same understanding that was wrong. A wrong assumption produces a test that passes while proving nothing. All seven critical bypasses were eventually found by an independent adversarial analysis — another AI reading WordPress's source and the plugin's side by side.
That is the result, and it is a negative one, documented in full rather than quietly fixed.
[!TIP] New here? Start with
docs/sudo-architecture-history.md. It's a short, plain-language walk through every major approach this project tried — what the shipped plugin attempted, what a separate WordPress-core research track tried instead, a newer idea sketched after conclusion but never built, and concrete next steps for both. Read it before the technical documents below; they assume the context it provides.
The result
A WordPress plugin cannot provide ecosystem-wide action-gated reauthentication through route enumeration and post-submission interception.
Two mechanisms fail, and they fail on the same operations.
Route matching drifts from core. An adversarial audit found seven
high-severity bypasses across six independent axes — REST route case, HTTP
method set, $_POST versus $_REQUEST, action-name derivation, matcher
evaluation order, and surface coverage. Each is a total bypass. All seven were
independently verified against WordPress 7.x source and simulated an attack where an admin session is hijacked by the attacker who does not know the hijacked admin's password.
The defect is not an incomplete rule list. It is that the plugin's matching predicate and the predicate WordPress core dispatches on are two independently maintained things that drift, with nothing able to detect the drift.
| Axis | Core dispatches on | Plugin matched on |
|---|---|---|
| REST route case | preg_match( '@^…$@i' ) |
patterns with no i flag |
| File editor write | 'POST' === $_SERVER['REQUEST_METHOD'] |
action=update required |
option_page source |
$_REQUEST |
$_POST, in the self-protection rules |
| Bulk promote | isset( $_REQUEST['changeit'] ) |
an action-name allowlist |
| REST method set | EDITABLE = 'POST, PUT, PATCH' |
array( 'PUT', 'PATCH' ) |
| Edited user | $_REQUEST['user_id'] |
$_POST['user_id'] |
Effect vetoes work, but not where it matters. Hooking the effect rather than
the route is sound, and does gate unambiguous destructive effects such as
delete_user and activate_plugin. It cannot be generalised to option writes or
capability mutation, because core and ordinary plugins fire those same hooks
incidentally during normal admin loads — there is no intent signal to key on.
Every one of the seven bypasses lands in that excluded set.
1,308 unit tests, 243 integration tests, 112 E2E tests, PHPStan level 6, Psalm, and a mandatory adversarial review gate detected none of the seven bypasses. They could not have: every test asserts the plugin against its own model of a request, so a wrong predicate produces a wrong test that passes. All six axes were found by reading core and the matcher side by side.
What this argues for
A narrow WordPress core primitive:
- Explicit effect vetoes — an intent signal at consequential effects, so a guard can distinguish an actor's intended operation from the same hook firing incidentally during a page load.
- Action-bound, single-use approval — actor A may perform effect E, once, within window W, consumed by the effect rather than by a route, authorising a specific effect and never "re-run this stored request."
- A disposition contract for non-interactive surfaces — present action-bound proof, refuse, or follow an explicitly separate and auditable machine policy. Not universal reauthentication: cron and the auto-updater have no present human, so reauthentication is category-incoherent there.
Where the work went
The finding pointed somewhere specific: authorization has to happen where an
effect is committed, inside WordPress, rather than being guessed at from a
request outside it. That successor work — including a Core patch demonstrating
it, and a second failed attempt that explains why the obvious fix does not work
either — is at
wp-effect-authorization.
Its history is mirrored here on the research/capability-floor branch.
Documents
| Document | Contents |
|---|---|
docs/finding.md |
The technical result and what a core primitive would need |
docs/audit-verification-record.md |
Independent verification of all seven bypasses against WP 7.0 source |
docs/post-mortem.md |
How a heavily tested project failed to see what it had already diagnosed |
docs/security-model.md |
Threat model and the boundaries the prototype never claimed to cover |
docs/upstream-sources.md |
Every third-party claim, with enclosing symbol, machine-checked |
PROJECT-STATUS.md |
The research-prototype classification and why it exists |
The implementation and test suites are retained, read-only, as the evidence the findings rest on. Deleting them would leave assertions nobody could reproduce, which is the failure mode this project exists to document.
Honest scope
Every bypass presupposes an already-authenticated administrator session. None is a privilege crossing by a low-privileged or unauthenticated actor. They are complete defeats of what this plugin claimed to provide — reauthentication as a barrier in front of a compromised admin session — and nothing more than that.
The findings concern this plugin's architecture. They do not establish that no plugin can gate effects it owns, and they are not a vulnerability report against WordPress core or any third-party plugin.
Acknowledgements
Sudo's core design, development, and inexorable fate owes a debt to four people:
- John Blackbourn, for the action-gating concept — that consequential operations should require a fresh proof of intent, regardless of role. It was the single biggest conceptual contribution to the project. A minimal, five-minute-readable demonstrator of the same core-primitive argument (Trac #20140) is preserved at consequential-actions — also concluded, also archived, also not for installation.
- Tim Nash, for pragmatic security ideas, including the lockdown for roles and permissions, which shaped Sudo's opt-in admin-escalation guard and its opt-in role/capability lockdown audit, which have always been a viable mechanism for user account oversight.
- Calvin Alkan, for critical early feedback on the Sudo concept, the only (then) complete and accurate documentation for WordPress user authentication, and for his work on Fortress as inspiration. Calvin's insistence that a normal plugin cannot fully achieve what Fortress does was a motivation to see how close a plugin could get — and how the UX might be smoothed out. Calvin's "Is this becoming a SIEM?" critique shaped Sudo's explicit not-a-SIEM boundary. (Which was intentionally violated a bit.)
- Austin Ginder, for AI advice and adversarial testing — helping make the machine a better collaborator, then turning it loose as a tireless skeptic until confident claims yielded the expected contrary evidence.
License
GPL-2.0-or-later.
Releases
Newest 25 of 41 recorded releases. Each count is every asset in that release; expand a row for the breakdown.