Pourquoi un fragment d’URL peut porter une clé, et quand cette protection lâche

Dans la barre d’adresse, le point d’interrogation et le dièse ne sont séparés que d’un caractère. La requête après ? s’écrit dans la ligne HTTP : l’hôte, le proxy inverse et le journal d’accès la voient. Le fragment après # reste pour cet onglet ; par défaut, il ne part pas avec cette requête de document. Placer une clé de déchiffrement après # bloque « l’hôte a vu la clé ». Cela ne bloque pas « quelqu’un a transféré le lien entier ».

D’abord la conclusion : le dièse convient pour une clé parce que le navigateur retire le fragment quand il construit une requête HTTP — pas parce qu’un nouveau protocole de chiffrement serait apparu. Le contrôle consiste à comparer la barre d’adresse à la ligne de requête dans Network. Une fois le lien entier collé dans un chat, un ticket ou l’historique du navigateur, cette couche ne tient plus face à quiconque voit la chaîne.

Répondre à l’intention de recherche en une phrase

Un fragment d’URL peut porter une clé parce que la RFC 9110 exclut le fragment de l’URI cible de la requête. Quand le navigateur demande le document au serveur, il retire # et tout ce qui suit. L’hôte, le CDN et le journal d’accès sont conçus pour voir le chemin et la requête après ? — pas la clé après le dièse.

Cette note s’adresse aux développeurs, à l’exploitation, et à quiconque veut séparer « le serveur ne le voit pas » de « l’historique du chat ne le voit pas » avant d’envoyer un lien à usage unique à un collègue. Elle ne remplace pas le mode d’emploi de Burn-Link, et ne répète pas comment créer un lien. Ce qu’il faut emporter : quelle couche ? et # rejoignent chacun, comment les comparer dans Network, et quelle protection tombe quand l’URL entière est transférée.

Le point d’interrogation entre dans HTTP ; le dièse reste dans l’onglet

Une même adresse peut porter une requête et un fragment. La forme est https://example.com/s.html?id=abc#key. Après le point d’interrogation, id=abc est la query. Après le dièse, key est le fragment. Quand le navigateur analyse la chaîne, le fragment va du premier # jusqu’à la fin. Un dièse littéral plus tôt dans l’URL doit s’écrire %23, sinon l’analyse coupe trop tôt.

Pour la requête de document, le navigateur assemble Host, chemin et query. La note MDN sur le fragment d’URI est nette : le fragment n’est pas envoyé au serveur lors de la demande de ressource ; le client le traite après l’arrivée du document. En HTML, il sert souvent à faire défiler jusqu’à un id. Sur une page de lecture à usage unique, le même emplacement est emprunté pour porter la clé dont cet onglet a besoin pour déchiffrer en local.

Donc « le serveur ne stocke que du texte chiffré » ne tient que si l’identifiant de recherche voyage dans la query, la clé de déchiffrement voyage dans le fragment, et les scripts de la page n’écrivent jamais location.hash dans une requête suivante. Manquez l’une de ces conditions, et le dièse n’est plus qu’un décor.

Les spécifications décrivent un comportement par défaut, pas un chiffrement en plus

La RFC 3986 §3.5 définit le fragment comme identifiant d’une ressource secondaire au-dessus de la ressource principale, interprété par le client selon le type de document. La RFC 9110 §7.1 précise ensuite que l’URI cible exclut tout fragment de la référence, parce que les identifiants de fragment sont réservés au traitement côté client. Lues ensemble : la partie après # ne fait pas partie de la cible HTTP.

C’est pourquoi les journaux d’accès n’ont pas de #. L’exploitation n’a pas caché un champ. La ligne de requête n’a jamais contenu ce segment. Compter « quel titre un utilisateur a cliqué » à partir des logs serveur échoue pour la même raison. Montrer « la clé n’est jamais arrivée au serveur » avec les mêmes logs, en revanche, s’aligne sur la spécification.

Referer suit la même règle. La page MDN sur Referer indique que l’en-tête peut porter l’origine, le chemin et la query, mais ne doit pas inclure un fragment d’URL ni un nom d’utilisateur et un mot de passe. Referrer Policy vide le fragment avant qu’une URL soit emballée dans Referer. Donc, quand la page de lecture charge ensuite une image ou un script, la clé après # n’est pas remise par défaut à un hôte tiers. Ce retrait est le comportement par défaut du navigateur — pas un protocole inventé par ce site.

Les scripts de la page peuvent encore lire le fragment. URL.hash sur MDN, c’est # plus le fragment lui-même ; la page courante peut aussi utiliser location.hash. La page de lecture s’en sert pour prendre la clé, puis appelle Web Crypto pour un déchiffrement AES-256-GCM. La même interface peut être mal utilisée : un script peut coller le hash dans une query, ou l’envoyer dans une charge analytics. La spécification bloque la ligne de requête que le navigateur construit par défaut. Elle ne bloque pas « la page a lu le hash et l’a envoyé quand même ».

N’expérimentez pas avec un vrai mot de passe, une clé d’API ou une chaîne de connexion non masquée. Préparez une phrase d’essai à usage unique. Vous contrôlez des lignes de requête et des corps — pas besoin d’exposer à nouveau un secret.

Comparer la barre d’adresse à la ligne de requête dans Network

Un slogan n’est pas une preuve. Un contrôle que vous pouvez finir maintenant : ouvrez le panneau Network et placez la chaîne complète de la barre d’adresse à côté de chaque URL de requête. La note précédente cherchait un canari de texte en clair. Celle-ci ne regarde que le fragment : le segment après # doit apparaître dans la barre d’adresse, et ne doit pas apparaître sur la ligne de requête.

  1. Ouvrez la page de lecture et confirmez que la barre d’adresse ressemble à s.html?id={id}#{key} : la query ne porte que l’id, et la clé n’est qu’après le dièse.
  2. Ouvrez Network, cochez Preserve log, et commencez le filtre sur All. Ne masquez pas encore la requête de document, les API suivantes, ni analytics.
  3. Collez la clé après # dans le filtre comme canari. Un hit en rouge : arrêtez-vous et lisez cette requête — l’implémentation a mis le fragment dans une query, ou un script l’a lu et renvoyé.
  4. S’il n’y a pas de hit, ouvrez une par une la requête de document et les interfaces du type /secrets. La ligne de requête ne doit porter que id ; le corps ne doit pas contenir le texte d’origine.

Vous pouvez faire le même contrôle à la création. Après que cet appareil a chiffré en AES-256-GCM, les champs sortants devraient être le texte chiffré, l’expiration et le nombre de lectures. La clé aléatoire ne devrait apparaître que dans le lien de partage que vous assemblez ensuite — pas dans le JSON du POST. Burn-Link de FastPwd suit cette frontière : créer et lire sont prêts à l’emploi, sans compte. Une promesse n’est encore qu’une promesse. Fiez-vous à ce que cette session Network montre.

Gardez %23 et un vrai # distincts. Des caractères après %23 sur la ligne de requête ont été envoyés comme chemin ou query : le serveur les voit. Seul un # non encodé commence un fragment. Écrire la clé en ?key= ou /%23key peut donner l’impression qu’« elle est cachée dans le dièse ». Pour un journal, c’est déjà une query en clair.

Les scripts de page et analytics peuvent encore lire le hash

Une requête HTTP par défaut sans fragment n’est pas la même chose que « aucun script de cet onglet ne peut le voir ». Le JavaScript du document courant a location.hash. La page de lecture doit le lire pour déchiffrer en local. Si un script analytics reporte tout location.href, la clé apparaît dans la query ou le corps de ce tracker.

Donc une recherche canari ne peut pas s’arrêter à l’API métier. Filtrez le chemin analytics à part et ouvrez query et corps. Le titre de page et le chemin peuvent y figurer. La clé après #, un mot de passe à tester et le texte avant masquage ne le devraient pas. Les statistiques de visite FastPwd passent par le tracker du site et, par défaut, n’envoient rien depuis localhost. Même ainsi, utilisez le même canari pour confirmer que le rapport n’a pas de clé de fragment.

Les extensions, un DLP d’entreprise et certains « navigateurs IA » peuvent aussi lire toute la barre d’adresse. Ils ne voyagent pas sur la ligne HTTP que vous regardez : le panneau Network ne prouvera pas qu’ils sont restés dehors. Réduisez l’affirmation à une phrase qu’un collègue peut réutiliser : « J’ai cherché la ligne de requête et la charge analytics ; la clé après # n’y est pas. » N’en faites pas « aucun logiciel de cet ordinateur n’a jamais vu ce hash ».

Quand cette couche de protection lâche

Le fragment bloque « cette requête de document a remis la clé à l’hôte ». Dans les cas ci-dessous, quiconque voit l’URL entière détient déjà la clé. La spécification n’y peut rien.

Premier cas : coller le lien entier dans un chat, un e-mail ou un ticket. Le corps du message contient la partie après #. L’autre client, l’historique du salon, la recherche du ticket et un export plus tard peuvent tous voir la chaîne. Le serveur peut encore ne jamais recevoir la clé en HTTP. Quiconque peut ouvrir le fil l’a déjà. Le lien entier est un secret au porteur.

Deuxième cas : l’historique du navigateur et la synchronisation. Un fragment # apparu dans la barre d’adresse atterrit en général dans l’historique local. Un navigateur avec la synchro activée peut copier le même enregistrement sur vos autres appareils. Fermer l’onglet ne vide pas l’historique.

Troisième cas : une redirection peut emporter le fragment. Si la cible Location n’a pas de fragment à elle, beaucoup de navigateurs recollent le fragment d’origine à la nouvelle adresse. La clé se retrouve alors dans une barre d’adresse que vous croyiez déjà « sortie de la page sensible ». Si la page de lecture est redirigée vers un hôte de statistiques ou une page de connexion, lisez d’abord la ligne de requête finale dans Network, puis voyez ce qu’il reste dans la barre d’adresse.

Quatrième cas : les aperçus de lien et les scanners de sécurité récupèrent la partie avant le dièse. Quand une appli de chat construit un aperçu, elle ne devrait pas envoyer le fragment à l’hôte prévisualisé, mais elle demandera s.html?id=…. Si l’implémentation brûle dès le premier GET du document ou le premier GET du texte chiffré, le robot d’aperçu peut consommer le compteur de lectures avant que le destinataire clique. Le destinataire voit alors « déjà brûlé ». La clé n’a jamais été donnée à l’hôte d’aperçu — c’est le compteur qui a brûlé, pas le secret du fragment.

La page de lecture FastPwd sépare ces deux étapes : ouvrir la page demande seulement si le texte chiffré est encore là ; elle ne l’emporte pas. Après que le destinataire clique pour ouvrir et consulter, la page récupère le texte chiffré par id et déchiffre en local avec la clé après #. Ni la page de création ni la page de lecture n’exigent de compte. Les robots d’aperçu récupèrent en général la page d’atterrissage et ne cliquent pas le bouton à votre place, ce qui abaisse le risque « aperçu égale brûlé ». Cela ne garantit pas que chaque scanner s’arrête à la page d’atterrissage. Le compteur reste un compteur côté serveur, pas une partie du fragment.

Qui regarde Voit-il la clé après # Cette couche tient-elle encore
Ligne de la requête de document Pas par défaut Oui ; comparez dans Network
Journal d’accès de l’hôte / CDN Pas par défaut Oui, si la clé n’est jamais entrée dans la query
Referer vers un tiers Pas par défaut Oui ; la spécification retire le fragment
Script de page / analytics Peut lire location.hash Selon l’implémentation ; cherchez le canari dans le corps
Chat, e-mail, corps de ticket Voit la chaîne entière Ne tient pas face à la personne à qui vous l’avez transféré
Historique / synchro du navigateur En général oui Ne tient pas face à quiconque peut ouvrir ce navigateur
Robots d’aperçu de lien Ne devraient pas recevoir le fragment Peuvent consommer un compteur de lectures

Ce que vous pouvez prouver, et ce que vous ne pouvez pas

Ce contrôle soutient une affirmation étroite. Il soutient : dans ce navigateur, à cette ouverture, la requête de document et les lignes d’interface observées n’ont pas emporté la clé après #, et le corps de la requête de création n’a pas contenu le texte d’origine.

Il ne soutient pas : qu’un admin du chat n’a jamais enregistré le lien entier ; qu’une extension n’a jamais lu la barre d’adresse ; que le destinataire ne fera pas de capture d’écran ; que le texte chiffré ne réapparaîtra jamais dans une autre sauvegarde une fois récupéré. Le lien auto-destructeur supprime le texte chiffré sur le serveur. Il ne supprime pas le texte en clair déjà déchiffré à l’écran, ni l’URL entière qui reste dans un fil de discussion.

Ne lisez pas un fragment comme « seul le destinataire prévu peut ouvrir ceci ». Quiconque détient la chaîne complète ?id=…#clé peut déchiffrer sur la page de lecture publique. La page de lecture n’a pas de barre de connexion. C’est le choix de conception : l’autre personne n’a pas à s’inscrire d’abord. Le coût, c’est que la chaîne entière doit se garder comme un secret, pas comme un lien de document que l’on transfère dans un groupe.

Questions fréquentes

La partie après # dans une URL est-elle envoyée au serveur ?

Pas par défaut. RFC 9110 exclut le fragment de l’URI cible. Quand le navigateur demande le document, il retire le dièse et tout ce qui suit. L’hôte, le proxy inverse et le journal d’accès ne voient, par conception, que le chemin et la requête après ?. Si la clé après # apparaît sur la ligne de requête, c’est un bug d’implémentation, pas une exception de la spécification.

Pourquoi ne pas mettre la clé de déchiffrement après le point d’interrogation ?

La requête après ? s’écrit dans la ligne HTTP. L’hôte, le CDN et les journaux d’accès la voient. Une fois la clé dans la query, « le serveur ne stocke que du texte chiffré » est déjà faux au niveau des logs. Un id peut vivre dans la query, parce que c’est ainsi que l’on récupère le texte chiffré. Une clé, non.

Coller un lien complet avec dièse dans un chat reste-t-il sûr ?

Le serveur ne voit toujours pas la clé dans cette requête HTTP. L’historique du chat, le ticket et l’historique du navigateur, si. Le lien entier est un secret au porteur : quiconque a la chaîne complète peut déchiffrer sur la page de lecture. Si vous voulez plus d’isolation, envoyez le lien qui porte l’id et la clé après # par deux canaux — ne jetez pas la chaîne entière dans le même salon.

Faut-il un compte pour ouvrir une page de lecture Burn-Link ?

Non. La page de lecture est publique pour le destinataire. Elle récupère le texte chiffré avec l’id dans la query et déchiffre en local avec la clé après #. FastPwd n’a ni compte ni coffre. Créer et lire sont prêts à l’emploi. Une barre de connexion ne réglerait pas la frontière du fragment, et elle ne devrait pas entrer dans ce contrôle.

Vérifier sur un lien d’essai jetable

Si vous voulez une page qui sépare déjà query et fragment, commencez par Burn-Link de FastPwd. Prêt à l’emploi — aucun compte. Écrivez une phrase d’essai jetable et générez un lien chiffré en local. Dans la requête de création, vous devriez voir le texte chiffré plus des champs de compteur et d’expiration. Le lien de partage est celui qui ressemble à s.html?id={id}#{key}.

Ouvrez la page de lecture avec cette chaîne. La barre d’adresse devrait montrer à la fois ? et #. Dans Network, la requête de document et le contrôle d’état ne devraient porter que id. Après le clic pour ouvrir et consulter, la requête qui récupère le texte chiffré doit encore omettre la clé après #. Le texte en clair ne devrait apparaître que sur la page, après le déchiffrement local. L’algorithme est AES-256-GCM ; le calcul passe par Web Crypto. Un seul texte est plafonné à 32 Ko. Pour un fichier entier, utilisez le chiffrement de fichier : un fichier jusqu’à 5 Go, sortie .lock / .enc.

Le but de l’exercice n’est pas de prouver qu’un site est « absolument sûr ». Il est de tenir deux faits à part. Première couche : l’hôte ne voit pas le fragment, selon la spécification. Seconde couche : quiconque voit le lien entier détient la clé. Sur n’importe quelle page qui affirme « la clé se place après le dièse », les étapes restent les mêmes : comparez la barre d’adresse à la ligne de requête, inspectez analytics à part, puis demandez-vous si cette URL va atterrir dans un fil de discussion.

Avant d’envoyer le suivant, retenez trois lignes. Premièrement : un id peut entrer dans la query ; une clé n’entre que dans le hash. Deuxièmement : une ligne de requête propre signifie seulement que le serveur n’a pas reçu la clé. Troisièmement : gardez le lien entier comme un secret — ne le transférez pas comme une URL de document ordinaire. Une observation que vous pouvez répéter est celle qui mérite d’entrer dans vos propres notes de passation.

Pour aller plus loin

La note précédente transforme « le texte en clair a-t-il quitté cet onglet ? » en un contrôle Network reproductible. Lisez-la à côté de celle-ci : Vérifier que le chiffrement local n’envoie pas le texte en clair. Cet article ne fait que séparer ? de #, et nommer la couche qui tombe quand vous transférez le lien entier.