Skip to content

[WOOMOB-3799] Verify cookie authentication endpoints before sending credentials (1/3) - #16396

Open
hichamboushaba wants to merge 4 commits into
trunkfrom
issue/WOOMOB-3799-cookie-nonce-endpoints
Open

[WOOMOB-3799] Verify cookie authentication endpoints before sending credentials (1/3)#16396
hichamboushaba wants to merge 4 commits into
trunkfrom
issue/WOOMOB-3799-cookie-nonce-endpoints

Conversation

@hichamboushaba

@hichamboushaba hichamboushaba commented Aug 9, 2026

Copy link
Copy Markdown
Member

Description

Fixes WOOMOB-3799

Part 1 of 3. Followed by #16397 and #16398.

Note

The PR is a bit big, but the bulk is just tests.

This is one direct-site-credentials fix split into three PRs for review; the parts are not intended to ship independently. Merge order is 1 → 2 → 3, once the whole stack is approved.

Cookie/nonce authentication assumed that every store exposed /wp-login.php and /wp-admin/. It also posted credentials without first establishing that the destination was a WordPress login form. That prevents login on stores where a security plugin moves either endpoint and makes redirect handling too permissive for a credential-bearing transaction.

This PR adds a validated endpoint model containing the canonical store origin plus optional login-entry and admin-base overrides, then rebuilds nonce acquisition around it.

flowchart LR
    A["GET login entry<br/>up to 3 safe redirects"] --> B{"Verified WordPress<br/>login form?"}
    B -->|No| X["Fail before sending credentials"]
    B -->|Yes| C["POST credentials to the<br/>validated form action"]
    B -->|Already signed in at<br/>the expected admin base| D
    C -->|Redirect must exactly match<br/>the derived nonce URL| D["GET REST nonce<br/>without following redirects"]
Loading

The important constraints are:

  • Every supplied or redirected URL must stay on the canonical host, contain no user information, preserve the approved port, and never downgrade HTTPS. A default-port HTTP origin may upgrade to HTTPS on port 443.
  • The login preflight accepts exactly one core-compatible loginform, with one enabled username field and one enabled password field. Its form action is resolved against the final preflight URL and validated before use.
  • The credential POST remains deliberately small: log, pwd, and redirect_to only.
  • The successful credential redirect must equal the nonce URL derived from the validated admin base. A same-origin lookalike is classified as a custom-admin failure but is never followed or persisted.
  • WPS Hide Login's already-authenticated /login/wp-admin/ path is handled without posting credentials to the dashboard; a valid nonce still has to prove the session.
  • Nonce cache identity now normalizes terminal slashes while continuing to isolate usernames, subdirectories, schemes, hosts, and ports.
  • Nonce failures record whether the login entry had already been verified. PR2 and PR3 use that provenance to distinguish “wrong login address” from a later credential, network, or nonce failure.

The form check intentionally fails closed. A store whose theme or plugin substantially changes WordPress's form identity or field semantics may be rejected rather than receiving credentials. Rejection is not limited to the login form itself: malformed form markup elsewhere on the page, or a comment/script marker that survives stripping, rejects the page with the same non-diagnostic invalid-response result. Supporting interactive 2FA, CAPTCHA, SSO, or arbitrary custom form markup is outside this stack.

This PR also introduces optional authenticated-dashboard verification for a supplied admin base; the user-facing recovery flow activates it in PR3. QR login, XML-RPC, and Application Password architecture are unchanged.

Review map:

  • CookieNonceAuthenticationEndpoints.kt: endpoint validation and same-origin policy.
  • NonceRestClient.kt: preflight, form-action selection, redirect rules, dashboard/nonce checks, and cache identity.
  • Nonce.kt: verified-login-entry provenance.
  • NonceRestClientTest.kt: the safety and compatibility matrix.

Test Steps

This PR has no custom-endpoint UI. Use the top-of-stack (PR3) build for step 4.

  1. Sign in to a self-hosted store that uses the default /wp-login.php and /wp-admin/ paths. Confirm the store opens normally.
  2. Sign out and try again with an incorrect password. Confirm an invalid-credentials error appears and the store does not open.
  3. On a store whose HTTP URL redirects to HTTPS on the same host, enter its http:// address and valid credentials. Confirm login succeeds normally.
  4. Using the PR3 build, sign in to a store with WPS Hide Login enabled. Enter its custom sign-in URL when prompted and confirm login succeeds.

Images/gif

N/A — this PR changes the networking layer; the transaction diagram above is the relevant visual.

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

The stack's single user-facing release note is owned by PR3.

@hichamboushaba hichamboushaba added this to the 25.5 milestone Aug 9, 2026
@hichamboushaba hichamboushaba added type: bug A confirmed bug. category: architecture Related to architecture such as the database, FluxC, Networking, Core Data, etc. category: unit tests Related to unit testing. feature: login Related to any part of the log in or sign in flow, or authentication. labels Aug 9, 2026
@dangermattic

dangermattic commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator
1 Error
🚫 This PR is tagged with status: do not merge label(s).
1 Warning
⚠️ This PR is larger than 300 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

@wpmobilebot

wpmobilebot commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number778
Version25.4-rc-1
Application IDcom.woocommerce.android.prealpha
Commit5872a87
Installation URL52d0svt5204s0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@hichamboushaba
hichamboushaba marked this pull request as ready for review August 10, 2026 18:04

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - Found 1 potential issue

This is a well-constructed, security-critical hardening of the cookie-nonce authentication flow. The new preflight GET, strict WordPress-login-form detection (rejecting decoys in comments/scripts/templates, split/duplicate/hidden fields, ambiguous multi-form pages), and same-host / no-downgrade / no-userinfo / exact-port / bounded-redirect checks all combine to ensure credentials are only ever POSTed to a verified same-origin login form. The test suite is comprehensive and covers the important adversarial cases (decoys, unsafe actions/redirects, HTTP→HTTPS upgrade, redirect cap, nonce-URL lookalikes, manual admin recovery).

Backwards compatibility looks good: the existing requestNonce(SiteModel) / requestNonce(siteUrl, username, password) signatures are preserved, and the new loginEntryVerified fields on Nonce.FailedRequest / Nonce.Unknown are additive with defaults. The only feedback is a minor readability nit on a security-sensitive boolean below.


Automatic review · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

@woocommerce woocommerce deleted a comment from claude Bot Aug 10, 2026
@hichamboushaba

Copy link
Copy Markdown
Member Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Code Review - Follow-up

  • Readability nit on a security-sensitive boolean (NonceRestClient / CookieNonceAuthenticationEndpoints): still-open — the authentication code is unchanged since the previous review (this pass was re-triggered via @claude review with no new commits), so the earlier non-blocking style suggestion still stands.

New issues: 0

Re-reviewed the full diff: the preflight-based cookie-nonce hardening remains sound. Credentials are only POSTed to a same-host, no-userinfo, no-downgrade, exact-port target derived from a rendered WordPress login form (with comments/scripts/templates stripped and split/duplicate/hidden/ambiguous forms rejected), the preflight and admin-dashboard redirect chains are bounded, and the post-credential redirect is matched exactly against the derived nonce URL. Backwards compatibility is preserved: the existing requestNonce(SiteModel) / requestNonce(siteUrl, username, password) overloads are intact and the new loginEntryVerified fields on Nonce.FailedRequest / Nonce.Unknown are additive with defaults. The test suite continues to cover the key adversarial cases.


Automatic review (follow-up) · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

@hichamboushaba

Copy link
Copy Markdown
Member Author

I'm not sure what's wrong with Claude, the issue it complains was in previous commit (

"http" -> canonical.port == HTTP_PORT &&
candidate.scheme == "https" && candidate.port == HTTPS_PORT ||
candidate.scheme == "http" && candidate.port == canonical.port
), but it's not present anymore.

@hichamboushaba hichamboushaba added the status: do not merge Dependent on another PR, ready for review but not ready for merge. label Aug 10, 2026
@AdamGrzybkowski AdamGrzybkowski self-assigned this Aug 11, 2026

@AdamGrzybkowski AdamGrzybkowski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! All manuall tests pass ✅

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: architecture Related to architecture such as the database, FluxC, Networking, Core Data, etc. category: unit tests Related to unit testing. feature: login Related to any part of the log in or sign in flow, or authentication. status: do not merge Dependent on another PR, ready for review but not ready for merge. type: bug A confirmed bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants