Skip to content
This repository was archived by the owner on Aug 8, 2026. It is now read-only.

Sudo v4.9.1

Pre-release
Pre-release

Choose a tag to compare

@dknauss dknauss released this 29 Jul 00:17
· 29 commits to main since this release
336d095

RESEARCH PROTOTYPE ONLY — DO NOT INSTALL ON PRODUCTION, PUBLIC STAGING, OR ANY SITE CONTAINING REAL USERS, CREDENTIALS, OR DATA.

Use this snapshot only in WordPress Playground or a disposable local test environment with synthetic data. It is not a supported production security plugin or security boundary.

A maintenance snapshot on top of 4.9.0. It corrects two ways the sudo session record and the cleanup that reads it could disagree, and finishes the documentation sweep 4.9.0 started. No migration; existing sudo sessions are unaffected.

If you are evaluating 4.9.0, prefer this snapshot. One of the fixes below is a silent failure in the revocation path.

Why this supersedes 4.9.0

"Revoke all sessions" could silently skip a live sudo session. Each user carries an expiry marker that must be at least as late as every sudo proof they hold, so anything enumerating on it cannot miss a user whose sudo is still enforcing. That marker was maintained by reading it back through the object cache, a few lines above a deliberately cache-bypassed read of the proofs themselves. A stale-low cached read could write a marker sitting beneath another browser's live proof — and because the bulk revoke selects on that marker in SQL, the operator's revoke skipped that user entirely. The per-user revoke was hidden on the Users list for the same reason.

The failure shape is the problem: it reported success, in the operator's favour, in the direction of believing the session was gone. A session that cannot be revoked is a worse outcome than one cleaned up too eagerly. The marker is now derived from the proof map the code already holds cache-bypassed, so a stale read can no longer lower it.

Site Health's cleanup deleted sessions that were still working. The stale-session sweep selected users one way and re-classified them another — a cached read against a cache-bypassed one — so a cache that fell out of step let it delete every valid browser proof for a user whose session was live. A second defect needed no cache failure at all: the sweep ignored the 120-second grace window that exists so a just-expired session can finish a form already in progress, deleting those proofs up to two minutes early on ordinary timing. Selection and classification now happen in one database query.

Operator-visible: cleanup of an expired session is now deferred by up to 120 seconds. During that window the sudo timer has expired but a cookie- and HMAC-bound proof is still accepted by gated paths — that is what the window is for, and the sweep was deleting proofs inside it.

Read this before evaluating

Browser-admin reauthentication is materially worse than before 4.9.0, and fixing it is the next priority.

Removing automatic replay closed a real vulnerability. The convenience cost is larger than the 4.9.0 notes conveyed, and is stated here rather than left to be discovered. Four faces, all tracked, none fixed in this snapshot:

  • a gated form returns you to the correct screen with every field blank, so the whole entry is retyped, not just the secrets (#436);
  • the notice reads "password and secret fields were not replayed — re-enter them", implying only the secrets were lost when in fact everything was (#436, #469);
  • a gated GET action is told to "review the form" when there is no form (#463);
  • options.critical gates on the presence of a critical field rather than a change to it, so every Settings → General save raises a challenge even when nothing changed (#445).

Combined, a routine settings save can mean: challenge, reauthenticate, lose the work, and read a notice that misdescribes what happened. The 4.9.0 notes said re-doing the action was "a re-click or a re-submit" — for a form it is a full retype, and that claim has been corrected.

A concurrency race remains open. The marker fix removes the object cache as a cause, not concurrency. The marker and the proof map are still two separate update_user_meta() calls, so two browsers activating at the same instant can still interleave to produce a too-low marker (#475).

Also in this snapshot

  • Documentation accuracy. 4.9.0 removed automatic replay outright, but claims describing the old behaviour survived in the security model, the FAQ, the developer reference, the manual test checklist, and roughly eighty code comments. Corrected — including two that were wrong rather than merely stale: a cleanup documented as guaranteed that is in fact best effort, and a rationale written in the present tense for a mechanism that no longer runs.
  • Project status. WP Sudo is explicitly classified as a research prototype. Tags are reproducible research snapshots, not production-readiness declarations.
  • Pre-tag audit. Six new entries in docs/llm-lies-log.md recording fabricated and unverified claims caught during this cycle — including three fabricated quotations in a draft of these very release notes, and one dangling citation created by the audit entry describing it.

Scope

The tag is retained for reproducible demonstration and review. A release number, green CI, and extensive review do not imply production readiness.

See Project Status, the 4.9.1 changelog, and docs/security-model.md for the threat model and its stated boundaries.

What's Changed

  • chore(release): point the Playground blueprint at v4.9.0 by @dknauss in #464
  • chore: classify Sudo as a research prototype by @dknauss in #466
  • docs: land the preflight architecture and sort the roadmap by the research status by @dknauss in #467
  • fix(354): make the stale-session sweep and the liveness marker cache-safe by @dknauss in #471
  • fix(427): bind reviewer approval to the staged tree, and install the gate by @dknauss in #472
  • docs: qualify the session-binding claim — auth cookie alone, not complete cookie state by @dknauss in #476
  • docs(480): stop release-status.md contradicting itself about replay by @dknauss in #481
  • chore(478): inventory the in-code replay-language debt before editing by @dknauss in #479
  • chore(release): sync version points to 4.9.1 by @dknauss in #492
  • fix(challenge): correct the entry-context reasoning in is_handler_endpoint() by @dknauss in #488
  • docs(lies-log): record the v4.9.1 pre-tag audit — entries #72-#77 by @dknauss in #495
  • fix(lies-log): remove a dangling citation the audit entry itself created by @dknauss in #496

Full Changelog: v4.9.0...v4.9.1