fix(security): verifier les fichiers televerses et retirer un intent Discord

SEC-021 -- rien ne validait le contrat signe
upload_signed_contract se contentait d un nom de fichier non vide.
ALLOWED_SIGNED_EXTENSIONS etait declaree juste a cote et jamais lue. Le
fichier atterrissait sur le disque sous un nom que download_signed_contract
sert ensuite : ce qu un joueur televerse est ce qu un gerant ouvre.

Le nom seul ne suffisait pas non plus cote upload_contract, qui verifiait
`.pdf` en fin de chaine -- payload.pdf ne dit rien des octets.

pdf_upload_error() couvre les deux routes : extension dans la liste, puis
signature %PDF- en tete de flux. Le flux est rembobine, l appelant
enregistre toujours le fichier entier.

OPS-014 -- intent Discord privilegie inutile
Le bot demandait GUILD_MEMBERS et ne s en servait pas : rien n enumere ni
ne recherche de membre de serveur, les personnes sont jointes par le
discord_user_id enregistre sur leur compte. Retire.

message_content reste : on_raw_reaction_add lit le texte de la reponse d un
coach pour consigner un motif de refus.

CI-003 et CI-005 sont deja appliques (permissions: contents: read,
checkout@v4, exclusions de deploiement). L epinglage par SHA des actions
n est pas fait : ce sont des actions GitHub de premiere partie, et
l epingler sans Dependabot echange une exposition contre une autre.

11 tests, dont deux verifient que signer un contrat marche toujours et
qu un autre joueur ne peut pas le faire.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-08 15:09:13 -04:00
co-authored by Claude Opus 5
parent 835a394d3f
commit 20158a9e7a
7 changed files with 365 additions and 181 deletions
+9 -2
View File
@@ -42,14 +42,21 @@ class TeamTryoutsBot(commands.Bot):
"""
def __init__(self, flask_app=None):
# Only what the bot actually reads. `members` — the privileged
# GUILD_MEMBERS intent — was requested and never used: nothing here
# lists or looks up guild members, the bot reaches people through
# the discord_user_id stored on their account (OPS-014).
#
# `message_content` stays: on_raw_reaction_add reads the text of a
# coach's reply to record a refusal note.
intents = Intents.default()
intents.message_content = True
intents.dm_messages = True
intents.dm_reactions = True
intents.reactions = True
intents.guilds = True
intents.members = True
super().__init__(command_prefix='!', intents=intents)
self.flask_app = flask_app
self.pending_requests = {} # Maps message_id to {type, id} for reaction handling