# Comment les invites de noms d'hôte Unicode peuvent approuver la mauvaise destination

Une fiche d'approbation pour la requête HTTP d'un agent est une frontière de sécurité, pas une simple notification. Si la fiche indique une destination et que la pile HTTP envoie les identifiants vers une autre, l'utilisateur n'a pas pris une décision éclairée. C'est la passerelle qui a décidé à sa place, avec une police plus agréable.

L'Unicode facilite ce décalage, car un nom d'hôte possède plusieurs formes qui semblent liées tout en ayant des fonctions différentes. L'utilisateur peut voir de l'Unicode. Le DNS reçoit des labels compatibles ASCII. Un analyseur d'URL peut normaliser les séparateurs, les échappements en pourcentage, la casse, la notation IPv4 ou un label final vide. Le gestionnaire de redirections peut analyser la destination suivante avec un autre chemin de code. Si une couche approuve une représentation et qu'une autre se connecte avec une représentation différente, les identifiants peuvent partir vers la mauvaise autorité.

La solution n'est pas « interdire les domaines non ASCII ». Cela pénaliserait les noms internationalisés légitimes et laisserait intactes les astuces ASCII comme les sous-domaines trompeurs, les informations utilisateur, les formes numériques d'adresses IP et les étapes de redirection. Il faut plutôt faire d'un seul enregistrement d'autorité analysé l'unique objet capable à la fois d'alimenter l'interface d'approbation et d'autoriser l'injection des identifiants. Testez ensuite les entrées difficiles avant leur arrivée en production.

## La fiche d'approbation doit décrire la requête qui sera exécutée

Une invite d'approbation doit provenir du même objet de requête canonique que celui exécuté par le client HTTP. Toute autre approche crée deux sources de vérité : une chaîne d'affichage pour les personnes et une chaîne de transport pour le code. C'est dans cette séparation que la confusion sur le nom d'hôte devient une fuite d'identifiants.

Construisez la requête dans cet ordre :

1. Acceptez l'URL brute comme une entrée non fiable et analysez-la une seule fois avec l'implémentation d'URL choisie pour la passerelle.
2. Refusez les schémas non pris en charge, les autorités mal formées, les identifiants intégrés et les entrées dont la forme canonique ne respecte pas vos règles de nom d'hôte.
3. Dérivez un enregistrement d'autorité structuré contenant le schéma, l'hôte canonique, le port effectif et un indicateur précisant si l'entrée comportait un point final de racine.
4. Affichez la fiche d'approbation à partir de cet enregistrement, puis transmettez le même enregistrement au code qui sélectionne et injecte les identifiants.
5. Exigez une nouvelle autorisation si une redirection, une nouvelle tentative, un proxy ou une adresse différente modifie l'enregistrement d'autorité.

Une URL brute est un élément de preuve, pas une autorité. Conservez-la dans l'enregistrement d'activité, car elle aide à expliquer ce que l'agent a tenté de faire. Ne l'utilisez pas seule pour faire correspondre des identifiants, mettre en cache des approbations ou afficher une destination.

Pour HTTP, l'autorité ne se résume pas à un nom d'hôte. `https://api.example.test:8443/` et `https://api.example.test/` peuvent mener à des services et à des politiques de certificats différents. Le port par défaut peut être omis de l'affichage une fois établi par l'analyseur, mais un port différent de la valeur par défaut doit apparaître sur la fiche. Affichez aussi le schéma. Envoyer un jeton bearer via `http` plutôt que `https` modifie le risque, même lorsque le texte du nom d'hôte est identique.

La RFC 3986 définit la partie hôte comme un littéral IP, une adresse IPv4 ou un nom enregistré. Cette définition est utile pour la syntaxe, mais elle n'indique pas à un système d'approbation ce qu'une personne peut reconnaître sans danger. Considérez l'analyse comme un préalable, puis construisez un affichage adapté à la sécurité à partir du résultat analysé.

## Le punycode est un identifiant, pas un nom convivial

Le punycode permet au DNS de transporter des labels internationalisés en utilisant ASCII. Un A-label commence par le préfixe ASCII `xn` suivi de deux tirets, tandis qu'un U-label est la forme Unicode correspondante. La conversion n'est réversible que lorsque le label est valide selon les règles IDNA applicables. Cette distinction compte : une chaîne qui ressemble simplement à un A-label n'est pas automatiquement valide ni sûre à afficher sous forme Unicode.

La RFC 5891 exige qu'une application de recherche consciente d'IDNA valide un A-label apparent avant de traiter sa sortie Unicode décodée comme un U-label. En particulier, lorsqu'une application décode un A-label pour l'afficher dans la langue de l'utilisateur, elle doit vérifier que sa reconversion produit le label d'origine. Ce parcours aller-retour n'est pas théorique. Sans lui, une interface peut transformer un ASCII mal formé en texte Unicode convaincant et donner à la personne chargée de la vérification une version que le transport n'a jamais réellement fournie.

La fiche d'approbation doit afficher les deux formes lorsque l'entrée est internationalisée :

```text
Destination
https://xn--example-ascii-label.test
Unicode rendering: example-unicode-label.test
Port: 443
Credential: deploy token
```

Placez le nom d'hôte ASCII canonique sur sa propre ligne, dans une police à chasse fixe ou dans une présentation particulièrement lisible. Ne le cachez pas derrière un bouton de divulgation. L'affichage Unicode aide à reconnaître un service légitime, mais la forme ASCII est l'identifiant durable qui doit correspondre aux listes d'autorisation, aux entrées d'audit, aux clés de cache et au véritable chemin de recherche DNS.

Ne décodez pas chaque label simplement parce qu'il commence par le marqueur d'un A-label. Décodez, validez, réencodez et comparez. Si la validation échoue, affichez le label ASCII littéral avec un avertissement clair indiquant que le nom d'hôte n'a pas passé la validation IDNA. L'action sûre consiste à refuser l'injection des identifiants, pas à deviner ce que l'agent voulait dire.

C'est un cas où une recommandation populaire échoue : « Affichez toujours l'Unicode, car le punycode semble suspect. » Ce conseil produit une interface accueillante, mais retire la seule représentation qui reste stable entre les polices et les scripts. Afficher uniquement l'ASCII est également une mauvaise pratique pour un service internationalisé légitime. Affichez les deux formes, faites de l'ASCII la référence et assurez-vous qu'elles proviennent du même résultat d'analyse validé.

## Les scripts mélangés doivent déclencher un avertissement, pas un faux verdict

Un nom d'hôte qui combine des caractères latins et cyrilliques peut sembler presque identique à un nom ASCII. Par exemple, un label peut contenir une petite lettre cyrillique qui ressemble à `a`, `c`, `e`, `o`, `p` ou `x` latin dans de nombreuses polices. Une personne qui parcourt une fiche d'approbation peut manquer la substitution, surtout lorsque la partie significative du nom est courte.

L'Unicode Technical Standard #39 parle de caractère confusable entre scripts lorsque des chaînes sont visuellement confondables sans constituer un caractère confusable d'un seul script. Il décrit aussi les caractères confusables entre scripts entiers, lorsqu'un label utilise un seul script tout en ressemblant à un label dans un autre. La norme reconnaît clairement la limite : la confusion dépend des polices, de la mise en forme contextuelle et de la familiarité des utilisateurs. Un détecteur peut repérer un risque, mais il ne peut pas prouver qu'un nom est trompeur.

C'est précisément pourquoi une passerelle ne doit pas transformer la détection des scripts mélangés en liste de blocage automatique. Une organisation japonaise, coréenne, chinoise, grecque ou multilingue peut posséder un domaine légitime qui échoue à une règle simpliste. Utilisez plutôt ce signal pour modifier le niveau d'approbation :

- Marquez tout label signalé comme utilisant plusieurs scripts afin d'exiger une approbation explicite pour chaque appel.
- Affichez les scripts et les points de code dans une vue détaillée lorsque la personne examine l'hôte.
- Comparez le squelette UTS #39 du nom d'hôte avec les noms internes protégés et les destinations importantes configurées explicitement.
- Refusez la réutilisation automatique des identifiants si la destination ressemble à un nom protégé, même si elle n'est jamais apparue auparavant.
- Enregistrez la version du détecteur et son résultat dans le journal d'activité afin qu'une analyse ultérieure puisse reproduire la décision.

Le squelette sert à comparer, pas à remplacer le nom d'hôte. L'UTS #39 indique explicitement que les résultats de squelette ne conviennent pas à l'affichage, au stockage ou à la transmission comme identifiants. Stockez l'hôte ASCII canonique. Utilisez un squelette pour poser une question précise : « Cette candidate ressemble-t-elle à un nom auquel nous avons décidé d'accorder une protection supplémentaire ? »

L'ensemble des noms protégés doit rester limité et intentionnel. Incluez vos points de terminaison de déploiement, registres de paquets, hôtes de gestion du code source, fournisseurs d'identité et API de paiement ou de production. Ne créez pas une liste énorme de tous les domaines publics potentiellement importants. Vous produiriez des avertissements que personne ne lit, et l'avertissement important finirait par sembler banal.

## Les points de code invisibles transforment la vérification en problème d'affichage

Les caractères invisibles sont plus dangereux que les caractères non ASCII évidents, car la personne chargée de la vérification ne peut pas les voir de manière fiable. Selon le caractère et le logiciel, ils peuvent modifier le comportement de liaison, la direction du texte, les retours à la ligne ou la sélection des glyphes. Certains peuvent être refusés par IDNA. D'autres peuvent être mappés ou traités différemment selon les bibliothèques. Le flux d'approbation ne doit pas dépendre de la capacité d'une personne à repérer l'absence d'encre.

Deux questions distinctes sont souvent confondues :

1. Ce point de code est-il valide dans un label de nom d'hôte IDNA ?
2. Peut-il rendre l'affichage trompeur, le journal ambigu ou le code de comparaison incohérent ?

Répondre oui à la première question ne règle pas la seconde. IDNA définit des règles contextuelles pour certains points de code et sa validation de recherche refuse plusieurs catégories d'entrées invalides. Mais une passerelle HTTP traite aussi des URL brutes, du rendu de l'interface, des journaux JSON, du texte copié et éventuellement des formes d'hôtes qui ne passent pas par le DNS. L'examen de sécurité doit donc couvrir tout ce parcours, pas seulement la validité DNS. La RFC 5891 précise qu'IDNA concerne les noms de domaine, pas le texte libre. Limitez son usage au traitement des noms d'hôte et ne prétendez pas qu'il assainit une URL complète.

Sur un écran d'approbation, refusez un nom d'hôte avant toute injection d'identifiants si son rendu Unicode analysé contient un point de code ignoré par défaut, un contrôle bidirectionnel ou un caractère que votre implémentation IDNA choisie signale comme invalide. Cette règle est volontairement plus stricte que « essayons la recherche et voyons ». Un navigateur public sans identifiants peut tenter une recherche. Une passerelle qui gère des identifiants ne doit pas envoyer un jeton tout en enquêtant sur une chaîne ambiguë.

Lorsque vous journalisez une entrée refusée, conservez deux formes : une séquence de points de code échappée de manière sûre et la séquence d'octets d'origine ou le texte UTF-8 dans un champ qui ne subit aucune normalisation silencieuse. Une bonne entrée d'activité contient par exemple :

```text
raw_host_escaped: "api\\u200d.example.test"
parsed_host_ascii: null
rejection: "default-ignorable code point in hostname display"
credential_attached: false
```

Ne placez pas le nom d'hôte brut dans une phrase qu'un opérateur parcourra rapidement. Les journaux deviennent souvent l'interface d'approbation suivante pendant une réponse à incident. Si un caractère invisible redevient invisible dans un terminal, vous n'avez fait que déplacer le piège.

## Un point final est une donnée, même lorsque le DNS le traite comme la racine

Un nom d'hôte qui se termine par un point n'est pas une ponctuation décorative. Dans la notation de présentation DNS, le point final désigne le label racine et indique que le nom est absolu. La RFC 1034 explique que tout nom de domaine complet se termine par le label racine et que sa forme imprimée se termine donc par un point.

Cela ne signifie pas que chaque composant HTTP traite `api.example.test` et `api.example.test.` de la même façon. Un analyseur peut conserver le point dans sa propriété de nom d'hôte. Un autre peut le normaliser avant la connexion. Le vérificateur de certificats, la gestion des cookies, le proxy, la liste d'autorisation ou le cache de redirections peuvent adopter un troisième comportement. La seule règle sûre consiste à décider si votre passerelle les considère comme équivalents et à tester cette décision dans chaque composant qui traite la requête.

Pour une passerelle d'identifiants, je recommande de conserver ce fait d'entrée et de ne normaliser l'autorité qu'après avoir défini une règle de comparaison documentée. Stockez les deux valeurs :

```text
input_host: "api.example.test."
canonical_dns_name: "api.example.test"
had_root_dot: true
```

Utilisez ensuite `canonical_dns_name` pour faire correspondre l'enregistrement de destination, mais affichez le point final sur la fiche lorsqu'il était présent. La personne chargée de la vérification doit voir que l'agent a fourni une forme légèrement inhabituelle. Un point normal ne doit pas élargir silencieusement l'autorité. Si un identifiant a été approuvé pour `api.example.test`, la requête vers `api.example.test.` ne peut réutiliser cette approbation que si l'analyseur, le résolveur, le vérificateur du nom TLS et la règle de comparaison de la passerelle s'accordent tous sur la même destination.

Ne supprimez pas un point final avec du code générique de traitement des chaînes avant l'analyse. Ce type de suppression évolue facilement en « nettoyage de l'hôte », puis finit par retirer des espaces, de la ponctuation ou des séparateurs Unicode qui auraient dû entraîner un refus. Analysez d'abord. Ne normalisez que les attributs couverts par une règle sémantique écrite.

## Le corpus de tests de l'analyseur doit contenir des cas hostiles, pas les exemples d'une présentation

Une suite de tests de noms d'hôte doit vérifier l'accord entre l'analyse, la canonicalisation, l'affichage, la correspondance des identifiants, l'établissement de la connexion et la journalisation. Tester une fonction qui convertit un label Unicode en ASCII est utile, mais ne prouve pas que le chemin complet de la requête est sûr.

Utilisez votre environnement d'exécution d'URL de production pour un premier passage. Le script Node suivant teste l'analyseur d'URL WHATWG et affiche les champs qu'une passerelle d'approbation devrait comparer. Il imprime volontairement du JSON afin de rendre les différences lisibles dans CI.

```js
const cases = [
  "https://example.test/",
  "https://example.test./",
  "https://münich.example.test/",
  "https://xn\\u002d\\u002dexample-ascii-label.test/",
  "https://pаypal.example.test/",
  "https://api\\u200d.example.test/",
  "https://example.test@attacker.test/",
  "https://example.test:8443/",
  "https://127.0.0.1./"
];

for (const raw of cases) {
  try {
    const u = new URL(raw);
    console.log(JSON.stringify({
      raw,
      href: u.href,
      protocol: u.protocol,
      hostname: u.hostname,
      host: u.host,
      port: u.port,
      username: u.username,
      passwordPresent: u.password.length \u003e 0
    }));
  } catch (error) {
    console.log(JSON.stringify({ raw, rejected: error.message }));
  }
}
```

La forme attendue de la sortie compte davantage qu'une chaîne universelle attendue, car les environnements évoluent et les règles d'analyse des hôtes varient selon les plateformes. Chaque cas accepté doit produire exactement un nom d'hôte canonique qui sera transmis à toutes les étapes suivantes. Chaque cas refusé doit prouver que la passerelle n'ajoute aucun identifiant et n'envoie aucune requête réseau.

Élargissez le corpus avec des labels utilisant les scripts que votre équipe rencontre réellement, des caractères Unicode ressemblant à des points, un encodage en pourcentage dans l'hôte, des marques combinatoires initiales, des crochets et des formes IPv6, des A-labels en majuscules, des A-labels mal formés, des labels vides, des noms locaux et des redirections. Ajoutez les cas issus des rapports de bugs. La suite devient utile lorsqu'elle contient les chaînes qui ont fait pester un ingénieur devant un fichier journal, pas lorsqu'elle contient dix variantes de `example.com`.

Le WHATWG URL Standard constitue une bonne base pour le comportement des URL Web, car il spécifie l'analyse des hôtes, les échecs liés aux hôtes encodés en pourcentage, la conversion Unicode vers ASCII et les cas limites IPv4. Cela ne vous autorise pas à supposer que chaque pile HTTP, bibliothèque DNS ou composant d'interface se comporte de manière identique. Exécutez le corpus dans votre pile réelle.

## Les redirections nécessitent une nouvelle décision d'autorité

La première approbation n'autorise pas tous les hôtes qu'une requête pourrait visiter ensuite. La gestion des redirections se trouve souvent sous le code applicatif qui a affiché l'invite, ce qui en fait un endroit courant où les identifiants sont transmis par erreur.

Supposons qu'un agent demande `https://build.example.test/artifact`. L'utilisateur approuve un jeton de déploiement pour cet hôte. La réponse renvoie vers `https://downloads.example.test/file`, ou pire, vers un nom d'hôte Unicode ressemblant à un autre. Si le client suit automatiquement les redirections et conserve l'en-tête Authorization, la passerelle a contourné la frontière d'approbation.

Appliquez une règle simple : une redirection HTTP qui modifie le schéma, le nom d'hôte canonique ou le port effectif invalide l'autorisation précédente. La passerelle peut suivre la redirection sans identifiants si ce comportement est sûr pour l'opération, mais elle doit s'arrêter avant d'injecter un identifiant dans la nouvelle autorité. Affichez une nouvelle fiche d'approbation construite à partir de la nouvelle URL analysée.

Séparez également un changement d'origine d'un changement de chemin. Une redirection au sein du même schéma, du même hôte canonique et du même port peut conserver l'autorisation si les règles de chemin de l'opération le permettent. Ne réduisez pas cela à une comparaison de préfixes textuels. `https://api.example.test.evil.test/` commence par une chaîne rassurante, mais appartient à un autre hôte.

Pour les jetons bearer, supprimez par défaut Authorization lors de toute redirection vers une autre autorité. Pour les certificats clients et les identifiants de type SSH, la couche de transport peut choisir une identité avant même qu'une redirection soit possible. La passerelle a donc besoin d'une règle équivalente au moment de l'établissement de la connexion. Le principe reste le même : l'autorisation est liée à une autorité analysée, pas à l'intention initiale de l'agent.

## La sélection des identifiants exige des limites précises

Une passerelle qui conserve plusieurs clés API doit décider laquelle peut être envoyée à un hôte. Ce n'est pas un problème d'interface. C'est le point où une erreur d'affichage devient une action réseau.

Stockez l'association d'un identifiant sous forme structurée, par exemple :

```json
{
  "scheme": "https",
  "host_ascii": "api.example.test",
  "port": 443,
  "allow_subdomains": false,
  "require_per_call_approval": true
}
```

Évitez une règle comme `host.endsWith("example.test")`. Elle accepte `notexample.test`, et une variante qui vérifie la présence d'un point précédent peut encore mal gérer la normalisation Unicode ou un point final de racine. Si vous autorisez les sous-domaines, séparez les labels ASCII canoniques et comparez-les de droite à gauche. `api.example.test` ne peut correspondre au parent `example.test` que si l'association autorise explicitement les sous-domaines. Le parent ne doit jamais correspondre à l'enfant dans l'autre sens.

Gardez les littéraux IP séparés des noms enregistrés. Ne faites pas de résolution inverse d'une IP pour appliquer ensuite une règle d'identifiant de nom d'hôte. Les noms DNS peuvent changer et le DNS inverse ne prouve pas qu'une IP est autorisée pour un identifiant API. De même, ne résolvez pas un nom d'hôte pour approuver toutes les adresses qu'il renvoie. L'identifiant est lié au nom d'hôte utilisé pour TLS et pour l'autorité HTTP, tandis que les contrôles de connexion peuvent limiter séparément les adresses privées, de bouclage local, locales au lien ou autrement interdites.

Une passerelle doit aussi distinguer une association d'identifiant d'une approbation. L'association répond à la question « cet identifiant pourrait-il un jour être utilisé ici ? ». L'approbation répond à la question « une personne a-t-elle autorisé ce processus d'agent à l'utiliser pour cette requête ou cette session ? ». Confondre ces questions transforme une liste d'autorisation en oracle de signature sans supervision.

## Les enregistrements d'audit doivent conserver ce que la personne a vu

Lorsqu'une approbation semble incorrecte, les opérateurs doivent répondre à trois questions : qu'est-ce que l'agent a envoyé, quelle autorité canonique la passerelle a-t-elle exécutée et quel texte exact la personne a-t-elle vu ? Une seule URL affichée ne peut pas répondre aux trois.

Enregistrez séparément les champs suivants :

- l'URL brute ou une représentation échappée de manière sûre ;
- le schéma analysé, le nom d'hôte ASCII canonique, le port effectif et le chemin ;
- le rendu Unicode validé du nom d'hôte, s'il existe ;
- les indicateurs de risque du nom d'hôte, comme le point final de racine, le résultat de scripts mélangés, la correspondance avec un nom protégé et le refus dû à un caractère invisible ;
- la décision d'approbation, l'identifiant du secret et l'indication précisant si la passerelle a joint les identifiants.

Rendez l'enregistrement d'approbation immuable avant le démarrage de la requête par le client. Si le client suit ensuite une redirection, créez un enregistrement enfant lié, avec sa propre autorité analysée et sa propre décision. Une ligne indiquant « requête approuvée réussie » ne suffit pas lorsque le fait important est que le premier hôte a renvoyé un en-tête Location vers un second hôte.

La séparation de Sallyport entre un journal Sessions pour les exécutions d'agents et un journal Activity pour les appels individuels constitue une structure pertinente pour ces éléments de preuve : la session indique quel processus a reçu l'autorité, tandis que l'appel indique quelle destination l'a utilisée. Son journal d'audit chaîné par hachage peut vérifier l'historique hors ligne, mais les champs utiles doivent tout de même être capturés avant l'action. Un champ vide et inviolable reste un champ vide.

## Rendez les noms d'hôte suspects coûteux pour l'agent, pas déroutants pour la personne

La meilleure interface d'approbation ne demande pas à quelqu'un de devenir spécialiste d'Unicode en deux secondes. Elle exige davantage d'autorité de la part de l'agent lorsque la forme est risquée, tout en donnant à la personne suffisamment d'éléments pour décider clairement.

Utilisez cette matrice de comportement :

| Condition de l'hôte | Action de la passerelle |
| --- | --- |
| Nom d'hôte ASCII canonique simple avec une association d'identifiant exacte | Comportement normal de session ou par appel |
| Nom d'hôte internationalisé valide | Afficher les formes ASCII et Unicode, puis appliquer les règles normales d'association |
| Scripts mélangés ou ressemblance avec un nom protégé | Exiger une approbation par appel et afficher les détails des points de code à la demande |
| Point final de racine | Conserver et afficher le point, puis comparer uniquement selon la règle canonique documentée |
| A-label invalide, contrôle invisible, séparateur non pris en charge ou désaccord entre analyseurs | Refuser avant toute tentative d'identification ou de connexion |
| Redirection vers une autre autorité | Arrêter la transmission des identifiants et demander une nouvelle approbation |

Ne rendez pas la fiche d'avertissement théâtrale. Un mur de texte rouge apprend aux utilisateurs à cliquer pour passer. Expliquez la raison en termes simples : « Ce nom d'hôte mélange des caractères latins et cyrilliques » ou « Ce nom d'hôte contient un caractère Unicode invisible ». Présentez ensuite l'hôte ASCII canonique, l'identifiant demandé et l'action. Les utilisateurs approuvent des actions, pas des cours.

Le premier test à ajouter est une requête qui ressemble, pour un lecteur non averti, à un hôte protégé, mais qui est analysée comme une autre autorité. Faites-la passer par le shim exact de l'agent, l'interface d'approbation, le sélecteur d'identifiants, la bibliothèque HTTP, le code de redirection et le journal d'audit. Si une étape produit une chaîne de nom d'hôte différente sans entraîner de refus, la passerelle a encore deux vérités. Corrigez cela avant d'ajouter un nouveau bouton de politique.
