v1.0.0
First stable release. Released on the main branch as Composer tag v1.0.0.
Upgrade summary
{
"require": {
"better-route/better-route": "^1.0"
}
}
1.0.0 consolidates the 0.3–0.6 development line and adds a pre-1.0 hardening pass across security, correctness, and the WooCommerce integration layer. Everything below is verified by the test suite (135 tests), PHPStan, and a live WordPress 7.0 / WooCommerce 10.9 HPOS install.
Unlike 0.6.0 (which was purely additive), 1.0.0 contains a small number of intentional behavior changes — all of them fail-safe hardening. Read the Behavior change checklist before upgrading.
Security
- Trusted-proxy client IP resolution is spoof-resistant.
TrustedProxyClientIpResolvernow walksX-Forwarded-Forright-to-left and skips trusted-proxy hops, returning the closest untrusted address, instead of trusting the (client-controllable) leftmost entry. With an appending proxy (e.g. nginxproxy_add_x_forwarded_for) the previous behavior let a client spoof its IP — defeatingIpAllowlistMiddleware, forging rate-limit buckets, and falsifying audit IPs. When every hop is trusted, resolution falls back toREMOTE_ADDR. See Trusted proxy. WpClaimsUserMapperno longer mapsemail/loginclaims by default. Theemail/loginclaim lists are now empty; onlyuser_id-style id claims and an explicit$customResolvermap by default. When email mapping is enabled it requires a truthyemail_verifiedclaim ($requireEmailVerified, defaulttrue). This closes an account-takeover vector where a validly signed token from a third-party issuer carrying a victim's (unverified) email would log the caller in as that WordPress user. See JWT and Bearer.- CORS wildcard + credentials is rejected at construction.
CorsPolicythrows ifallowedOriginscontains*together withallowCredentials: true. See CORS. - Granted-scope wildcards are opt-in.
JwtAuthMiddleware/BearerTokenAuthMiddlewarenow treat token-supplied (granted) scopes as literals; a trailing*on a granted scope is honored only whenallowGrantedScopeWildcards: true. Server-defined required-scope wildcards are unchanged. - Optional HMAC query-string signing.
HmacSignatureMiddlewarecan include the canonicalized (key-sorted) query string in the signed canonical viasignQueryString: true. Query params remain unauthenticated by default (documented on the constructor). See HMAC signatures. WpCacheSingleUseTokenStorerequires a persistent object cache. It now throws in the constructor when no persistent object cache is present, because thewp_cache_addconsume-lock is not cross-request atomic otherwise. UseWpdbSingleUseTokenStoreon installs without Redis/Memcached. See Single-use tokens.- Idempotency stores restrict
unserialize().WpdbIdempotencyStoreandWpdbAtomicIdempotencyStorerestrict deserialization to the library's ownResponseclass, removing a latent object-injection sink. - JWKS fetch is bounded.
HttpJwksProvideruseswp_safe_remote_get()with bounded redirects and response size (keeping the existing HTTPS +sslverifyenforcement). - The error envelope no longer leaks internals. Uncaught throwables return a generic
400/500body with no exception class name or raw message; intentionalApiExceptiondetail is preserved. See Request, response, and errors.
Correctness
- No
_doing_it_wrongon unfiltered table lists.WpdbAdapterno longer calls$wpdb->prepare()on the binding-lessCOUNTquery (the identifiers are already validated and backtick-quoted). 428 Precondition Required. A missing optimistic-lock precondition now throwsPreconditionRequiredException(428) instead of412;412is reserved for a precondition that was supplied but failed. See Optimistic locking.- Caching skips error responses.
CachingMiddlewareno longer caches non-2xx responses, and its identity-aware cache key requires the auth middleware to run before it (documented on the class). - CPT delete null handling. A
nullreturn fromwp_delete_post()/wp_trash_post()is treated as failure. - Single-use token salt derives from the documented
wp_salt('auth')scheme.
WooCommerce
- Variation line items are priced correctly. Order line items that reference a
variation_idare now built from the actual variation product, not the parent, so price, name, and attributes are correct. The variation is validated to belong to the givenproduct_id. See Orders. - Order/product search works.
?search=on orders and products now uses the supportedsquery var (previously an unsupportedsearchvar was silently ignored, returning unfiltered results). See Orders and Products. - Customer delete works in REST.
wp-admin/includes/user.phpis loaded beforewp_delete_user(), soDELETE /customers/{id}actually deletes instead of silently failing. See Customers. - Line-item edits are rejected on stock-reduced orders.
PUT/PATCH /orders/{id}withline_itemsnow returns409 woo_line_items_lockedon an order that has already reduced stock, preventing silent inventory corruption. Edit items before stock reduction, or adjust the order through status transitions. - Monetary values are decimal strings. Order/line-item/coupon/customer money fields are serialized as strings (matching WooCommerce's own REST API and avoiding float drift); the OpenAPI money types are aligned to
string. - HPOS unavailability is
503.HposGuardthrows503 hpos_required(was409), and product routes no longer require HPOS (products are not moved by HPOS). See Configuration. - Product
priceis read-only.priceis a derived field in WooCommerce; setregular_price/sale_priceinstead.priceremains readable. - Woo validation errors are
400.WC_Data_Exceptionthrown by WooCommerce CRUD setters (invalid email, discount type, etc.) maps to400instead of500. - Product
pricesort removed. WooCommerce's product query does not reliably order by price;sort=priceis no longer advertised (ordertotalsort is retained and verified on HPOS). - Coupon code handling. The
?code=filter and create both resolve throughwc_get_coupon_id_by_code()(normalization + cache), and create rejects a duplicate code with409 coupon_exists. See Coupons. - Customer list is no longer an N+1.
orders_count/total_spentare excluded from the default list fields (each is a per-customer order query); request them explicitly with?fields=. - HPOS compatibility helper.
HposGuard::declareCompatibility(__FILE__)lets a host plugin declarecustom_order_tablescompatibility onbefore_woocommerce_init. See Configuration.
Tooling
php-stubs/woocommerce-stubsbumped to^10.9(WordPress stubs stay at^6.9, capped by the WooCommerce stubs' dependency).- Added
tests/RegressionFixesTest.php(11 tests; 135 total). Support\Version::VERSION→1.0.0.
Files added
src/Http/PreconditionRequiredException.php—428 Precondition Requiredexception.tests/RegressionFixesTest.php— regression coverage for the security/correctness fixes.
Behavior change checklist
1.0.0 is safe to adopt, but these consumer-visible changes are intentional:
| Change | Action |
|---|---|
WpClaimsUserMapper email/login mapping is off by default | If you relied on email/login mapping, pass emailClaims/loginClaims explicitly and ensure the issuer sets a trustworthy email_verified claim (or use a user_id claim / custom resolver) |
Granted-scope trailing-* wildcards are off by default | If your issuer grants hierarchical wildcard scopes (e.g. orders:*), set allowGrantedScopeWildcards: true. Required-scope wildcards are unaffected |
CorsPolicy(['*'], allowCredentials: true) now throws | List explicit origins when credentials are enabled |
WpCacheSingleUseTokenStore throws without a persistent object cache | Use WpdbSingleUseTokenStore on default hosting |
| Monetary response fields are now strings | Update clients that expected JSON numbers for money |
HPOS-unavailable is 503 (was 409) | Update error handling / retry logic keyed on the status |
Missing optimistic-lock precondition is 428 (was 412) | Update precondition-required handling |
Product price is not writable (send regular_price/sale_price) | Sending price now returns 400 validation_failed |
Product sort=price is removed | Sort by a supported field; sort=price now returns 400 |
Line-item edits on stock-reduced orders return 409 | Edit line items only before stock reduction |
Woo CRUD setter errors are 400 (was 500) | No action needed; more accurate status |
Compared to 0.6.0
- 0.6.0 added the identity-boundary primitives (JWKS, HMAC, trusted-proxy IP, single-use tokens) as purely additive features.
- 1.0.0 hardens the defaults of those primitives against realistic misconfiguration (XFF spoofing, unverified-email account takeover, wildcard-with-credentials CORS, non-atomic single-use consumption) and fixes correctness/semantics bugs in the WooCommerce layer (variation pricing, search, stock, money serialization, HPOS scoping).