Skip to content

Commit 50f6da4

Browse files
committed
[FIX] fix security vulnerabilies
1 parent 1a8166b commit 50f6da4

7 files changed

Lines changed: 34 additions & 12 deletions

File tree

backend/app/api/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from __future__ import annotations
1919

20+
import secrets
2021
from typing import Optional
2122

2223
from fastapi import APIRouter, Depends, HTTPException, Query, status
@@ -44,7 +45,7 @@ def _require_admin(
4445
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
4546
detail="Admin endpoints are disabled (ADMIN_TOKEN not set)",
4647
)
47-
if credentials is None or credentials.credentials != token:
48+
if credentials is None or not secrets.compare_digest(credentials.credentials, token):
4849
raise HTTPException(
4950
status_code=status.HTTP_401_UNAUTHORIZED,
5051
detail="Invalid or missing admin token",

backend/app/api/me.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,9 @@ def merge_progress(
9191
current_ids = {row.soldier_id for row in current_rows}
9292
merged_ids = current_ids | set(req.collected_soldier_ids)
9393

94-
for soldier_id in sorted(merged_ids - current_ids):
95-
collection_repo.add_soldier(
96-
db,
97-
user.id,
98-
soldier_id,
99-
source="client_merge",
100-
)
94+
new_ids = sorted(merged_ids - current_ids)
95+
if new_ids:
96+
collection_repo.add_soldiers_batch(db, user.id, new_ids, source="client_merge")
10197

10298
merged_currency = max(user.profile.currency, req.currency)
10399
if user.profile.currency != merged_currency:

backend/app/repositories/collection_repo.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,24 @@ def add_soldier(
6666
db.commit()
6767
db.refresh(entry)
6868
return entry
69+
70+
71+
def add_soldiers_batch(
72+
db: Session,
73+
user_id: str,
74+
soldier_ids: list[int],
75+
source: str = "client_merge",
76+
) -> int:
77+
"""Add multiple soldiers in a single transaction. Returns count added."""
78+
now = _utcnow()
79+
for sid in soldier_ids:
80+
db.add(
81+
UserCollection(
82+
user_id=user_id,
83+
soldier_id=sid,
84+
source=source,
85+
unlocked_at=now,
86+
)
87+
)
88+
db.commit()
89+
return len(soldier_ids)

src/fall_in/core/rules.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,11 @@ def _check_game_end(self) -> None:
322322

323323
if human_eliminated:
324324
# Single-player: human lost — lowest-score AI wins.
325+
human = self.players[self._human_seat] # type-safe: human_eliminated guarantees _human_seat is not None
325326
ai_players = [
326327
p
327328
for p in self.players
328-
if p is not self.players[self._human_seat] and not p.is_eliminated # type: ignore[index]
329+
if p is not human and not p.is_eliminated
329330
]
330331
if ai_players:
331332
self.winner = min(ai_players, key=lambda p: p.penalty_score)

src/fall_in/net/ws_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ async def _recv_loop(self, ws) -> None:
214214
msg = json.loads(raw)
215215
msg_type = msg.get("type", "")
216216
data = msg.get("data") or {}
217+
if not isinstance(data, dict):
218+
data = {}
217219
self._recv_queue.put((msg_type, data))
218220
except Exception as e:
219221
logger.warning("ws_client_parse_error", extra={"error": str(e)})

src/fall_in/ui/settings_popup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import webbrowser
8+
from collections.abc import Callable
89

910
import pygame
1011

@@ -70,10 +71,10 @@ def __init__(self) -> None:
7071
)
7172

7273
# Exit callback and eliminated state (set by owning scene)
73-
self._exit_callback: object = None # Callable[[], None] | None
74+
self._exit_callback: Callable[[], None] | None = None
7475
self._is_eliminated = False
7576

76-
def set_exit_callback(self, callback) -> None:
77+
def set_exit_callback(self, callback: Callable[[], None]) -> None:
7778
"""Set the callback invoked when the user confirms exit."""
7879
self._exit_callback = callback
7980

src/fall_in/utils/debug_overlay.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def handle_debug_event(self, event: pygame.event.Event) -> bool:
109109
self._debug_overlay_active = False
110110
return True
111111

112-
# F12 toggles overlay (only when DEBUG_MODE is on and not in multiplayer)
112+
# F12 toggles overlay (only when DEBUG_MODE is on and not authenticated)
113113
if event.key == pygame.K_F12:
114114
from fall_in.config import DEBUG_MODE
115115
from fall_in.core.game_manager import GameManager

0 commit comments

Comments
 (0)