release: v1.8.15 -- atomic rate limiter + attribution length cap (security hardening) - #64
Merged
Merged
Conversation
Closes both items deferred from the 1.8.14 security review: - check_rate_limit() now uses wp_cache_add()/wp_cache_incr() for an atomic increment-then-check under a persistent object cache, matching the pattern already used by Webhook_Auth::verify_request(). Closes the read-then-write race that let concurrent requests from the same IP both pass on the same pre-increment count. Non-object-cache transient fallback is unchanged (documented best-effort tradeoff). - Attribution_Provider::sanitize() now truncates each UTM/click-ID field to 255 chars, mirroring the 128-char cap already enforced on the /attribution-token/sign path, before persistence into the ct_attribution cookie, session storage, and WooCommerce order meta. Adds regression coverage for the attribution truncation in BoundarySecurityTest.php. The rate limiter's wp_cache_* branch can't be exercised by the existing PHPUnit bootstrap, which hardcodes wp_using_ext_object_cache() to false and stubs no wp_cache_* functions (same reason the existing atomic webhook-replay branch has zero coverage today) — noted in the PR description rather than skipped silently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships both items deferred from the 1.8.14 security review (
docs/PLAN-2026-08-16-security-hardening-and-repo-cleanup.md, "Deferred — tracked for next").includes/api/traits/trait-tracking-controller-security.php,check_rate_limit()): when a persistent object cache is present, the counter is now seeded withwp_cache_add()and bumped with the atomicwp_cache_incr(), comparing the returned count against the limit instead of a separately-read value — closing the read-then-write race where two concurrent requests from the same IP in the same window could both read the same pre-increment count and both pass. Ifwp_cache_incr()returnsfalse(key evicted between the add and the incr), it reseeds to 1 and treats it as the first hit of a new window. This mirrors the atomic-claim patternincludes/tracking/class-webhook-auth.php'sverify_request()already uses for replay protection. The non-object-cache transient read-then-write fallback is unchanged — still a documented best-effort tradeoff for a soft abuse limit.includes/Core/class-attribution-provider.php,sanitize()): each UTM/click-ID field is now truncated to 255 chars right aftersanitize_text_field()and the{{...}}ad-platform-macro rejection check, before assignment — mirroring the 128-char capincludes/api/traits/trait-tracking-controller-attribution-token.php'ssanitize_attribution_token_data()already enforces on the signed cross-domain token payload. 255 (not 128) because these are general UTM/campaign fields, not the click-ID-heavy token payload the tighter cap was tuned for. This bounds what gets persisted into thect_attributioncookie, session storage, and WooCommerce order meta.Also: version bump to 1.8.15 (
clicutcl.php), changelog entries inchangelog.txtandreadme.txt(readme'sStable tagintentionally stays at 1.8.13 — GitHub is now 2 releases ahead of WP.org, per the 3-version buffer policy), and the plan doc's Deferred section marked shipped for both items.Tests
Added two regression tests to
tests/unit/BoundarySecurityTest.phpfor the attribution length cap (Attribution_Provider::sanitize()truncates a 400-char field to 255 and leaves short values untouched) — this is pure-PHP logic with no WordPress runtime dependency beyond thesanitize_text_field/sanitize_keystubs the suite already provides.Not covered by a test, and why: the rate limiter's
wp_cache_add()/wp_cache_incr()atomic branch can't be exercised by the current PHPUnit bootstrap.tests/bootstrap.phphardcodeswp_using_ext_object_cache()to always returnfalseand stubs nowp_cache_*functions at all, so that code path is unreachable from unit tests as the suite is set up today — same reason the existing atomic webhook-replay branch inWebhook_Auth::verify_request()(which this PR's rate limiter deliberately mirrors) also has zero test coverage right now. The non-object-cache transient fallback path incheck_rate_limit()is exercised only indirectly (through the realTracking_Controller, which isn't instantiated in this suite either); adding bootstrap-levelwp_cache_*stubs and aget_option-backedTracking_Settings::get()stub to make this testable felt out of scope for this PR, per the instruction not to try to force a live-WP-style test harness the sandbox doesn't have.CI note
CI will likely still show the pre-existing "Dependency Review" failure also seen on PR #62 and #63 — Dependency graph isn't enabled at the
vizuhorg level (confirmed via thedependency-graph/sbomAPI 404ing). That's an org-owner settings fix, not a code fix, and unrelated to this PR's content.🤖 Generated with Claude Code