feat(editor): in-editor reauth — Milestone A (password path)#178
feat(editor): in-editor reauth — Milestone A (password path)#178dknauss wants to merge 21 commits into
Conversation
🎮 Try in WordPress PlaygroundPreview is pinned to WordPress Uses the checked-in Logs in as ✅ What you can test
❌ What won't work
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #178 +/- ##
=========================================
Coverage 90.79% 90.79%
Complexity 1524 1524
=========================================
Files 20 20
Lines 5801 5801
=========================================
Hits 5267 5267
Misses 534 534
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
▶ START HERE — executing Milestone A (browser-capable session)This PR is a parked WIP tracking home, not merge-ready. Milestone A needs a browser-capable session (wp-env / Docker / real browsers + password managers) — the headless build sandbox can't run those. Launch with Read first (in order):
Current state (facts):
First tasks:
Exit gates (all required before un-drafting): rebased; EDITOR-06 + bypass-invariant + stale-nonce + degradation green on single-site AND Scope: password path only. In-modal 2FA (server-rendered provider partial; WebAuthn stays link-out) is Milestone B — a separate follow-up on top of this. Generated by Claude Code |
|
Execution checklist added → Generated by Claude Code |
…loor)
WIP floor — runtime/E2E verification deferred to a wp-env/browser session
(the modal-render tests are test.fixme; code-level review passed).
On sudo_required the apiFetch middleware now opens a single-flight in-editor
modal instead of only linking out. The password step mirrors the proven
wp-sudo-challenge.js fetch flow (FormData authAction + wp_sudo_challenge
nonce, session-only, credentials:same-origin); on {authenticated} it closes
and transparently re-dispatches the original request via wp.apiFetch so it
carries the user's own wp_rest nonce and re-passes the now-active session
(C3). 2fa_pending / cancel / no-config fall back to the Increment 1 link-out
snackbar. A stale grant nonce is refreshed via refreshNonceAction first.
- admin/js/wp-sudo-editor-reauth.js: modal + single-flight + re-dispatch.
- Plugin::enqueue_editor_reauth(): +wp-element, +wp-components deps.
- editor-reauth.spec.ts: snackbar-primary EDITOR-01/02/03/05 -> test.fixme
(superseded by the modal; reconcile in wp-env); EDITOR-04 stays active;
new EDITOR-06 documents the modal-grant target. E2E 69 -> 70.
C1 holds (grant nonce is CSRF-only; server still enforces check_ajax_referer
+ password + Sudo_Session). No stash_key, no content-save gating, no build
step. Password never stored/sanitized/logged client-side.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a WordPress Playground demo for the in-development block-editor (Gutenberg) reauthentication modal. The blueprint boots WP + PHP 8.2, installs the plugin from this branch's GitHub archive, and writes a demo-only mu-plugin that suppresses the login auto-grant and clears the session on each editor page load, so the reauth modal reliably triggers on the first gated editor action (e.g. a Block Directory install). Also add a readme.md Playground badge, an "In-editor reauthentication (Gutenberg — in development)" section, and a screenshot of the modal over the block editor. Browser-verified end-to-end in Playground; no production code changed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the scope/sequencing decision after finding a working password-grant modal floor already built on this branch. Splits the transparent-modal work into Milestone A (password path — finish + harden this floor) and Milestone B (2FA in the modal), with per-milestone gates including a hard 2FA-bypass invariant test. Corrects the phase-2 plan's stale "not started" status to point at the new milestone plan. Docs-only; no code change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
Scopes the hard piece of Milestone B (in-modal TOTP/email-OTP/backup-code reauth): extract the full-page 2FA render block into a shared renderer, expose it behind a login+nonce+2fa_pending-gated admin-ajax partial endpoint, inject verbatim, and POST the provider's own fields to the unchanged handle_ajax_2fa. WebAuthn/push/unknown/hook-only providers link out (default-deny). Design-reviewed; critique incorporated. Notably: the email provider's authentication_page() sends the OTP mail on render, so the partial fetch is state-changing for email — flagged as needing a shared server-side resend throttle, retracting the "render surface only" framing. Also handles the hook-based 2FA extension path (no Two_Factor_Core provider -> link out), pending-expiry mid-2FA, generic field serialization, and multisite transient locality. Verified against the live WordPress/two-factor source (2026-07-14): Two_Factor_Email::authentication_page() calls generate_and_email_token(), which calls wp_mail() — the OTP email is sent on render, so a partial re-fetch is a mail-sending side effect. Source: https://github.com/WordPress/two-factor/blob/master/providers/class-two-factor-email.php Docs-only; no code change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
Docs-only; no code change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
Ordered RED->GREEN TDD steps (rebase -> EDITOR-06 floor -> 2FA-bypass invariant -> stale-nonce -> REST boundaries -> degradation/C4 -> password-manager matrix -> full single-site+multisite gate sweep -> un-draft), then the Milestone B sequence from the reviewed 2FA-partial brief. Gate commands and the per-step gates included so a browser-capable session can execute straight through. Docs-only; no code change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TCJoA72EDAmLviMKDB5zx
0979474 to
964d730
Compare
Rebase onto origin/main was a clean replay (0 behind / 6 ahead); branch editor-reauth code byte-identical to the reviewed floor. Gates green (test:unit 1026, lint, PHPStan+Psalm, verify:metrics); force-pushed to PR #178 (head 964d730). Note the stale-classmap gotcha for the next session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebase onto origin/main was a clean replay (0 behind / 6 ahead); branch editor-reauth code byte-identical to the reviewed floor. Gates green (test:unit 1026, lint, PHPStan+Psalm, verify:metrics); force-pushed to PR #178 (head 964d730). Note the stale-classmap gotcha for the next session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fore E2E A bare 'a WordPress answers on :8889' is not sufficient — that port can be an unrelated project's env (observed: a Pixel wp-env via SSH tunnel with no WP Sudo). With nothing gating, EDITOR-04 passes vacuously and masks a dead target. Guard: assert a WP Sudo plugin entry is status:active and a gated apiFetch is rejected (sudo_required) before trusting any spec result. Also note wp-env start here brought up MySQL but not the WordPress containers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
⚠ Step 1 env guard — verify WP Sudo's own wp-env before running E2ELearned the hard way during Step 0 handoff prep: "a WordPress answers on Consequences if you don't check:
Do this before trusting any E2E result:
Also: on this session Guard is now recorded at the top of Step 1 in Context: Step 0 (rebase) is complete — rebased clean onto Generated by Claude Code |
✅ Ready E2E env for Step 1 — WordPress Studio (
|
| Piece | State |
|---|---|
| Site | Studio "Sudo Test" → http://localhost:8881 — Online, WP 7.0.1, PHP 8.5 |
| Plugin | wp-content/plugins/wp-sudo symlinked to the repo (live mount → tracks branch feat/gutenberg-reauth-increment-3), activated, v4.6.0 |
| Served build | Confirmed this branch's modal JS (wp-sudo-editor-reauth.js?ver=4.6.0, components.Modal present) |
| Admin | admin / password with all four governance caps (manage_wp_sudo, view_wp_sudo_activity, export_wp_sudo_activity, revoke_wp_sudo_sessions) |
| Gating proven | With wp_sudo_* cookies stripped (as global-setup does), a gated apiFetch opens the modal — "Confirm your identity", password field, Confirm button. All three EDITOR-06 selectors verified live. |
Run Step 1 against it (the harness defaults to wp-env on :8889, so target Studio explicitly and skip the wp-env-only cap step — caps are already set):
WP_BASE_URL=http://localhost:8881 WP_SUDO_SKIP_WP_ENV_CAP_SETUP=1 npm run test:e2e:localglobal-setup already logs in as admin/password, strips wp_sudo_* cookies, and works against this site (verified by mirroring its exact flow).
Studio CLI cheatsheet (bundled at /Applications/Studio.app/Contents/Resources/bin/studio-cli.sh):
… status --path ~/Studio/sudo-test— site URL / online state… start --path ~/Studio/sudo-test— start if offline… wp --path ~/Studio/sudo-test <wp-cli …>— WP-CLI passthrough (note: plugin toggles are themselves sudo-gated on the CLI surface)
Baseline note: Hello Dolly is currently active; EDITOR-06's PUT /wp/v2/plugins/hello {status:'active'} still fires the gate on the request (proven), so it passes regardless of current state.
Remaining Step 1 work (browser session): un-fixme EDITOR-06 (selectors already confirmed) → green with the command above; reconcile EDITOR-01/02/03/05 (retire or repoint to link-out; keep EDITOR-04); update docs/current-metrics.md E2E count in the same commit; reviewer flag on the code commit.
Generated by Claude Code
…Milestone A, Step 1) Activate the in-editor grant-modal E2E floor and correct the middleware to the reviewed C4/Q2 decisions that the initial floor (25c2e72) had not yet met. Production (admin/js/wp-sudo-editor-reauth.js): - C4: handleSudoRequired() now opens the grant modal only when the response carried a validated same-origin challenge_url. A null/unsafe URL (headless, app-password, or a javascript: URL rejected by isSafeChallengeUrl) surfaces a plain notice with no reauth affordance — no modal. - Q2: a batched /batch/v1 sudo_required is detect-and-surface ONLY. It links out via the snackbar and never re-dispatches the envelope (which could repeat successful sibling mutations) and never opens the modal. - Robustness: a terminal .catch on the submit chain restores the busy state on a rejected transport/body-read, so a failed grant can never lock the modal (onRequestClose/Cancel are disabled while busy). Tests (tests/e2e/specs/editor-reauth.spec.ts) — 7 active, no test.fixme: - EDITOR-06 modal password grant re-dispatches the original request. - EDITOR-01 modal cancel falls back to the link-out snackbar. - EDITOR-02 batched sudo_required detect-and-surface (no modal, no replay). - EDITOR-03/05 no safe challenge_url -> plain message, no modal (C4). - EDITOR-07 a rejected submission restores the modal and replays nothing. - EDITOR-04 unchanged (normal error != modal). Verified 7/7 green against a live WP Sudo env (WordPress Studio, WP 7.0.1). Also: refresh languages/wp-sudo.pot (new + previously-unextracted modal strings), update the editor docblock in includes/class-plugin.php, and sync the E2E count in docs/current-metrics.md (71). Q3 concurrent single-flight and the /batch/v1 deeper handling remain Step 4; PR #178 stays draft until Step 8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Milestone A, Step 1) Activate the in-editor grant-modal E2E floor and correct the middleware to the reviewed C4/Q2 decisions that the initial floor (25c2e72) had not yet met. Production (admin/js/wp-sudo-editor-reauth.js): - C4: handleSudoRequired() now opens the grant modal only when the response carried a validated same-origin challenge_url. A null/unsafe URL (headless, app-password, or a javascript: URL rejected by isSafeChallengeUrl) surfaces a plain notice with no reauth affordance — no modal. - Q2: a batched /batch/v1 sudo_required is detect-and-surface ONLY. It links out via the snackbar and never re-dispatches the envelope (which could repeat successful sibling mutations) and never opens the modal. - Robustness: a terminal .catch on the submit chain restores the busy state on a rejected transport/body-read, so a failed grant can never lock the modal (onRequestClose/Cancel are disabled while busy). Tests (tests/e2e/specs/editor-reauth.spec.ts) — 7 active, no test.fixme: - EDITOR-06 modal password grant re-dispatches the original request. - EDITOR-01 modal cancel falls back to the link-out snackbar. - EDITOR-02 batched sudo_required detect-and-surface (no modal, no replay). - EDITOR-03/05 no safe challenge_url -> plain message, no modal (C4). - EDITOR-07 a rejected submission restores the modal and replays nothing. - EDITOR-04 unchanged (normal error != modal). Verified 7/7 green against a live WP Sudo env (WordPress Studio, WP 7.0.1). Also: refresh languages/wp-sudo.pot (new + previously-unextracted modal strings), update the editor docblock in includes/class-plugin.php, and sync the E2E count in docs/current-metrics.md (71). Q3 concurrent single-flight and the /batch/v1 deeper handling remain Step 4; PR #178 stays draft until Step 8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…carryovers Step 1 (in-editor reauth modal floor) landed in ce1c67e, codex-approved. Record that Q2 batch detect-and-surface (Step 4) and C4 no-safe-URL client degradation (Step 5) landed early in Step 1, and carry the two deferred PR-level items to Step 8: Q3 concurrent single-flight and the b86de6b commit-message source-URL amendment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… client (Milestone A, Step 2) The security gate: a 2FA user's password-only step must return 2fa_pending and NEVER mint a sudo session, so the in-editor password modal is safe for 2FA accounts (they link out to the full challenge). The invariant already held in production (attempt_activation() only calls activate() on the non-2FA path); this makes it an enforced, regression-guarded guarantee. Tests only — no production change. - Unit (SudoSessionTest): a 2FA user's correct password yields 2fa_pending and update_user_meta() is never called — activate() writes the expiry/token meta, so its absence proves no session/token was minted. - Integration (TwoFactorTest, real Two Factor plugin): attempt_activation() and handle_ajax_auth() both yield 2fa_pending (never authenticated) with is_active() false. Verified against a Studio-seeded WP 7.0.1 core + MariaDB (full suite 215 tests, 0 failures). - E2E (editor-reauth.spec.ts EDITOR-08): a 2fa_pending grant response makes the modal link out instead of granting; the original request stays rejected. Verified 8/8 on WordPress Studio. Sync docs/current-metrics.md (unit 1027/3104, integration 210 methods, E2E 72, line counts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2FA-bypass invariant locked in across session/AJAX/client layers (1c42898, codex-approved). Records the Studio-seeded-core method used to run the full integration suite locally despite the throttled wordpress.org download. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(Step 3) The in-editor reauth modal must survive an overnight-open editor tab whose localized grant nonce has aged past the ~24h nonce life. This recovery already holds in production and is now regression-guarded at both layers; tests only, no production change. Server (tests/Integration/StaleNonceRecoveryTest.php, 3 methods): - STALE-01: handle_ajax_refresh_nonce() re-mints a nonce that validates for Challenge::NONCE_ACTION WITHOUT requiring the stale grant nonce (login-gated only) — the property that makes recovery possible. - STALE-02: the refresh endpoint is login-gated (logged-out is rejected). - STALE-03: end-to-end — a stale nonce is rejected by handle_ajax_auth (no session minted), then the freshly-refreshed nonce is accepted and grants. Asserts the recovery AND the failure mode it recovers from. Client (tests/e2e/specs/editor-reauth.spec.ts, EDITOR-09): - Poison the localized grant nonce; submit()'s refreshNonce() re-mints it before postPassword(), so the grant still succeeds and re-dispatches. Also asserts the localized nonce was replaced, so a still-valid original cannot explain the pass. C1 preserved: recovery re-mints the same single wp_sudo_challenge CSRF action; no second nonce action is introduced. Gates: unit 1027, lint 22/22, analyse (PHPStan + Psalm) 0 errors, verify:metrics in sync, integration 218/218, E2E editor-reauth 9/9 (Studio, WP 7.0.1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tep 4, Q3)
The in-editor apiFetch middleware shows one grant modal for concurrent
sudo_required rejections (single-flight), but on a grant EVERY waiting caller
re-dispatched its own request via wp.apiFetch(options) — replaying background /
secondary gated requests the user never actioned at that moment. For an
intent-confirmation plugin that is a surprise mutation: the session legitimately
authorizes it, but the user never confirmed it.
Make re-dispatch owner-scoped:
- requestGrant() captures isOwner = !pendingGrant synchronously per-caller (the
caller that opens the modal owns the grant; single-threaded JS makes the
check-and-set race-free) and returns { granted, isOwner } so ownership rides
each caller's own continuation, not the shared promise's resolution value.
- handleSudoRequired() re-dispatches only for result.granted && result.isOwner.
A granted non-owner returns undefined with no surface() — the session is now
active (nothing to link out) and the request was not user-actioned, so it is
left rejected with its ORIGINAL error and self-heals on a natural retry. The
not-granted path (2FA / cancel) still surfaces the link-out to every caller.
- Header, pendingGrant, and requestGrant() docstrings updated to the owner-scoped
contract (they previously promised "all re-dispatch").
Design: Pre-Implementation Design Review endorsed owner-only over replay-all —
under-replay fails safe and self-heals; over-replay fires an unconfirmed mutation.
Tests (tests/e2e/specs/editor-reauth.spec.ts):
- EDITOR-10: two concurrent gated requests share one modal; on grant only the
owner re-dispatches and resolves, the non-owner stays rejected:sudo_required.
Verified RED on the old replay-all code (non-owner resolved), GREEN after.
- EDITOR-11: cancelling a shared modal links out and rejects every concurrent
caller (guards the shared not-granted fallback).
Clears the Q3 (codex #4) Step 8 un-draft blocker.
Gates: unit 1027, lint 22/22, analyse (PHPStan) 0 errors, verify:metrics in sync
(E2E 73→75), editor-reauth E2E 11/11 (Studio, WP 7.0.1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The in-editor reauth UI must never echo the action-specific rule label (Q4): a
generic prompt cannot be socially engineered around a specific action and does
not disclose what the user was doing. This already holds in production; Step 5
makes it a regression-guarded guarantee at both halves. Tests only.
- Server boundary (tests/Integration/RestGatingTest.php): a cookie-auth
sudo_required error's human MESSAGE carries the label ("Delete plugin", so the
label is available server-side), but the structured data the editor consumes
(rule_id = machine id plugin.delete, challenge_url) never embeds it. Proves the
editor's generic copy is a deliberate suppression, not an accident, and guards
against a future leak of the label into client-consumed error data.
- Client (tests/e2e/specs/editor-reauth.spec.ts EDITOR-12): the grant modal body
is generic ("reauthentication") and never contains the plugin.activate label
tokens ("activate"/"plugin") nor the server message's keyboard-shortcut hint.
Complements EDITOR-01 (snackbar surface).
Headless (C4) needed no new server test: intercept_rest()'s app-password/bearer
branch never returns sudo_required and never carries challenge_url (it returns
sudo_blocked/sudo_disabled or passes through) — already asserted with
assertArrayNotHasKey('challenge_url') by the existing RestGatingTest app-password
tests. Cited rather than duplicated.
Gates: unit 1027, lint 22/22, analyse (PHPStan) 0 errors, verify:metrics in sync
(integration 214 methods, E2E 76), integration 219/219, editor-reauth E2E 12/12.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…matrix (Step 6) Step 6's automatable half. New docs/password-manager-compatibility.md records a factual, source-cited markup audit of both reauth surfaces (full-page challenge and the in-editor createElement modal): both already expose the standards-minimal password-autofill set — real <form>, type=password, autocomplete="current-password", associated <label>, submit. The full-page TOTP one-time-code token is provided by the Two Factor plugin's own rendering; the modal has no 2FA step yet (Milestone B). The one gap is no autocomplete="username" hint field — documented as a candidate cheap fix but deliberately NOT applied: it changes a security surface for an unverified per-manager payoff, and the repo's rules forbid recording manager behavior without a concrete version + repro. The per-manager × surface × autofill matrix is scaffolded with every cell 'pending' — it requires manual verification with each manager installed (incl. the 1Password-with-2FA repro) and cannot be produced here. Docs only (findings note + research-todo progress + checklist). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 4's owner-scoping edit to admin/js/wp-sudo-editor-reauth.js shifted the source lines of already-extracted strings (e.g. :208→:215, :281→:288). No strings were added or removed — regenerating languages/wp-sudo.pot updates only the `#:` line-reference comments so `composer verify:i18n` is back in sync. Should have ridden with the Step 4 commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Steps 5 (Q4) and 6 (autofill markup audit) recorded; Step 7 gate results logged — integration 219/0 single-site AND multisite, PHPStan+Psalm/lint/metrics/i18n green, security-scoped C1-C4 review clean (two LOW residuals, no code change). Full/multisite E2E deferred to CI per maintainer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
753ce24 to
289b53a
Compare
Milestone A un-drafted — ready for reviewThe in-editor reauth password path is complete. This turn landed Steps 3–7 of the execution checklist:
The Remaining pre-merge (not blockers to review, but before merge)
2FA accounts link out to the full challenge page (password-only in-editor for now); the 2FA-bypass invariant is enforced and regression-guarded at all three layers. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 289b53a20b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( res.ok && '2fa_pending' === res.code ) { | ||
| // 2FA is not handled in-modal yet (Task 4) — link out. | ||
| props.resolve( false ); |
There was a problem hiding this comment.
Avoid double-prompting 2FA users before link-out
For accounts with 2FA enabled, this branch runs only after the user has already entered a correct password in the modal, then falls back to the full challenge. The full challenge page still starts on the password form and keeps the 2FA step hidden until its own password AJAX returns 2fa_pending (includes/class-challenge.php:371, revealed by admin/js/wp-sudo-challenge.js only after another password submit), so these users must enter their password twice before reaching 2FA. That regresses the previous link-out path for any 2FA account; either skip the modal for known-2FA users or have the challenge page consume the existing pending state directly.
Useful? React with 👍 / 👎.
…ocs-lint) The docs-lint gate (.github/workflows/docs-lint.yml) forbids YYYY-MM-DD fixed-date references in user-facing docs. This file cited the research todo by its dated filename (.planning/todos/pending/2026-07-05-...md), and the 2026-07-05 tripped the gate. Reference the todo generically (the item under .planning/todos/pending/) instead; the todo itself is under .planning/, which the gate excludes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dknauss
left a comment
There was a problem hiding this comment.
Final review: no blocking findings from my side.
What changed since the last review:
- Fixed the docs-lint blocker on
docs/password-manager-compatibility.mdin commit94b992e. - Added a manual password-manager verification protocol plus a focused 1Password + Two Factor repro target, without recording unverified manager behavior as fact.
Validation:
- Local
composer test:unit: pass, 1027 tests / 3104 assertions. - Local
composer lint: pass. - Local
composer analyse: pass. - PR CI on head
94b992e: all checks pass, including all four wp-env E2E shards andchallenge-replay-multisite.
Remaining-items take:
- I would not dispatch extra release-confidence or Nginx/MariaDB multisite E2E solely for this PR. The relevant PR wp-env E2E and multisite integration coverage are green; reserve the heavier workflow for release-candidate confidence.
- The password-manager matrix is now scaffolded for human-local verification. I did not inspect or automate vault contents; those observations should be filled from a real local browser profile.
- Q3 owner=request assumption is not a merge blocker. Worst case remains UX or request semantics, not auth bypass. A good follow-up would encode a per-request owner/queue so only the initiating request is retried after grant.
Approved from my side.
|
Final review: no blocking findings from my side. What changed since the last review:
Validation:
Remaining-items take:
Approved from my side. |
Summary
25c2e72) that was built, manually demoed in Playground, then parked so 4.6.0 could ship only the safe Increment 1 (link-out) + Increment 2 (server grant plumbing). This PR revives it as a tracked, reviewable work item and adds the milestone plan.sudo_requiredREST rejection inside the block editor, anapiFetchmiddleware opens a single-flightwp.components.Modal(build-free), grants the sudo session via AJAX (session-only, user's ownwp_rest/wp_sudo_challengenonces — C3), then transparently re-dispatches the original request so the action completes without leaving the editor. A stale grant nonce is refreshed first. 2FA / cancel / no-config fall back to the Increment-1 link-out to the full challenge page.2fa_pendingfor 2FA accounts, which the modal bounces to the full challenge). In-modal 2FA is Milestone B. Rationale + full task/gate breakdown:.planning/gutenberg-editor-reauth-milestone-plan.md; technical detail + C1–C4 + security-config in the phase-2 plan.Validation
composer test— not yet run for this milestone. This is a WIP draft, not merge-ready. The modal has no automated verification yet:editor-reauth.spec.tsEDITOR-06 (modal grant) and the superseded snackbar tests aretest.fixme; only code-level review passed.main(branch is 9 behind); de-fixme+ finish EDITOR-06; 2FA-bypass invariant test (a 2FA account's password-only modal call must never grant); stale-nonce recovery test; password-manager/autofill matrix; single-site andWP_MULTISITE=1E2E green; degradation + headless-challenge_url(C4) regression; mandatory security-scoped review of the real diff with the C1–C4 checklist;docs/current-metrics.mdsynced.Checklist
test.fixme(Milestone A work).🤖 Generated with Claude Code
Generated by Claude Code