# Normaliser les adresses IPv6 pour les cibles d'approbation

Un écran d'approbation qui affiche une chaîne IPv6 brute demande à une personne de faire le travail d'un analyseur sous pression. C'est une erreur de conception. Le système doit analyser la requête pour en faire un point de terminaison typé, rejeter toute ambiguïté, comparer la valeur typée et afficher une forme stable qu'une personne pourra reconnaître lors de la requête suivante.

La notation IPv6 donne aux attaquants comme aux logiciels ordinaires de nombreuses façons de faire paraître une même destination différente : champs nuls compressés, zéros non significatifs, crochets exigés par les URL, terminaison IPv4 intégrée et zones d'interface. Cela ne rend pas IPv6 suspect. Cela signifie qu'une cible d'approbation exige un traitement plus strict qu'un champ qui contient simplement un nom d'hôte.

## Une même adresse peut arriver sous plusieurs chaînes

Les chaînes `2001:db8:0:0:0:0:0:9`, `2001:0db8::9` et `2001:db8::9` désignent la même adresse IPv6 sans portée. Si votre carte d'approbation stocke une chaîne et qu'une requête ultérieure en fournit une autre, une comparaison de chaînes conclut qu'elles diffèrent. Si votre liste d'autorisations accepte une orthographe mais que la recherche dans l'audit en attend une autre, les opérateurs perdent la piste précisément lorsqu'ils en ont besoin.

IPv6 comprend huit champs de 16 bits. Les auteurs peuvent omettre les zéros non significatifs dans chaque champ, puis remplacer une suite consécutive de champs entièrement nuls par `::`. Le marqueur `::` est pratique pour les humains, mais il supprime l'information sur le nombre de champs omis. Un analyseur rétablit les champs manquants et renvoie la seule représentation qui compte pour décider de l'égalité : 16 octets d'adresse.

Cette distinction est souvent brouillée : le texte canonique sert à l'examen, tandis que les octets analysés servent à la comparaison. La canonicalisation ne constitue pas à elle seule une autorisation. Elle garantit simplement que la forme montrée à une personne ne dépend pas de l'orthographe de l'appelant.

Traitez les éléments suivants comme des données distinctes :

- `raw_input` : le texte exact de l'hôte et la syntaxe d'autorité qui l'entoure, reçus de l'appelant
- `address` : 16 octets après une analyse stricte
- `scope_id` : une portée d'interface lorsque l'entrée en fournit une de façon valide
- `display_host` : une chaîne canonique générée à partir de l'adresse typée
- `port`, `scheme` et détails de la requête : les champs qui décrivent l'action réelle

Conservez l'entrée brute dans l'enregistrement d'audit. Elle explique ce que l'agent a demandé. Ne l'utilisez pas comme jeton de comparaison et ne l'affichez pas seule à l'écran.

## Les crochets d'URI relèvent de la syntaxe, pas de l'hôte

Un littéral IPv6 dans l'autorité d'une URI doit être entre crochets, car les deux-points séparent déjà un hôte d'un port. RFC 3986 définit cette forme : `https://[2001:db8::9]:8443/v1/jobs`. L'adresse est `2001:db8::9` ; les crochets indiquent à l'analyseur d'URI où cet hôte se termine.

Cette règle entraîne un échec courant. Un développeur passe `[2001:db8::9]` à un analyseur d'adresses, reçoit un rejet, retire des caractères jusqu'à ce que cela fonctionne, puis accepte plus tard une autorité malformée parce que les deux analyseurs ne sont plus d'accord. L'autre version du même bug stocke les crochets avec l'adresse : un enregistrement contient `[2001:db8::9]`, un autre `2001:db8::9`. Ils n'auraient jamais dû parvenir dans le même champ de stockage.

Analysez à la frontière grammaticale où la valeur est arrivée. Pour une cible HTTP, analysez d'abord l'URI complète avec un analyseur d'URI conforme aux standards. Extrayez son hôte, son port, son schéma, son chemin et sa requête selon cet analyseur. Envoyez ensuite uniquement la valeur de l'hôte, sans crochets d'URI, à l'analyseur IPv6. Pour un argument direct d'hôte SSH, utilisez la grammaire documentée par l'interface de commande SSH au lieu de faire comme s'il s'agissait d'une URI.

