Skip to content

release: v1.8.15 -- atomic rate limiter + attribution length cap (security hardening) - #64

Merged
Atroci merged 1 commit into
mainfrom
agent-v1-8-15-hardening
Aug 16, 2026
Merged

release: v1.8.15 -- atomic rate limiter + attribution length cap (security hardening)#64
Atroci merged 1 commit into
mainfrom
agent-v1-8-15-hardening

Conversation

@Atroci

@Atroci Atroci commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

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").

  1. Atomic rate limiter (includes/api/traits/trait-tracking-controller-security.php, check_rate_limit()): when a persistent object cache is present, the counter is now seeded with wp_cache_add() and bumped with the atomic wp_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. If wp_cache_incr() returns false (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 pattern includes/tracking/class-webhook-auth.php's verify_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.
  2. Length cap on classic attribution capture (includes/Core/class-attribution-provider.php, sanitize()): each UTM/click-ID field is now truncated to 255 chars right after sanitize_text_field() and the {{...}} ad-platform-macro rejection check, before assignment — mirroring the 128-char cap includes/api/traits/trait-tracking-controller-attribution-token.php's sanitize_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 the ct_attribution cookie, session storage, and WooCommerce order meta.

Also: version bump to 1.8.15 (clicutcl.php), changelog entries in changelog.txt and readme.txt (readme's Stable tag intentionally 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.php for 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 the sanitize_text_field/sanitize_key stubs 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.php hardcodes wp_using_ext_object_cache() to always return false and stubs no wp_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 in Webhook_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 in check_rate_limit() is exercised only indirectly (through the real Tracking_Controller, which isn't instantiated in this suite either); adding bootstrap-level wp_cache_* stubs and a get_option-backed Tracking_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 vizuh org level (confirmed via the dependency-graph/sbom API 404ing). That's an org-owner settings fix, not a code fix, and unrelated to this PR's content.

🤖 Generated with Claude Code

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>
@Atroci
Atroci merged commit 3bbed3a into main Aug 16, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant