Les uploads sécurisés exigent des permissions strictes, des limites de taille, des URLs signées courtes et des scans simples pour éviter les incidents.

Les uploads semblent anodins : une photo de profil, un PDF, un tableur. Pourtant, ils sont souvent la première cause d'incident de sécurité parce qu'ils permettent à des inconnus d'envoyer à votre système une boîte mystère. Si vous l'acceptez, la stockez et la montrez à d'autres, vous avez créé une nouvelle surface d'attaque pour votre appli.
Le risque n'est pas seulement « quelqu'un a téléversé un virus ». Un mauvais upload peut divulguer des fichiers privés, faire exploser votre facture de stockage, ou piéger des utilisateurs pour qu'ils donnent leur accès. Un fichier nommé « invoice.pdf » n'est peut‑être pas du tout un PDF. Même de vrais PDF et images peuvent poser problème si votre appli fait confiance aux métadonnées, génère des aperçus automatiquement ou les sert avec de mauvaises règles.
Les échecs réels ressemblent souvent à ceci :
Un détail provoque beaucoup d'incidents : stocker des fichiers n'est pas la même chose que les servir. Le stockage, c'est où vous gardez les octets. Le serving, c'est comment ces octets sont livrés aux navigateurs et applis. Les choses tournent mal quand une appli sert des uploads utilisateurs avec le même niveau de confiance que le site principal, si bien que le navigateur considère l'upload comme « digne de confiance ».
« Assez sûr » pour une petite appli signifie souvent pouvoir répondre sans détour à quatre questions : qui peut téléverser, quoi accepter, quelle taille et quelle fréquence, et qui peut lire ensuite. Même si vous développez vite (code généré ou plateforme pilotée par chat), ces garde‑fous restent essentiels.
Traitez chaque upload comme une entrée non fiable. La manière pratique de garder les uploads sûrs est d'imaginer qui pourrait les abuser et ce que ces gens considèrent comme une réussite.
La plupart des attaquants sont soit des bots qui scannent des formulaires d'upload faibles, soit des utilisateurs réels qui poussent les limites pour obtenir du stockage gratuit, gratter des données ou troller votre service. Parfois c'est un concurrent qui cherche des fuites ou des pannes.
Leur objectif est généralement l'un des suivants :
Puis cartographiez les points faibles. Le point d'upload est la porte d'entrée (fichiers trop gros, formats étranges, taux de requêtes élevés). Le stockage est l'arrière-boutique (buckets publics, mauvaises permissions, dossiers partagés). Les URL de téléchargement sont la sortie (prévisibles, longue durée, ou non liées à un utilisateur).
Exemple : une fonctionnalité « téléversement de CV ». Un bot envoie des milliers de PDF volumineux pour faire grimper les coûts, tandis qu'un utilisateur abusif téléverse un fichier HTML et le partage comme « document » pour tromper d'autres personnes.
Avant d'ajouter des contrôles, décidez ce qui compte le plus pour votre appli : confidentialité (qui peut lire), disponibilité (pouvez‑vous continuer à servir), coût (stockage et bande passante) et conformité (où les données sont stockées et combien de temps). Cette liste de priorités rend les décisions cohérentes.
La plupart des incidents d'upload ne sont pas des hacks sophistiqués. Ce sont de simples bugs « je vois le fichier de quelqu'un d'autre ». Traitez les permissions comme une partie intégrante des uploads, pas comme un truc qu'on rajoute après.
Commencez par une règle : refus par défaut. Considérez chaque objet uploadé comme privé jusqu'à autorisation explicite. « Privé par défaut » est une bonne base pour les factures, dossiers médicaux, documents de compte et tout ce qui est lié à un utilisateur. Rendez publics les fichiers uniquement lorsque l'utilisateur s'y attend clairement (par exemple un avatar public), et même là, pensez à un accès limité dans le temps.
Gardez les rôles simples et séparés. Une découpe courante :
Ne comptez pas sur des règles au niveau dossier comme « tout dans /user-uploads/ est OK ». Vérifiez la propriété ou l'accès du locataire au moment de la lecture, pour chaque fichier. Cela vous protège lorsqu'une personne change d'équipe, quitte une orga, ou qu'un fichier est réaffecté.
Un bon pattern pour le support est étroit et temporaire : accorder l'accès à un fichier spécifique, le journaliser et l'expirer automatiquement.
La plupart des attaques d'upload commencent par une astuce simple : un fichier qui semble sûr à cause de son nom ou d'un en-tête navigateur, mais qui est autre chose. Traitez tout ce que le client envoie comme non fiable.
Commencez par une allowlist : décidez des formats exacts que vous acceptez (par exemple .jpg, .png, .pdf) et rejetez le reste. Évitez « n’importe quelle image » ou « n’importe quel document » sauf si vous en avez vraiment besoin.
Ne faites pas confiance à l'extension du fichier ni à l'en-tête Content-Type envoyé par le client. Les deux sont faciles à falsifier. Un fichier nommé invoice.pdf peut être un exécutable, et Content-Type: image/png peut être un mensonge.
Une approche plus robuste est d'inspecter les premiers octets du fichier, souvent appelés « magic bytes » ou signature de fichier. Beaucoup de formats courants ont des en-têtes constants (comme PNG et JPEG). Si l'en-tête ne correspond pas à ce que vous autorisez, refusez.
Une configuration de validation pratique :
Le renommage compte plus qu'il n'y paraît. Si vous enregistrez les noms fournis par l'utilisateur, vous ouvrez la porte aux astuces de chemin, aux caractères étranges et aux écrasements accidentels. Utilisez un ID généré pour le stockage et conservez le nom d'origine uniquement pour l'affichage.
Pour les photos de profil, n'acceptez que JPEG et PNG, vérifiez les en-têtes et supprimez les métadonnées si possible. Pour les documents, limitez aux PDF et refusez tout contenu actif. Si vous décidez plus tard d'autoriser SVG ou HTML, traitez-les comme potentiellement exécutables et isolez-les.
La plupart des pannes liées aux uploads ne sont pas des attaques sophistiquées. Ce sont des fichiers énormes, trop de requêtes, ou des connexions lentes qui bloquent les serveurs jusqu'à ce que l'appli semble hors service. Considérez chaque octet comme un coût.
Choisissez une taille max par fonctionnalité, pas un unique chiffre global. Un avatar n'a pas besoin de la même limite qu'un document fiscal ou une courte vidéo. Définissez la plus petite limite qui reste raisonnable, puis ajoutez un flux « large upload » séparé seulement si nécessaire.
Appliquez les limites à plusieurs niveaux, car les clients peuvent mentir : dans la logique applicative, au niveau du serveur web ou reverse proxy, avec des timeouts d'upload, et en rejetant tôt quand la taille déclarée est trop grande (avant de lire tout le corps).
Exemple concret : avatars limités à 2 Mo, PDFs limités à 20 Mo, et tout plus grand requiert un autre flux (par ex. upload direct vers l'object storage via URL signée).
Même de petits fichiers peuvent devenir un DoS si quelqu'un les téléverse en boucle. Ajoutez des limites de débit sur les endpoints d'upload par utilisateur et par IP. Pensez des règles plus strictes pour le trafic anonyme que pour les utilisateurs connectés.
Les uploads résumables aident les vrais utilisateurs sur des réseaux instables, mais le token de session doit être strict : courte durée, lié à l'utilisateur et borné à une taille et une destination spécifiques. Sinon l'endpoint « resume » devient un tuyau gratuit vers votre stockage.
Quand vous bloquez un upload, renvoyez des erreurs claires pour l'utilisateur (fichier trop volumineux, trop de requêtes) mais ne divulguez pas d'internes (stack traces, noms de buckets, détails fournisseurs).
La sécurité des uploads ne concerne pas seulement ce que vous acceptez. Elle concerne aussi où le fichier va et comment vous le rendez disponible ensuite.
Écartez les octets d'uploads de votre base de données principale. La plupart des applis n'ont besoin que de métadonnées en BD (ID du propriétaire, nom de fichier original, type détecté, taille, checksum, clé de stockage, date de création). Stockez les octets dans un object storage ou un service de fichiers adapté aux gros blobs.
Séparez fichiers publics et privés au niveau stockage. Utilisez des buckets ou conteneurs différents avec des règles distinctes. Les fichiers publics (comme un avatar public) peuvent être lisibles sans login. Les fichiers privés (contrats, factures, documents médicaux) ne doivent jamais être lisibles publiquement, même si quelqu'un devine l'URL.
Évitez de servir les fichiers utilisateurs depuis le même domaine que votre appli quand vous le pouvez. Si un fichier risqué passe (HTML, SVG avec scripts, ou bizarreries de sniffing MIME), l'héberger sur votre domaine principal peut conduire à une prise de compte. Un domaine de téléchargement dédié (ou domaine de stockage) limite la zone d'impact.
Sur les téléchargements, forcez des en-têtes sûrs. Définissez un Content-Type prévisible basé sur ce que vous autorisez, pas ce que l'utilisateur prétend. Pour tout ce que le navigateur pourrait interpréter, préférez l'envoi en téléchargement.
Quelques choix par défaut qui évitent les mauvaises surprises :
Content-Disposition: attachment pour les documents.Content-Type sûr (ou application/octet-stream).La rétention est aussi de la sécurité. Supprimez les uploads abandonnés, retirez les anciennes versions après remplacement, et imposez des durées pour les fichiers temporaires. Moins de données stockées, moins de risque de fuite.
Les URLs signées (pre-signed URLs) sont un moyen courant de laisser les utilisateurs uploader ou télécharger sans rendre votre bucket public et sans faire transiter chaque octet par votre API. L'URL transporte une permission temporaire, puis expire.
Deux flux courants :
Le direct-to-storage réduit la charge API, mais rend les règles de stockage et les contraintes d'URL plus importantes.
Traitez une URL signée comme une clé à usage restreint. Rendez‑la spécifique et de courte durée.
Un pattern pratique : créez d'abord un enregistrement d'upload (status: pending), puis émettez l'URL signée. Après l'upload, confirmez que l'objet existe et correspond à la taille et au type attendus avant de marquer prêt.
Un flux d'upload sûr, c'est surtout des règles claires et un état explicite. Traitez chaque upload comme non fiable tant que les vérifications ne sont pas terminées.
Écrivez ce que chaque fonctionnalité autorise. Une photo de profil et un document fiscal ne devraient pas partager les mêmes types de fichiers, limites de taille ou visibilité.
Définissez les types autorisés et une limite de taille par fonctionnalité (par exemple : photos jusqu'à 5 Mo ; PDFs jusqu'à 20 Mo). Faites respecter les mêmes règles côté backend.
Créez un « enregistrement d'upload » avant l'arrivée des octets. Stockez : propriétaire (utilisateur ou org), usage (avatar, facture, pièce jointe), nom de fichier original, taille max attendue, et un statut comme pending.
Uploadez dans un emplacement privé. Ne laissez pas le client choisir le chemin final.
Validez à nouveau côté serveur : taille, magic bytes/type, allowlist. Si tout passe, passez le statut à uploaded.
Scannez pour malware et mettez à jour le statut en clean ou quarantined. Si le scan est asynchrone, gardez l'accès verrouillé en attendant.
Autorisez le téléchargement, l'aperçu ou le traitement seulement quand le statut est clean.
Petit exemple : pour une photo de profil, créez un enregistrement lié à l'utilisateur et au but avatar, stockez en privé, confirmez que c'est bien JPEG/PNG (pas seulement par nom), scannez, puis générez une URL de prévisualisation.
Le scan anti-malware est un filet de sécurité, pas une promesse. Il détecte les fichiers connus et les astuces évidentes, mais ne trouvera pas tout. Le but est simple : réduire le risque et rendre les fichiers inconnus inoffensifs par défaut.
Un patron fiable est « quarantaine d'abord ». Sauvez chaque nouvel upload dans un emplacement privé et marquez‑le comme en attente. Ce n'est qu'après les contrôles que vous le déplacez en « propre » (ou marquez disponible).
Les scans synchrones ne fonctionnent que pour de petits fichiers et peu de trafic, car l'utilisateur attend. La plupart des applis scannent de façon asynchrone : acceptez l'upload, renvoyez un état « traitement », scannez en arrière‑plan.
Le scan basique est typiquement un moteur antivirus (ou service) plus quelques garde‑fous : scan AV, vérifications de type de fichier (magic bytes), limites d'archives (zip bombs, zips imbriqués, taille décompressée énorme), et blocage des formats non nécessaires.
Si le scanner échoue, expire ou renvoie « inconnu », traitez le fichier comme suspect. Gardez‑le en quarantaine et ne fournissez pas de lien de téléchargement. C'est là que les équipes se brûlent : « scan failed » ne doit pas se transformer en « on publie quand même ».
Quand vous bloquez un fichier, gardez le message neutre : « Nous n'avons pas pu accepter ce fichier. Essayez un autre fichier ou contactez le support. » Ne dites pas que vous avez détecté un malware sauf si vous en êtes sûr.
Considérez deux fonctionnalités : une photo de profil (affichée publiquement) et un reçu PDF (privé, utilisé pour la facturation ou le support). Ce sont deux problèmes d'upload, mais ils ne doivent pas partager les mêmes règles.
Pour la photo de profil, restez strict : n'acceptez que JPEG/PNG, limitez la taille (par ex. 2–5 Mo), et réencodez côté serveur pour ne pas servir les octets originaux de l'utilisateur. Stockez en public uniquement après vérifications.
Pour le reçu PDF, acceptez une taille plus grande (par ex. jusqu'à 20 Mo), gardez‑le privé par défaut, et évitez de le rendre inline depuis votre domaine principal.
Un modèle simple de statuts garde les utilisateurs informés sans exposer les détails internes :
Les URLs signées s'intègrent bien ici : utilisez une URL signée courte pour l'upload (write-only, une clé d'objet). Émettez une URL signée de lecture séparée et courte, et seulement quand le statut est clean.
Journalisez ce dont vous avez besoin pour les enquêtes, pas le fichier lui‑même : ID utilisateur, ID fichier, type estimé, taille, clé de stockage, horodatages, résultat du scan, IDs de requête. Évitez de logger le contenu brut ou des données sensibles trouvées dans les documents.
La plupart des bugs d'upload viennent d'un petit raccourci « temporaire » qui devient permanent. Assumez que chaque fichier est non fiable, que chaque URL sera partagée, et que chaque réglage « on le fera plus tard » sera oublié.
Les pièges récurrents :
Content-Type, permettant au navigateur d'interpréter du contenu risqué.La surveillance est ce que les équipes sautent jusqu'à ce que la facture de stockage explose. Suivez le volume d'uploads, la taille moyenne, les plus gros téléverseurs et les taux d'erreur. Un compte compromis peut téléverser des milliers de gros fichiers en une nuit.
Exemple : une équipe stocke des avatars avec des noms fournis par l'utilisateur comme « avatar.png » dans un dossier partagé. Un utilisateur écrase les images des autres. La correction est ennuyeuse mais efficace : générez des clés d'objet côté serveur, gardez les uploads privés par défaut, et exposez une image redimensionnée via une réponse contrôlée.
Utilisez ceci comme passe final avant mise en production. Traitez chaque élément comme un blocage de release, car la plupart des incidents proviennent d'un garde‑fou manquant.
Content-Type prévisible, noms de fichiers sûrs et attachment pour les documents.Écrivez vos règles en langage simple : types autorisés, tailles max, qui peut accéder à quoi, combien de temps vivent les signed URLs, et ce que signifie « scan passé ». Cela devient le contrat partagé entre produit, ingénierie et support.
Ajoutez quelques tests qui attrapent les échecs communs : fichiers trop gros, exécutables renommés, lectures non autorisées, signed URLs expirées, et téléchargements « scan pending ». Ces tests sont peu coûteux comparés à un incident.
Si vous développez et itérez vite, utilisez un workflow qui permet de planifier les changements et de revenir en arrière en sécurité. Les équipes utilisant Koder.ai (koder.ai) s'appuient souvent sur le mode planning et les snapshots/rollback en resserrant les règles d'upload au fil du temps, mais l'exigence principale reste la même : la politique doit être appliquée côté backend, pas par l'UI.
Commencez par privé par défaut et traitez chaque upload comme une entrée non fiable. Faites respecter quatre principes côté serveur :
Si vous pouvez répondre clairement à ces points, vous êtes déjà mieux protégé que la plupart des projets.
Parce que les utilisateurs peuvent téléverser une « boîte mystère » que votre appli stocke et pourrait ensuite servir à d'autres. Cela peut conduire à :
Ce n’est généralement pas juste « quelqu’un a téléversé un virus ».
Le stockage, c’est conserver des octets. La diffusion (serving), c’est la manière dont ces octets sont livrés aux navigateurs et aux applis.
Le problème arrive quand votre appli sert des uploads utilisateurs avec le même niveau de confiance et les mêmes règles que le site principal. Si un fichier risqué est traité comme une page normale, le navigateur peut l’exécuter (ou les utilisateurs lui feront confiance).
Un comportement plus sûr : stocker en privé, puis servir via des réponses contrôlées avec des en-têtes sûrs.
Utilisez refus par défaut et vérifiez l’accès à chaque fois qu’un fichier est téléchargé ou prévisualisé.
Règles pratiques :
Ne faites pas confiance à l’extension de nom de fichier ni à l’en-tête Content-Type du navigateur. Validez côté serveur :
Les pannes viennent souvent d’abus ennuyeux : trop d’uploads, fichiers gigantesques, ou connexions lentes qui épuisent les ressources.
Bonnes pratiques :
Considérez chaque octet comme un coût et chaque requête comme un risque potentiel.
Oui, mais prudemment. Les signed URLs permettent au navigateur d’uploader/télécharger directement du stockage sans rendre le bucket public.
Bonnes pratiques par défaut :
Le direct-to-storage réduit la charge API, mais la portée et l’expiration sont non négociables.
Le schéma le plus sûr :
pendingLe scan aide, mais ce n’est pas une garantie. Utilisez-le comme un filet de sécurité, pas comme seul contrôle.
Approche pratique :
La règle clé : « non scanné » ne doit jamais signifier « disponible ».
Servez les fichiers de manière à empêcher le navigateur de les interpréter comme des pages web.
Bonnes pratiques :
La plupart des bugs réels sont de simples erreurs « je peux voir le fichier d’un autre utilisateur ».
Si les octets ne correspondent pas à un format autorisé, refusez l’upload.
clean ou quarantinedcleanCela empêche qu’un fichier « scan failed » ou « en cours » soit partagé par erreur.
Content-Disposition: attachmentContent-Type sûr choisi par le serveur (ou application/octet-stream)Cela réduit le risque qu’un fichier uploadé devienne une page d’hameçonnage ou exécute du script.