L'ordre compte. Considérez cette requête :

```text
https://[2001:0db8:0:0:0:0:0:9]:8443/admin
```

Un résultat interne correct prend cette forme :

```text
kind: ipv6
address: 20010db8000000000000000000000009
scope_id: null
display_host: 2001:db8::9
scheme: https
port: 8443
path: /admin
```

Une interface d'approbation peut maintenant afficher `https://[2001:db8::9]:8443/admin`. Elle ne doit pas la réduire discrètement à `2001:db8::9`, car le port et le chemin font partie de ce que l'utilisateur évalue. Inversement, elle ne doit pas conserver l'orthographe avec zéros ajoutés de l'appelant simplement parce qu'elle est arrivée en premier.

Un littéral seul n'a pas de crochets. Une autorité d'URI avec un littéral IPv6 a des crochets. Gardez cette règle limitée et prévisible.

## L'affichage canonique doit suivre RFC 5952

RFC 5952 recommande des chiffres hexadécimaux minuscules, aucun zéro non significatif dans un champ et `::` pour la plus longue suite consécutive de champs nuls. Lorsque deux suites de zéros ont la même longueur, il choisit la première. Il précise aussi qu'un seul champ nul ne doit pas utiliser `::`. Ces règles donnent à la forme destinée aux humains un résultat stable.

Par exemple, normalisez ces valeurs ainsi :

```text
2001:0DB8:0000:0000:0000:0000:0000:0009  ->  2001:db8::9
2001:db8:0:1:0:0:0:1                    ->  2001:db8:0:1::1
2001:db8:0:1:0:0:0:0                    ->  2001:db8:0:1::
2001:db8:0:1:0:0:0:2                    ->  2001:db8:0:1::2
0:0:0:0:0:0:0:1                         ->  ::1
```

Les recommandations de la norme sont plus utiles qu'elles n'en ont l'air. Elles donnent aux opérateurs une orthographe unique à rechercher dans les journaux et empêchent un appelant de faire passer `2001:0DB8::9` pour une nouvelle destination après l'approbation de `2001:db8::9`.

N'écrivez pas votre propre formateur en découpant sur les deux-points et en comptant les chaînes vides. La forme avec terminaison IPv4, la double compression malformée et la syntaxe de portée rendent cette approche fragile. Utilisez un analyseur IPv6 éprouvé dans le langage qui exécute l'action, conservez sa sortie de 16 octets et formatez à partir de ces octets. Testez le formateur avec les exemples de RFC 5952, ainsi que des cas ayant des suites de zéros de même longueur.

Ne surinterprétez pas non plus RFC 5952. Il décrit une représentation textuelle recommandée. Il ne décide pas si `::ffff:192.0.2.7` et `192.0.2.7` doivent recevoir la même autorisation. C'est une décision produit aux conséquences réelles.

## Les formes mappées IPv4 exigent une règle de comparaison explicite

`::ffff:192.0.2.7` est une adresse IPv6 mappée IPv4. Les 32 derniers bits portent une adresse IPv4 et le motif qui précède identifie la forme mappée. Les systèmes d'exploitation exposent souvent cette forme lorsqu'une application accepte des connexions IPv4 sur un socket IPv6. Elle apparaît assez souvent dans les journaux pour que la traiter comme un cas limite étrange garantisse une confusion ultérieure.

Deux modèles internes se défendent. Choisissez-en un pour chaque frontière d'autorisation et documentez-le dans l'interface.

Le premier modèle conserve les familles d'adresses. `::ffff:192.0.2.7` reste une adresse IPv6 de 16 octets avec le type `ipv6`, tandis que `192.0.2.7` reste une adresse IPv4 de quatre octets avec le type `ipv4`. Elles ne sont jamais égales. C'est le choix le plus sûr par défaut lorsqu'une approbation décrit une connexion réseau demandée dans une forme particulière, car il évite d'élargir silencieusement une décision entre familles.

Le second modèle projette les adresses mappées en IPv4 pour un cas d'usage d'identité de pair défini de façon restrictive. Dans ce modèle, l'analyseur enregistre à la fois la famille d'origine et une valeur `embedded_ipv4`. Le code de comparaison indique délibérément qu'une forme mappée est égale à son adresse IPv4 intégrée pour ce seul usage. L'enregistrement d'audit conserve malgré tout la forme brute et précise que la comparaison a utilisé une projection.

Ce qui échoue, c'est la projection accidentelle. De nombreuses bibliothèques standard offrent une méthode pratique qui transforme une adresse mappée en IPv4 sans indiquer comment elle est arrivée. C'est utile pour journaliser les connexions ; c'est dangereux si un développeur la réutilise pour un jeton d'approbation. Une règle écrite pour `192.0.2.7` peut alors approuver `::ffff:192.0.2.7` sans que personne ait décidé qu'elle le devait.

Utilisez des paires de test qui rendent le choix visible :

```text
input A: 192.0.2.7
input B: ::ffff:192.0.2.7

strict endpoint comparison: different
explicit peer-identity projection: same IPv4 peer, if the product says so
```

Ne traitez pas toute chaîne IPv6 avec une terminaison pointée comme une adresse mappée. L'analyseur doit valider le préfixe complet et la position de la partie IPv4. RFC 4291 définit les adresses IPv4 mappées et autorise aussi la notation IPv4 compatible dans le texte IPv6. Votre formateur doit conserver assez d'informations typées pour éviter d'appeler toutes les adresses à terminaison pointée la même chose.

## Les identifiants de zone appartiennent à une interface locale

Un identifiant de zone transforme une adresse à portée limitée par ailleurs ambiguë en destination locale utilisable. `fe80::1%en0` signifie une adresse link-local sur l'interface nommée `en0`. Sans la zone, un hôte doté de plusieurs interfaces réseau ne peut pas savoir quel lien l'appelant désigne.

RFC 4007 décrit cela comme un concept de zone de portée, pas comme une décoration ajoutée à une adresse. Le nom ou l'index d'interface n'a de sens que pour l'hôte qui le résout. Sur une autre machine, `en0` peut nommer une autre interface ou aucune. Un identifiant de zone est donc une mauvaise cible d'approbation portable.

Pour une action directe sur un socket local, acceptez une zone uniquement si l'analyseur de la plateforme la valide et si l'action s'exécute sur la même machine. Stockez l'index numérique d'interface normalisé comme valeur de comparaison lorsque le système d'exploitation l'expose. Vous pouvez aussi conserver le nom d'interface fourni pour la piste d'audit, mais les noms peuvent changer avec le matériel et la configuration réseau.

Pour une URI HTTP, les règles sont plus strictes. RFC 6874 précise que le signe pourcent qui introduit un identifiant de zone est encodé en `%25` dans le littéral entre crochets. Une URI utilise donc une forme comme celle-ci :

```text
http://[fe80::1%25en0]/status
```

Un analyseur d'URI doit décoder cela au bon stade. Ne décodez pas en pourcentage l'URL entière avant de l'analyser, car un décodeur générique peut modifier des délimiteurs et produire une URL différente de celle fournie par l'appelant. Analysez d'abord l'URI, extrayez le littéral d'hôte, puis appliquez les règles de littéral à portée requises pour ce composant.

La plupart des flux d'approbation HTTP destinés aux agents devraient rejeter les littéraux à portée et expliquer pourquoi : les adresses link-local n'ont de sens qu'avec une interface locale précise. Demander à l'agent d'utiliser un nom DNS stable, une adresse sans portée ou un point de terminaison local délibérément configuré crée une approbation qu'une autre personne peut comprendre. Autorisez l'exception uniquement lorsque le produit agit réellement sur des équipements réseau locaux et affiche clairement la portée de l'interface.

