Skip to main content

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. TrustedProxyClientIpResolver now walks X-Forwarded-For right-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. nginx proxy_add_x_forwarded_for) the previous behavior let a client spoof its IP — defeating IpAllowlistMiddleware, forging rate-limit buckets, and falsifying audit IPs. When every hop is trusted, resolution falls back to REMOTE_ADDR. See Trusted proxy.
  • WpClaimsUserMapper no longer maps email/login claims by default. The email/login claim lists are now empty; only user_id-style id claims and an explicit $customResolver map by default. When email mapping is enabled it requires a truthy email_verified claim ($requireEmailVerified, default true). 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. CorsPolicy throws if allowedOrigins contains * together with allowCredentials: true. See CORS.
  • Granted-scope wildcards are opt-in. JwtAuthMiddleware / BearerTokenAuthMiddleware now treat token-supplied (granted) scopes as literals; a trailing * on a granted scope is honored only when allowGrantedScopeWildcards: true. Server-defined required-scope wildcards are unchanged.
  • Optional HMAC query-string signing. HmacSignatureMiddleware can include the canonicalized (key-sorted) query string in the signed canonical via signQueryString: true. Query params remain unauthenticated by default (documented on the constructor). See HMAC signatures.
  • WpCacheSingleUseTokenStore requires a persistent object cache. It now throws in the constructor when no persistent object cache is present, because the wp_cache_add consume-lock is not cross-request atomic otherwise. Use WpdbSingleUseTokenStore on installs without Redis/Memcached. See Single-use tokens.
  • Idempotency stores restrict unserialize(). WpdbIdempotencyStore and WpdbAtomicIdempotencyStore restrict deserialization to the library's own Response class, removing a latent object-injection sink.
  • JWKS fetch is bounded. HttpJwksProvider uses wp_safe_remote_get() with bounded redirects and response size (keeping the existing HTTPS + sslverify enforcement).
  • The error envelope no longer leaks internals. Uncaught throwables return a generic 400/500 body with no exception class name or raw message; intentional ApiException detail is preserved. See Request, response, and errors.

Correctness

  • No _doing_it_wrong on unfiltered table lists. WpdbAdapter no longer calls $wpdb->prepare() on the binding-less COUNT query (the identifiers are already validated and backtick-quoted).
  • 428 Precondition Required. A missing optimistic-lock precondition now throws PreconditionRequiredException (428) instead of 412; 412 is reserved for a precondition that was supplied but failed. See Optimistic locking.
  • Caching skips error responses. CachingMiddleware no 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 null return from wp_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_id are 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 given product_id. See Orders.
  • Order/product search works. ?search= on orders and products now uses the supported s query var (previously an unsupported search var was silently ignored, returning unfiltered results). See Orders and Products.
  • Customer delete works in REST. wp-admin/includes/user.php is loaded before wp_delete_user(), so DELETE /customers/{id} actually deletes instead of silently failing. See Customers.
  • Line-item edits are rejected on stock-reduced orders. PUT/PATCH /orders/{id} with line_items now returns 409 woo_line_items_locked on 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. HposGuard throws 503 hpos_required (was 409), and product routes no longer require HPOS (products are not moved by HPOS). See Configuration.
  • Product price is read-only. price is a derived field in WooCommerce; set regular_price / sale_price instead. price remains readable.
  • Woo validation errors are 400. WC_Data_Exception thrown by WooCommerce CRUD setters (invalid email, discount type, etc.) maps to 400 instead of 500.
  • Product price sort removed. WooCommerce's product query does not reliably order by price; sort=price is no longer advertised (order total sort is retained and verified on HPOS).
  • Coupon code handling. The ?code= filter and create both resolve through wc_get_coupon_id_by_code() (normalization + cache), and create rejects a duplicate code with 409 coupon_exists. See Coupons.
  • Customer list is no longer an N+1. orders_count / total_spent are 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 declare custom_order_tables compatibility on before_woocommerce_init. See Configuration.

Tooling

  • php-stubs/woocommerce-stubs bumped 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::VERSION1.0.0.

Files added

  • src/Http/PreconditionRequiredException.php428 Precondition Required exception.
  • 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:

ChangeAction
WpClaimsUserMapper email/login mapping is off by defaultIf 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 defaultIf your issuer grants hierarchical wildcard scopes (e.g. orders:*), set allowGrantedScopeWildcards: true. Required-scope wildcards are unaffected
CorsPolicy(['*'], allowCredentials: true) now throwsList explicit origins when credentials are enabled
WpCacheSingleUseTokenStore throws without a persistent object cacheUse WpdbSingleUseTokenStore on default hosting
Monetary response fields are now stringsUpdate 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 removedSort by a supported field; sort=price now returns 400
Line-item edits on stock-reduced orders return 409Edit 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).