Un lien à usage unique qui se détruit sur une simple requête GET meurt dès que Slack, Teams ou une passerelle de messagerie le récupère pour afficher un aperçu. La solution : ne renvoyer que des métadonnées sur GET et ne détruire le secret que lors d’un affichage explicite par POST, avec en dernier rempart une phrase de passe envoyée par un second canal. Cet article passe en revue les systèmes qui récupèrent les liens sans rien demander et explique comment concevoir un outil qui leur résiste à tous.
Le générateur de liens secrets à usage unique de LinkPilot fonctionne ainsi : une récupération pour aperçu n’obtient que la structure de la page ; le secret est récupéré, et l’ouverture comptée, quand un navigateur exécute la page.
Le mode de défaillance par défaut
La plupart des clients de messagerie instantanée et d’e-mail essaient d’afficher un aperçu dès qu’ils voient une URL. Le déroulement est le suivant :
- L’utilisateur colle le lien secret dans Slack.
- L’outil d’aperçu de liens de Slack récupère l’URL (il envoie un
GET; Slack documente ce comportement dans sa page sur le link unfurling). - Votre gestionnaire d’affichage traite la requête comme une ouverture humaine, détruit le secret et renvoie le contenu.
- Slack reçoit le contenu détruit (ou une page générique) et en affiche l’aperçu.
- La personne clique sur le lien, voit « déjà consulté » et demande un nouveau lien à l’expéditeur.
L’outil d’aperçu de liens n’avait aucune intention malveillante. C’est votre gestionnaire qui a confondu « quelqu’un a récupéré l’URL » et « le destinataire a lu le secret ». Ce sont deux événements différents, et tout le problème de conception consiste à les séparer.
Une variante plus grave se cache à l’étape 3 : si le GET de l’outil d’aperçu de liens a reçu le vrai contenu, le secret se trouve désormais dans l’infrastructure de génération d’aperçus d’un tiers. Dans le meilleur des cas, il a été supprimé une fois l’aperçu généré, mais vous n’avez aucun moyen de le confirmer, et aucun événement d’audit ne vous signale qu’il a été reçu.
Qui récupère un lien avant que le destinataire ne l’ouvre ?
Les outils d’aperçu de liens des messageries, les passerelles de sécurité des e-mails, la protection des postes de travail, les proxys d’entreprise et les extensions d’aperçu, et en général plusieurs d’entre eux à la fois. Dire « Slack affiche des aperçus » sous-estime le problème. Une URL collée dans un environnement de travail moderne est en général récupérée par plusieurs systèmes avant qu’un humain ne clique dessus :
| Système | Quand il intervient | Identifiable ? |
|---|---|---|
| Outils d’aperçu de liens des messageries (Slack, Teams, iMessage, WhatsApp, LinkedIn) | Dès l’envoi du message | En général : des user agents distincts, comme Slackbot-LinkExpanding |
| Passerelles de sécurité des e-mails (Mimecast, Proofpoint, Microsoft Defender) | À la réception, et souvent de nouveau au moment du clic | Souvent non : beaucoup se présentent avec le user agent d’un vrai navigateur |
| Protection des postes de travail / antivirus | Quand l’URL arrive sur la machine du destinataire | Rarement |
| Proxys web d’entreprise | Au moment du clic, avant que la requête de l’utilisateur ne soit transmise | Rarement |
| Extensions de navigateur d’aperçu de liens | Au survol ou au chargement de la page | Non : elles utilisent la session de navigation de l’utilisateur lui-même |
Le comportement des éditeurs décrit dans ce tableau évolue sans cesse : considérez chaque affirmation sur une passerelle précise comme « telle que publiée en juillet 2026, à vérifier dans la documentation actuelle ». Le point commun, lui, ne change pas : tous ces systèmes envoient un simple GET, car c’est la seule façon sûre et universelle de consulter une URL que vous ne contrôlez pas.
Comment empêcher un aperçu de détruire un lien à usage unique ?
Servez des métadonnées sur GET et ne détruisez le secret que sur un POST explicite. La sémantique HTTP le prévoit déjà : la RFC 9110, section 9.2.1 définit GET comme une méthode sûre et idempotente, c’est-à-dire une requête que n’importe qui peut envoyer librement parce qu’elle n’est censée rien modifier. POST est le verbe de l’action. Traitez donc l’affichage comme une action POST derrière un bouton visible :
GET /s/abc123 → HTML page: "Click to reveal this secret"
POST /s/abc123 → burns the secret, returns the payload
Le GET de Slack renvoie désormais une page de métadonnées neutre. Le secret n’est détruit que lorsque la personne clique sur le bouton. Tout scanner qui respecte la sémantique HTTP (presque tous, puisque envoyer des POST à effets de bord vers des URL arbitraires casserait la moitié du web) ne touche jamais au contenu.
Un exemple pas à pas
Suivons un lien du début à la fin. Vous créez un secret avec destruction après lecture et une phrase de passe, puis vous collez l’URL dans un message privé Slack et dans un e-mail.
- L’outil d’aperçu de liens de Slack envoie
GET /s/abc123. Réponse : une petite page de métadonnées (statut « active », phrase de passe requise). Aucune ouverture enregistrée. Le secret est intact. - La passerelle de messagerie du destinataire réécrit l’URL dans l’e-mail et la récupère à la réception pour en analyser la réputation. Même
GET, mêmes métadonnées. Toujours intact. - Le destinataire clique sur le lien reçu par e-mail. Au moment du clic, le scanner de la passerelle récupère l’URL une fois de plus (
GET), puis redirige le navigateur vers sa destination. - Le navigateur charge la page d’affichage, avec un nouveau
GET. La page montre un bouton d’affichage et un champ pour la phrase de passe. Toujours aucune ouverture enregistrée. - Le destinataire saisit la phrase de passe et clique sur le bouton d’affichage. Le navigateur envoie
POST /s/abc123. L’ouverture est enregistrée, le contenu est renvoyé, le secret est détruit. - Quiconque récupère l’URL ensuite obtient une réponse explicite « déjà consulté et détruit » au lieu du contenu.
Cinq récupérations, un seul affichage. Ce rapport est normal en entreprise : une conception qui ne le tolère pas échouera dès le premier jour, et l’expéditeur finira par coller le mot de passe dans la messagerie.
Des défenses en couches
La séparation GET / POST
Elle couvre tous les outils d’aperçu de liens et scanners qui respectent les règles, soit l’immense majorité du trafic automatisé. C’est la base ; tout ce qui suit vient la renforcer.
Phrase de passe obligatoire
Elle couvre ce que la séparation ne couvre pas : les bacs à sable d’analyse qui exécutent du JavaScript et interagissent avec la page, ainsi que les clics humains accidentels et les e-mails transférés. Un bac à sable d’analyse peut cliquer sur un bouton d’affichage ; il ne peut pas fournir une phrase de passe transmise par un autre canal. Envoyez le lien par e-mail et la phrase de passe par message privé Slack ou par SMS.
Filtrage des robots par user agent
Écartez les robots d’aperçu connus (Slackbot, LinkedInBot, Twitterbot) avant qu’ils n’atteignent la logique d’affichage. C’est une précaution supplémentaire utile, mais jamais suffisante seule : les scanners de sécurité se présentent couramment avec le user agent d’un vrai navigateur, justement pour que les pages malveillantes ne puissent pas se dissimuler à l’analyse.
Expiration courte
Limitez la fenêtre d’exposition. Un lien qui n’a vécu que quinze minutes laisse dans les scanners, les fichiers journaux et l’historique des messageries une URL qui ne mène plus à rien.
Cache-Control: no-store
Définissez cet en-tête sur chaque réponse d’affichage pour qu’aucun intermédiaire (proxy d’entreprise, CDN, cache du navigateur) ne conserve une copie du contenu après l’unique lecture légitime.
Le cas des passerelles de sécurité des e-mails
Les passerelles d’entreprise méritent une section à part, car elles récupèrent le lien deux fois. Les produits de cette catégorie (Microsoft Defender Safe Links en est l’exemple documenté) réécrivent chaque URL des e-mails entrants pour la faire passer par leur propre domaine d’analyse. L’URL est en général récupérée une première fois à la réception pour l’analyse de réputation, puis de nouveau au moment du clic, avant que la personne ne soit redirigée vers la vraie destination.
Si l’une ou l’autre de ces récupérations détruisait le secret, le destinataire verrait toujours un lien mort : une défaillance qui paraît aléatoire aux utilisateurs, mais qui est entièrement déterministe. La séparation GET/POST absorbe d’office les deux récupérations, puisque ce sont deux simples GET. La redirection au moment du clic amène ensuite la personne sur la page d’affichage, où a lieu le POST.
Les cas limites qui posent encore problème
- Les bacs à sable qui exécutent du JavaScript. Certains environnements d’analyse affichent la page et interagissent avec elle. Un
POSTderrière un bouton sans phrase de passe pourrait, en théorie, être déclenché par un bac à sable agressif. La phrase de passe est le dernier rempart. - Lien et phrase de passe sur le même canal. S’ils voyagent dans le même e-mail, toutes les défenses ci-dessus se réduisent à « espérer que personne ne lise cet e-mail ». Séparez toujours les canaux.
- Les transferts. Chaque transfert vers une nouvelle messagerie ou un nouveau système d’e-mail déclenche une nouvelle série d’aperçus. Sans conséquence avec la séparation, mais chaque étape ajoute des journaux qui contiennent l’URL, ce qui plaide pour une expiration courte.
- Les affichages simultanés. Si deux personnes cliquent sur le bouton d’affichage au même moment, la lecture et la destruction doivent se faire en une seule étape indivisible : une seule requête l’emporte et l’autre reçoit « déjà consulté ». Une implémentation qui sépare ces deux opérations peut servir le contenu deux fois.
Testez votre propre outil
Avant de confier de vrais identifiants à un service de partage de secrets, faites ce contrôle de deux minutes :
- Créez un secret de test avec la destruction après lecture activée.
- Récupérez-le depuis un terminal, exactement comme le fait un outil d’aperçu de liens :
curl -s -o /dev/null -w "%{http_code}" "<url>" - Ouvrez le lien dans un navigateur. Si vous voyez « déjà consulté », l’outil détruit le secret sur
GETet il est dangereux de coller ses liens dans une messagerie ou un e-mail. - Si le secret a survécu, affichez-le normalement dans le navigateur, puis relancez
curl: vous devez maintenant obtenir une réponse explicite indiquant que le secret n’existe plus ou a été détruit, et non le contenu.
Comment LinkPilot met cela en œuvre
Pour mémoire, voici la conception qu’utilisent les liens secrets de LinkPilot : un GET sur l’endpoint d’affichage ne renvoie que des métadonnées (le statut, et si une phrase de passe est requise) ; une ouverture n’est enregistrée que lors d’un affichage explicite par POST. Les phrases de passe sont hachées en SHA-256 côté client et limitées à cinq tentatives par IP, par minute et par secret. Chaque réponse d’affichage porte l’en-tête Cache-Control: no-store, les secrets détruits ou expirés renvoient le code HTTP 410 et leur contenu stocké est écrasé, et l’historique d’audit enregistre chaque événement avec une IP hachée à l’aide d’un sel renouvelé chaque jour : l’IP brute n’est jamais stockée. Tous les détails figurent sur la page Architecture de sécurité.
Essai gratuit, sans inscription
Message éphémère
Envoyez une note qui disparaît après une seule lecture.
Les limites de cette conception
Soyons clairs sur ses limites : rien de ce qui précède ne protège un secret après un affichage légitime. Le destinataire peut copier le contenu, en faire une capture d’écran ou le transférer. Cette conception n’aide pas non plus si la phrase de passe voyage avec le lien. Et elle ne peut pas empêcher une personne qui a reçu le lien par erreur de l’ouvrir avant le bon destinataire : c’est à cela que sert le journal d’audit. Vous voyez l’ouverture inattendue, et vous renouvelez l’identifiant.
À lire aussi
- Comment fonctionnent les liens secrets à usage unique
- Pourquoi les URL divulguent des informations sensibles
- Architecture de sécurité de LinkPilot
Références
- IETF, RFC 9110: HTTP Semantics, section 9.2.1 Safe Methods.
- Slack API, Unfurling links in messages.
- Microsoft Learn, Safe Links in Microsoft Defender for Office 365.
