• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleedingdeacons / reach / 34297150596
86%

Build:
DEFAULT BRANCH: main
Ran 09 Sep 2026 01:01AM UTC
Jobs 1
Files 92
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

09 Sep 2026 12:56AM UTC coverage: 86.416% (+0.05%) from 86.366%
34297150596

push

github

web-flow
fix: throttle the OAuth surface and floor the JWKS cache bust (#114)

OAuthController was the only unauthenticated controller in Reach taking no
RateLimiter at all — DeviceAuthController, PasswordAuthController,
NearestMembersController and AlertController all do. Two costs were
unbounded as a result.

/oauth/start and /oauth/apple/start each write a StateStore transient with a
10-minute TTL, and expired transients are only swept by a daily cron, so
wp_options grew without limit in between.

Worse, /oauth/apple takes a state (one free request to mint) and an
id_token. JwtVerifier::verify() reads the header's kid and, on a miss,
retries with forceRefresh — which delete_transient()s the cached JWKS and
refetches. A token carrying a random kid therefore dropped Apple's key set
from cache and forced an outbound fetch, on every request. Held cold from
outside, that puts a 5-second-timeout HTTPS call on the critical path of
every genuine Apple sign-in and makes the site an unauthenticated request
amplifier pointed at the provider.

Both halves are fixed here because either alone leaves the other reachable:
the throttle bounds how often the bust can be attempted, and the floor makes
each attempt cheap.

The four unauthenticated routes now share one per-IP bucket. One bucket
rather than four because they are steps of the same flow and an attacker
free to spend a fresh allowance on each would pick the cheapest. Sign-out is
deliberately excluded: it is cookie-authenticated and CSRF-checked, and
refusing to let someone sign out defends nothing.

The cap is deliberately generous. RateLimiter takes the client IP from
REMOTE_ADDR only, which behind a CDN is the edge's address rather than the
visitor's, so a whole intergroup can share a bucket; 120 per quarter hour is
far past real usage from one edge while still bounding a flood hard.

findKey() now gates its cache bust on a per-JWKS-URL floor, so an unknown
kid triggers at most one refetch a minute however many ar... (continued)

6260 of 7244 relevant lines covered (86.42%)

14.99 hits per line

Coverage Regressions

Lines Coverage ∆ File
16
90.23
1.0% reach/reach/src/Rest/OAuthController.php
3
96.8
0.16% reach/reach/src/Auth/JwtVerifier.php
Jobs
ID Job ID Ran Files Coverage
1 34297150596.1 09 Sep 2026 01:01AM UTC 92
86.42
GitHub Action Run
Source Files on build 34297150596
  • Tree
  • List 92
  • Changed 3
  • Source Changed 0
  • Coverage Changed 3
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • Github Actions Build #34297150596
  • 703c6784 on github
  • Prev Build on main (#34297137128)
  • Next Build on main (#34299400352)
  • Delete
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc