v1.6.1 — Security Release: Payment-State Gate on Manual Order Completion
Security release — upgrade recommended for all stores
What was fixed
WooCommerce automatically records any order moved to completed as paid. RouteMile's manual transition paths allowed that to happen with no real payment:
- Manager path: the deliveries dashboard accepted a transition to
completedfor unpaid orders — one form POST (with a valid dashboard nonce) turned a pending order intopaid=1. - Rider path (more serious): an assigned delivery rider could tap Mark Picked Up → Mark Delivered on a prepaid order (bank transfer / cheque) and the order became
completed paid=1with an empty transaction ID — no manager involvement, no gateway call.
Both were reproduced end-to-end in a local penetration test before the fix, and the fix was verified by replaying the exact attacks (now rejected) plus the legitimate flows (now still working).
The fix
All four manual transition endpoints (manager admin-post + AJAX, rider admin-post + AJAX) now call ROUTEW_Order_Lifecycle::can_complete_order(), which refuses the completed transition while the order still needs payment. Unaffected by design:
- gateway-paid orders (
is_paid/date_paidset by the real payment webhook), - cash-on-delivery orders where the rider confirmed cash collection (that confirmation already sets
date_paid), - zero-total orders.
Also in this release
- New PG1 regression suite (8 tests): pins the gate, all four call sites, the legitimate COD pick-up → cash → deliver sequence, and a future-proof scan that fails the build if any new action can set
completedon an unpaid order without the gate. - Full audit of the checkout REST endpoints for cross-origin CSRF: confirmed not exploitable in practice (WooCommerce's session cookie never rides a cross-site POST; a live cross-origin browser experiment left the victim's session and cart untouched). No behaviour change needed.
Quality: test suite now 311 checks — WordPress Plugin Check: 0 errors, 0 warnings on the shipped zip. Requires WP 6.0+, PHP 7.4+, WooCommerce 7.0+.
Install: download routemile-woocommerce.1.6.1.zip below and upload via Plugins → Add New → Upload Plugin (or update in-place from 1.6.0 — no settings migration needed).