fix: add per-IP rate limiting and strict validation to badge endpoints#471
Open
advikdivekar wants to merge 4 commits into
Open
Conversation
- Wrap req.json() in try/catch — returns 400 on malformed JSON - Validate body is a non-null object before destructuring - title: must be non-empty string, trimmed, max 100 characters - target: must be integer in [1, 10000] — blocks 0, negatives, floats, NaN, overflow - unit: clamped to 30 chars silently - recurrence: unknown values default to 'none' explicitly - All bounds defined as named constants Fixes division-by-zero (target:0 → Infinity% progress bar) and negative target making 0-progress goals show as 100% complete. Closes Priyanshu-byte-coder#454
Badge endpoints accepted arbitrary GitHub usernames with no auth, no rate limiting, and only a length check — a single script could exhaust the shared GITHUB_TOKEN quota and DoS the whole platform. Adds a 20 req/min sliding-window rate limit per IP (shared with the existing middleware pattern), replaces the length check with a GitHub-spec username regex, and removes console.log calls that leaked user access patterns to server logs. Fixes Priyanshu-byte-coder#453
|
@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. |
GSSoC Label Checklist 🏷️@Priyanshu-byte-coder — please apply the appropriate labels before merging: Difficulty (pick one):
Quality (optional):
Validation (required to score):
|
Priyanshu-byte-coder
requested changes
May 21, 2026
Owner
Priyanshu-byte-coder
left a comment
There was a problem hiding this comment.
Merge conflict — rebase on main to resolve. The implementation is solid (rate limiting, username regex, badge validation, goals route hardening, .gitignore cleanup) — once conflict is cleared this is ready to merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #453
The badge endpoints (
/api/badge/commitsand/api/badge/streak-shield) accepted any GitHub username with only a length check — noauthentication, no rate limiting. A single script looping thousands of usernames could exhaust the shared
GITHUB_TOKENquota (5,000 req/hr) andtake down the public profile page, leaderboard, and all badge endpoints simultaneously. Access patterns were also leaked unconditionally via
Changes
src/lib/badge-rate-limit.ts(new) — sliding-window, in-memory rate limiter scoped to badge endpoints (20 req/min per IP), mirrors theexisting middleware pattern exactly. Falls back gracefully: unknown IP counts as
"unknown", still rate limited.src/app/api/badge/commits/route.ts— replaces the loose length check withGITHUB_USERNAME_RE(GitHub-spec: 1–39 chars, alphanumeric +hyphens, no leading/trailing hyphen). Adds per-IP rate limit gate before any processing. Returns
429withRetry-After,X-RateLimit-*headerson excess. Removes all
console.logstatements. UpgradesCache-Controltos-maxage=3600, stale-while-revalidate=86400.src/app/api/badge/streak-shield/route.ts— same validation, same rate limit gate, same header cleanup.Test plan
GET /api/badge/commits?user=torvalds→ 200 SVG badgeGET /api/badge/commits?user=-invalid-→ 400{"error":"Invalid username"}GET /api/badge/commits(no param) → 400{"error":"Invalid username"}GET /api/badge/streak-shield?user=torvalds→ 200 SVG badgeGET /api/badge/streak-shield?user=-invalid-→ 400Retry-Afterheaderconsole.logof usernames during any of the aboveVerification summary: All 7 criteria passed live against the running dev server — valid usernames return SVG (200), invalid usernames return 400,
missing param returns 400, and the 20 req/min limit correctly returns 429 with Retry-After/X-RateLimit-* headers on excess. No console.log of
usernames appeared in server output (only console.error for actual GitHub API failures, which is correct).