From 45c0521f2ae04e0e32da056dc9286fe1c05e12d7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:17:58 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20missing=20auth=20on=20migrate-formats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: The `/auth/migrate-formats` endpoint was publicly accessible without authentication. 🔧 Fix: Added `X-Admin-Secret` header validation using `secrets.compare_digest`. ✅ Verification: Added backend tests verifying 403/200 responses. Co-authored-by: Horus0305 <98160215+Horus0305@users.noreply.github.com> --- CricketGame/backend/api/auth.py | 14 ++++++++++++-- CricketGame/backend/core/config.py | 6 ++++++ CricketGame/backend/test_auth.py | 29 +++++++++++++++++++++++++++++ cricket.db | Bin 0 -> 176128 bytes 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 CricketGame/backend/test_auth.py create mode 100644 cricket.db diff --git a/CricketGame/backend/api/auth.py b/CricketGame/backend/api/auth.py index dd97d4b..d748cf7 100644 --- a/CricketGame/backend/api/auth.py +++ b/CricketGame/backend/api/auth.py @@ -1,9 +1,11 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Header, status from sqlalchemy.orm import Session from pydantic import BaseModel +import secrets import json from ..data.database import get_db +from ..core.config import ADMIN_SECRET from ..core.auth import register_player, login_player, get_player_stats, decode_token, create_token from ..data.models import Player, MatchHistory, TournamentHistory, FormatStats @@ -62,7 +64,15 @@ def _merge_format_stats(keeper: FormatStats, src: FormatStats) -> None: keeper.best_bowling_runs = src.best_bowling_runs @router.get("/migrate-formats") -def migrate_formats(db: Session = Depends(get_db)): +def migrate_formats( + x_admin_secret: str = Header(..., alias="X-Admin-Secret"), + db: Session = Depends(get_db), +): + if not secrets.compare_digest(x_admin_secret, ADMIN_SECRET): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Invalid admin secret" + ) + stats_merged_2v2 = 0 stats_deduped = 0 legacy_rows = db.query(FormatStats).filter(FormatStats.format == "2v2").all() diff --git a/CricketGame/backend/core/config.py b/CricketGame/backend/core/config.py index e36c09d..256d7a2 100644 --- a/CricketGame/backend/core/config.py +++ b/CricketGame/backend/core/config.py @@ -11,6 +11,12 @@ raise ValueError("SECRET_KEY is required in production") SECRET_KEY = "cricket-dev-key-2026" +ADMIN_SECRET = os.getenv("ADMIN_SECRET") +if not ADMIN_SECRET: + if APP_ENV in ("production", "prod"): + raise ValueError("ADMIN_SECRET is required in production") + ADMIN_SECRET = "super-secret-admin-key" + ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440")) diff --git a/CricketGame/backend/test_auth.py b/CricketGame/backend/test_auth.py new file mode 100644 index 0000000..e156c6b --- /dev/null +++ b/CricketGame/backend/test_auth.py @@ -0,0 +1,29 @@ +from fastapi.testclient import TestClient +from backend.main import app +from backend.core.config import ADMIN_SECRET +from backend.data.database import init_db + +# Initialize DB for tests +init_db() + +client = TestClient(app) + +def test_migrate_formats_no_header(): + # Expect 422 because the header is required (Header(...)) + response = client.get("/auth/migrate-formats") + assert response.status_code == 422 + +def test_migrate_formats_wrong_header(): + # Expect 403 because the secret is invalid + response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": "wrong-secret"}) + assert response.status_code == 403 + assert response.json()["detail"] == "Invalid admin secret" + +def test_migrate_formats_correct_header(): + # Expect 200 because the secret matches + response = client.get("/auth/migrate-formats", headers={"X-Admin-Secret": ADMIN_SECRET}) + assert response.status_code == 200 + data = response.json() + assert "format_stats_2v2_merged" in data + assert "duplicate_rows_removed" in data + assert "match_history_fixed" in data diff --git a/cricket.db b/cricket.db new file mode 100644 index 0000000000000000000000000000000000000000..be81601dca7d20970cc3990b26846002646ddeac GIT binary patch literal 176128 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