GHSA-p6v2-xcpg-h6xw is a high-severity (CVSS 7.3) CWE-307 vulnerability in better-auth. A fix is available for better-auth — see the affected versions and patch details below.
Better Auth: Rate limiter keys IPv6 addresses individually and is bypassable via prefix rotation
Exploitation Status
No confirmed exploitation observed yet
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-p6v2-xcpg-h6xw.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-p6v2-xcpg-h6xw plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 379,842 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
better-authnpmDescription
Am I affected?
Users are affected if all of the following are true:
- Their app uses
better-authat a version< 1.4.17, or at a v1.5 prerelease tagged<= 1.5.0-beta.8. - The apps authentication endpoints serve clients reachable over IPv6. Most managed hosts including Cloudflare, Vercel, Fly.io, AWS Application Load Balancer, and Google Cloud Load Balancing advertise IPv6 by default.
- The app's rate-limit configuration is enabled (the production default) and relies on the leftmost
x-forwarded-forvalue (the stock setup) or any other configured IP-bearing header.
If users are on 1.4.16 specifically, the normalizeIP helper exists in your version but the IPv6 prefix length defaults to /128. Stock config still permits prefix rotation because no prefix mask is applied. Either upgrade to 1.4.17 or set advanced.ipAddress.ipv6Subnet: 64 in the config.
If applications do not use the rate limiter, or if the deployment serves only IPv4 clients, the prefix-rotation vector does not apply. The representation-aliasing vector still applies to IPv6 addresses delivered over IPv4 transport in some edge cases (an upstream proxy carrying an IPv4-mapped IPv6 source), but it is rare in practice.
Fix:
- Upgrade to
[email protected]or later. The current stable line1.6.xand the pre-release line1.7.0-betaboth carry the fix. - If applications cannot upgrade, see workarounds below.
Summary
Better Auth's HTTP rate limiter keyed each request by the exact textual IP address it received in x-forwarded-for (or the configured IP-bearing header). IPv6 clients controlling a typical /64 allocation could rotate through 2^64 distinct source addresses without exhausting the per-address counter, defeating rate limiting on /sign-in/email, /sign-up/email, /forget-password, and every other path the limiter protects. The same bug allowed a single client to vary the textual encoding of one IPv6 address (uppercase, compression, IPv4-mapped, hex-encoded IPv4-in-IPv6) and produce multiple distinct keys.
Details
The pre-fix getIp function returned the leftmost x-forwarded-for value verbatim after a single validity check, and onRequestRateLimit constructed the rate-limit key by string concatenation of that value with the request path. Two facts of IPv6 made the key space larger than the population of clients:
- ISPs and cloud providers assign prefixes, not addresses. RFC 6177 recommends
/56for residential users; cloud providers commonly assign/29to/48. An attacker controlling a single/64therefore controls 2^64 source addresses without doing anything unusual. - IPv6 has multiple textual representations for the same address. RFC 5952 specifies a canonical form, but RFC 4291 §2.2 permits the older mixed forms, and
::ffff:0:0/96IPv4-mapped addresses can be written as either dotted-decimal or hex-encoded.
The fix in [email protected] introduces normalizeIP and applies it to every getIp result. Normalization expands compressed IPv6 forms, lowercases hex digits, collapses IPv4-mapped IPv6 to plain IPv4, and applies a default /64 prefix mask. The rate-limit key construction now uses an explicit | separator to prevent key-construction collisions across address-and-path joins.
The /64 default matches the smallest commonly-allocated IPv6 unit, so a single client cannot use prefix rotation to defeat rate limiting on stock config. Operators who serve clients on coarser allocations (/56 for residential ISPs, larger for cloud) can configure advanced.ipAddress.ipv6Subnet accordingly.
Patches
Fixed in [email protected] on the v1.4.x maintenance line and in [email protected] on the v1.5.x line. PR #7470 introduced the normalization primitive (packages/core/src/utils/ip.ts) and applied it to getIp and the rate-limit key. PR #7509 changed the IPv6 prefix-length default from /128 to /64 so that stock config closes the prefix-rotation vector without requiring users to opt in.
After the patch, the rate limiter treats all IPv6 addresses within a /64 allocation as a single client, all textual encodings of one IPv6 address as the same address, and all IPv4-mapped IPv6 addresses as their underlying IPv4 form.
Workarounds
If users cannot upgrade past 1.4.17:
- On
>= 1.4.16: setadvanced.ipAddress.ipv6Subnet: 64in the auth configuration. ThenormalizeIPhelper is present at1.4.16; only the default is wrong. This restores the post-1.4.17behavior on stock config. - On
< 1.4.16: shift the bypass mitigation upstream. Set the IPv6 prefix length on the app's CDN, WAF, or load balancer rate-limit policy to/64(or coarser per RFC 6177 if the app serves residential traffic). Cloudflare, Vercel Firewall, AWS WAF, and Google Cloud Armor all support per-prefix rate limiting. - As a partial mitigation on any version: tighten the
customRuleswindow for sign-in, sign-up, and password-reset endpoints. This narrows the abuse window but does not close it.
Impact
The bypass enables unbounded authentication attempts from a single IPv6-capable client. Direct consequences:
- Credential-stuffing and brute-force on
/sign-in/emailare no longer rate-limited per client. - Account enumeration via response-shape differences becomes faster.
- Password-reset and email-verification email fan-out can be amplified.
The bypass does not directly compromise any account. Successful exploitation still requires the attacker to guess a credential the password store accepts. The rating reflects the loss of one defense-in-depth layer rather than a direct compromise.
Credit
Reported by @nexryai on GitHub.
Resources
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | better-auth | all versions | 1.4.17npm install better-auth@1.4.17 |
| 📦npm | better-auth | ≥ 1.5.0-beta.1&&< 1.5.0-beta.9 | 1.5.0-beta.9npm install better-auth@1.5.0-beta.9 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for better-auth, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update better-auth to 1.4.17 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-p6v2-xcpg-h6xw is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-p6v2-xcpg-h6xw can be triaged on real exposure rather than presence alone.
Tailored to GHSA-p6v2-xcpg-h6xw. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-p6v2-xcpg-h6xw in your dependencies?
O3 Security finds GHSA-p6v2-xcpg-h6xw across npm dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.