## La comparaison d'un hôte est plus restreinte que l'approbation d'une action

Normaliser une adresse corrige une catégorie limitée de tromperie. Cela ne rend pas `https://[2001:db8::9]/` équivalent à `https://[2001:db8::9]:9443/delete`, et ne rend pas une destination SSH sûre parce que le texte de l'hôte semble familier.

La cible d'approbation typée doit préserver les frontières que la chaîne brute masque. Pour HTTP, cela inclut généralement au minimum le schéma, le type et les octets de l'hôte, le port après résolution du port par défaut, la méthode et une description de requête qui expose le chemin. La présence des en-têtes et du condensat du corps dans la décision dépend de l'action. Si un même jeton bearer peut appeler plusieurs API sans rapport, l'identité de l'identifiant doit aussi apparaître dans le prompt.

Pour SSH, distinguez un point de terminaison réseau de la commande distante. L'approbation d'ouvrir une connexion SSH ne décrit pas automatiquement l'autorisation d'exécuter `sudo`, de modifier des fichiers de déploiement ou de transférer un port. Si un outil peut effectuer plusieurs opérations après une connexion, l'interface doit indiquer ce que couvre l'autorisation de session et conserver un enregistrement de chaque commande.

C'est là qu'un modèle de données clair porte ses fruits. Il évite une recommandation tentante mais erronée : mettre des hôtes canoniques dans une liste d'autorisations plate et considérer le travail terminé. Les listes d'hôtes plates sont populaires parce qu'elles sont faciles à expliquer. Elles échouent parce qu'un hôte n'est qu'une partie d'une action et parce qu'une réponse DNS, un port, un chemin ou une commande ultérieurs peuvent modifier l'effet.

Un enregistrement d'approbation utile peut ressembler à ceci :

```text
transport: https
host_kind: ipv6
host_bytes: 20010db8000000000000000000000009
host_display: 2001:db8::9
scope_id: null
port: 8443
method: POST
path: /v1/releases
credential_ref: deploy-api
raw_authority: [2001:0db8::9]:8443
```

L'autorité brute documente la requête. Les champs typés déterminent la comparaison. Le champ d'affichage donne à la personne une formulation stable à lire. Ne réutilisez aucun de ces champs comme substitut aux autres.

## Les règles de rejet doivent être simples et strictes

Un analyseur doit échouer de façon sûre lorsque l'entrée ne correspond pas à la grammaire de sa position. Le message d'erreur peut être utile, mais le système ne doit pas réparer une adresse en devinant l'intention de l'appelant. Un normaliseur qui accepte des entrées presque valides crée une seconde grammaire que les relecteurs de sécurité auront du mal à reconstituer.

Rejetez une requête de littéral IP si elle présente l'un de ces défauts :

- plus d'un marqueur de compression `::` ou trop de champs après expansion
- des caractères non hexadécimaux dans un champ hexadécimal
- une terminaison IPv4 hors de la position autorisée par la syntaxe textuelle IPv6
- des crochets transmis à un analyseur d'adresse nue, ou des crochets absents dans une autorité d'URI
- un identifiant de zone dans un contexte où l'action ne peut pas se lier à une interface locale

Rejetez aussi un littéral que votre analyseur d'URI et votre analyseur de socket interprètent différemment. Ce n'est pas un scrupule théorique. Selon les périodes, différentes bibliothèques ont fait des choix différents pour l'encodage en pourcentage, les formes IPv4 inhabituelles et l'analyse d'hôtes. Lorsqu'un composant approuve un texte auquel un autre se connecte, il existe une faille d'autorisation.

Constituez un petit corpus de tests inter-couches. Faites passer chaque cas par le même analyseur d'URI, extracteur d'hôte, analyseur IP, formateur, comparateur et constructeur de transport qu'en production. Pour une entrée acceptée, vérifiez à la fois l'affichage canonique et la destination réelle du socket. Pour une entrée rejetée, vérifiez qu'aucun objet de transport n'apparaît.

Un corpus compact doit comprendre `::`, `::1`, une adresse entièrement développée, deux suites de zéros de même longueur, un littéral d'URI entre crochets avec un port, une valeur IPv4 mappée, une terminaison pointée malformée, un littéral link-local à portée et une zone `%25` encodée dans une URI. Ajoutez toute forme d'entrée que les agents de votre environnement ont réellement produite. Les tests de régression tirés d'approbations réelles sont moins spectaculaires que les astuces d'analyseur, mais ils ont bien plus de chances de détecter la prochaine erreur.

## La carte d'approbation doit exposer la normalisation

Une personne ne peut pas évaluer un point de terminaison si la carte masque la partie qui a changé. Affichez la cible canonique de façon visible, puis l'orthographe soumise lorsqu'elle diffère. Une ligne discrète comme `Soumis sous la forme [2001:0DB8:0:0:0:0:0:9]:8443` donne au relecteur des éléments de preuve sans l'obliger à décoder les champs mentalement.

La carte doit aussi rendre les formes spéciales explicites. Étiquetez une adresse mappée comme `IPv6 mappée IPv4` au lieu de la présenter comme un littéral IPv6 ordinaire. Étiquetez une adresse à portée avec le nom de l'interface et précisez qu'elle est locale à cette interface. Si la politique d'action projette une adresse mappée en IPv4, indiquez cette décision sur la carte. Les équivalences silencieuses surprennent les relecteurs.

Pour les appels aux conséquences importantes, exigez que la personne approuve l'action complète, pas seulement l'hôte canonique. Une présentation compacte peut tout de même contenir les détails nécessaires :

```text
POST https://[2001:db8::9]:8443/v1/releases
Uses credential: deploy-api
Submitted host spelling: 2001:0DB8:0:0:0:0:0:9
```

Sallyport applique cette séparation là où elle compte : l'agent ne reçoit jamais le secret API ou SSH, tandis que l'app exécute l'action et renvoie son résultat. Son autorisation par session peut établir qui a lancé une exécution, et ses contrôles par appel peuvent garder les actions sensibles visibles au lieu de considérer une approbation antérieure comme un chèque en blanc.

## Les enregistrements d'audit ont besoin du texte brut et du sens normalisé

L'examen d'un incident exige deux réponses qui entrent souvent en conflit si vous n'avez enregistré qu'une seule représentation : qu'a réellement envoyé l'agent et quelle destination le transport a-t-il utilisée ? Enregistrez les deux. Indiquez ensuite clairement quelle valeur a déterminé la décision.

Un événement d'audit doit inclure l'entrée brute, le type d'hôte analysé, la forme d'affichage canonique, les octets d'adresse dans un encodage sans ambiguïté, l'identité de portée lorsqu'elle est présente et tous les champs d'action couverts par l'approbation. Hacher l'événement après sérialisation n'est utile que si la sérialisation a une définition stable. Sinon, le même événement sémantique peut produire des enregistrements différents parce qu'un formateur a changé.

N'effacez pas une entrée non canonique après avoir dérivé la forme d'affichage. Elle peut révéler un client bogué, une tentative de contournement ou une particularité inoffensive de bibliothèque qui expliquera plus tard un incident. Conservez-la comme élément de preuve, mais ne laissez pas les tableaux de bord de recherche en faire une seconde identité trompeuse pour le même point de terminaison.

Les journaux Activity et Sessions de Sallyport sont générés à partir d'un même journal d'audit chiffré et chaîné par hachage, et `sp audit verify` vérifie cette chaîne hors ligne sur le texte chiffré. Cette structure est particulièrement utile lorsqu'un relecteur doit distinguer une orthographe inhabituelle d'une action exécutée différente.

Commencez par un test avant de repenser toute la couche d'approbation : soumettez la même cible sous une forme entièrement développée puis sous la forme RFC 5952. Si votre système produit des identités d'approbation, des résultats de recherche d'audit ou des décisions d'autorisation différents, la frontière de l'analyseur est encore au mauvais endroit.
