name: Bug report
title: "[Bug] POST /leaderboard/update is unauthenticated — scores can be gamed by anyone"
labels: bug, security
What happened
POST /leaderboard/update accepts any github_username and any is_pr_merged: true payload without any authentication or verification. There is nothing preventing a user from sending arbitrary POST requests to inflate their own (or any other contributor's) leaderboard score.
The leaderboard is designed to reward real contributors who close issues and merge PRs, but right now the data is fully user-controlled and untrustworthy.
Steps to reproduce
- Start the backend.
- Run the following command:
curl -X POST http://localhost:8000/leaderboard/update \
-H "Content-Type: application/json" \
-d '{"github_username": "any_username", "pr_description": "Closes #1 Closes #2 Closes #3", "fixes_passed": 100, "is_pr_merged": true}'
- Call
GET /leaderboard — any_username now has a fabricated score of 305 points.
- Repeat step 2 as many times as you want. There is no rate limit, no auth, and no validation against real GitHub PR data.
Expected behaviour
Only a trusted, verifiable caller can write to the leaderboard. The recommended options (pick one):
Option A — GitHub Actions webhook (recommended): The endpoint requires an X-Hub-Signature-256 HMAC header signed with a LEADERBOARD_WEBHOOK_SECRET environment variable, matching the standard GitHub webhook payload format. Only GitHub's servers (triggered by a merged PR event) can post valid signatures.
Option B — Existing API key guard: Apply the same HTTPBearer authentication dependency used by other sensitive endpoints (introduced in PR #295) so that only callers with the server's API key can update the leaderboard.
Actual behaviour
Relevant code — backend/app/main.py, lines 1068–1091:
@app.post("/leaderboard/update")
async def update_leaderboard_endpoint(req: LeaderboardUpdateRequest):
# No authentication check whatsoever
pattern = r"(?i)(?:close|closes|...)...\s+#(\d+)"
matches = re.findall(pattern, req.pr_description)
findings_closed = len(set(matches))
...
await upsert_contributor_stat(...)
return {"status": "success", ...}
Environment
| Field |
Value |
| OS |
Any |
| Python version |
3.10+ |
| PatchPilot version / commit |
main |
Logs
INFO: 127.0.0.1 - "POST /leaderboard/update HTTP/1.1" 200 OK
No warning, no auth failure — the score is silently written.
Additional context
Option B implementation sketch (simpler, pairs with existing security infra):
from app.utils.security import verify_api_key # or however PR #295 exposed the dependency
@app.post("/leaderboard/update")
async def update_leaderboard_endpoint(
req: LeaderboardUpdateRequest,
_: str = Depends(verify_api_key), # ← add this
):
...
Option A requires reading the raw request body to verify the HMAC before Pydantic parses it — slightly more involved but fully automated when triggered by a GitHub webhook.
Acceptance criteria:
name: Bug report
title: "[Bug] POST /leaderboard/update is unauthenticated — scores can be gamed by anyone"
labels: bug, security
What happened
POST /leaderboard/updateaccepts anygithub_usernameand anyis_pr_merged: truepayload without any authentication or verification. There is nothing preventing a user from sending arbitrary POST requests to inflate their own (or any other contributor's) leaderboard score.The leaderboard is designed to reward real contributors who close issues and merge PRs, but right now the data is fully user-controlled and untrustworthy.
Steps to reproduce
GET /leaderboard—any_usernamenow has a fabricated score of 305 points.Expected behaviour
Only a trusted, verifiable caller can write to the leaderboard. The recommended options (pick one):
Option A — GitHub Actions webhook (recommended): The endpoint requires an
X-Hub-Signature-256HMAC header signed with aLEADERBOARD_WEBHOOK_SECRETenvironment variable, matching the standard GitHub webhook payload format. Only GitHub's servers (triggered by a merged PR event) can post valid signatures.Option B — Existing API key guard: Apply the same
HTTPBearerauthentication dependency used by other sensitive endpoints (introduced in PR #295) so that only callers with the server's API key can update the leaderboard.Actual behaviour
Relevant code —
backend/app/main.py, lines 1068–1091:Environment
mainLogs
No warning, no auth failure — the score is silently written.
Additional context
Option B implementation sketch (simpler, pairs with existing security infra):
Option A requires reading the raw request body to verify the HMAC before Pydantic parses it — slightly more involved but fully automated when triggered by a GitHub webhook.
Acceptance criteria:
POST /leaderboard/updaterequest returns401 Unauthorized.tests/test_leaderboard_security.pyasserts401for missing/invalid credentials.backend/README.md.