Skip to content

fix: harden badge endpoints against rate limit exhaustion and username disclosure (GHSA-6c5j-4w43-2v8f #4)#596

Closed
advikdivekar wants to merge 1 commit into
Priyanshu-byte-coder:mainfrom
advikdivekar:fix/ghsa-badge-rate-limit-validation
Closed

fix: harden badge endpoints against rate limit exhaustion and username disclosure (GHSA-6c5j-4w43-2v8f #4)#596
advikdivekar wants to merge 1 commit into
Priyanshu-byte-coder:mainfrom
advikdivekar:fix/ghsa-badge-rate-limit-validation

Conversation

@advikdivekar
Copy link
Copy Markdown
Contributor

Problem

Both badge endpoints (/api/badge/commits and /api/badge/streak-shield) were fully unauthenticated, accepted arbitrary ?user= values with only a 50-character length check, and made direct GitHub API calls consuming the shared GITHUB_TOKEN quota with no rate limiting.

Three concrete issues:

  1. Rate limit exhaustion: An automated script looping over badge endpoints with no auth requirement could exhaust the 5,000 req/hr GITHUB_TOKEN quota in ~83 minutes, taking down the leaderboard, public profiles, and all badge features for every user until the window reset.

  2. Username disclosure via logs: Both routes called console.log for every request — every queried username and its commit/streak count was written to Vercel function logs, creating a persistent record accessible to anyone with log access.

  3. Unvalidated username characters: The only check was a 50-char length limit. Characters like /, ?, +, and # were passed directly into GitHub API URL path and query segments.

Root cause: Badge routes were excluded from the existing rate limiting middleware (matcher only covered /api/metrics/*) and had no format validation.

What changed

src/middleware.ts

  • Extended matcher to ["/api/metrics/:path*", "/api/badge/:path*"]
  • Badge routes are always unauthenticated, so the middleware applies ANONYMOUS_LIMIT (10 req/min per IP) — no other middleware changes needed

src/app/api/badge/commits/route.ts and src/app/api/badge/streak-shield/route.ts

  • Replaced the weak 50-char length check with a strict GitHub username regex: alphanumeric and hyphens, no leading/trailing hyphens, no consecutive hyphens, max 39 characters
  • Removed all console.log statements that disclosed queried usernames and their data
  • Added per-username response caching using cacheGet/cacheSet from @/lib/metrics-cache (TTL 3600s) — repeated requests for the same username within the cache window skip the GitHub API call entirely
  • Cache keys are badge:commits:{username} and badge:streak:{username} — public data, no per-user scoping needed
  • Gracefully falls through to live fetches when Redis is unavailable

How to verify

  1. curl "/api/badge/commits?user=valid-user" → 200 SVG response
  2. curl "/api/badge/commits?user=invalid/user" → 400 JSON error
  3. Send 11 rapid requests from the same IP → 11th returns 429 with Retry-After header
  4. Check Vercel function logs — no username appears in log output
  5. Two requests for the same username within 3600s → second request served from cache (no additional GitHub API call)

Regression check

  • Valid badge embeds: still render correctly
  • Rate limit only affects >10 req/min from the same IP — normal badge embed usage unaffected
  • console.error for actual GitHub API errors: preserved

Fixes GHSA-6c5j-4w43-2v8f vulnerability #4 (Medium).

…sclosure

Both badge routes accepted arbitrary usernames with only a 50-char length
check, made unauthenticated GitHub API calls consuming shared GITHUB_TOKEN
quota with no rate limiting, and logged every queried username to server logs.

- Extend middleware matcher to cover /api/badge/:path* so both routes share
  the existing per-IP rate limiter (10 req/min for anonymous callers)
- Replace weak length check with strict GitHub username regex
  (alphanumeric + hyphens, no leading/trailing hyphens, max 39 chars)
- Remove all console.log statements that disclosed queried usernames
- Add per-username response caching via cacheGet/cacheSet (TTL 3600s) to
  short-circuit repeated API calls for the same username
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

@advikdivekar is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix labels May 21, 2026
@github-actions
Copy link
Copy Markdown

GSSoC Label Checklist 🏷️

@Priyanshu-byte-coder — please apply the appropriate labels before merging:

Difficulty (pick one):

  • level:beginner — 20 pts
  • level:intermediate — 35 pts
  • level:advanced — 55 pts
  • level:critical — 80 pts

Quality (optional):

  • quality:clean — ×1.2 multiplier
  • quality:exceptional — ×1.5 multiplier

Validation (required to score):

  • gssoc:approved — counts for points
  • gssoc:invalid / gssoc:spam / gssoc:ai-slop — does not score

Type labels (type:*) are auto-detected from files and title. Review and adjust if needed.
Points formula: (difficulty × quality_multiplier) + type_bonus

@advikdivekar
Copy link
Copy Markdown
Contributor Author

Closing — patch will be submitted through the private advisory fork (GHSA-6c5j-4w43-2v8f) to avoid public disclosure before coordinated release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc26 GSSoC 2026 contribution type:bug GSSoC type bonus: bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant