# Normalisierung von IPv6-Adressen für Freigabeziele

Ein Freigabebildschirm, der eine rohe IPv6-Zeichenfolge zeigt, verlangt von Menschen unter Zeitdruck Parserarbeit. Das ist ein Designfehler. Das System muss die Anfrage in einen typisierten Endpunkt parsen, Mehrdeutigkeiten ablehnen, den typisierten Wert vergleichen und eine stabile Form anzeigen, die eine Person bei der nächsten Anfrage wiedererkennt.

Die IPv6-Notation bietet Angreifern und gewöhnlicher Software viele Möglichkeiten, dasselbe Ziel anders aussehen zu lassen: komprimierte Nullfelder, führende Nullen, für URLs nötige Klammern, einen eingebetteten IPv4-Endteil und Schnittstellenzonen. Das macht IPv6 nicht verdächtig. Es bedeutet jedoch, dass ein Freigabeziel strenger behandelt werden muss als ein Feld, das zufällig einen Hostnamen enthält.

## Eine Adresse kann in mehreren Zeichenfolgen erscheinen

Die Zeichenfolgen `2001:db8:0:0:0:0:0:9`, `2001:0db8::9` und `2001:db8::9` bezeichnen dieselbe IPv6-Adresse ohne Bereich. Wenn Ihre Freigabekarte eine Zeichenfolge speichert und eine spätere Anfrage eine andere liefert, behauptet ein Zeichenfolgenvergleich, sie seien verschieden. Wenn Ihre Allowlist eine Schreibweise akzeptiert, die Audit-Suche aber eine andere erwartet, verlieren Betreiber die Spur genau dann, wenn sie sie brauchen.

IPv6 hat acht 16-Bit-Felder. Autorinnen und Autoren dürfen führende Nullen in jedem Feld weglassen und anschließend eine zusammenhängende Folge von Nullfeldern durch `::` ersetzen. Der Marker `::` ist praktisch für Menschen, verschleiert aber, wie viele Felder ausgelassen wurden. Ein Parser ergänzt die fehlenden Felder und liefert die einzige Darstellung zurück, die für eine Gleichheitsentscheidung zählt: 16 Adressbytes.

Dieser Unterschied wird häufig verwischt: Kanonischer Text dient der Prüfung, geparste Bytes dem Vergleich. Kanonisierung allein ist keine Autorisierung. Sie sorgt nur dafür, dass die einer Person gezeigte Form nicht von der Schreibweise des Aufrufers abhängt.

Behandeln Sie diese Werte als getrennte Datenfelder:

- `raw_input`: der exakte Hosttext und die umgebende Authority-Syntax, die vom Aufrufer eingegangen sind
- `address`: 16 Bytes nach strengem Parsen
- `scope_id`: ein Schnittstellenbereich, wenn die Eingabe ihn zulässigerweise liefert
- `display_host`: eine aus der typisierten Adresse erzeugte kanonische Zeichenfolge
- `port`, `scheme` und Anfragedetails: Felder, die die tatsächliche Aktion beschreiben

Bewahren Sie die Rohdaten in der Audit-Aufzeichnung auf. Sie erklären, was der Agent angefordert hat. Verwenden Sie sie nicht als Vergleichstoken und zeigen Sie sie nicht als einziges Element auf dem Bildschirm.

## URI-Klammern sind Syntax, kein Teil des Hosts

Ein IPv6-Literal innerhalb einer URI-Authority muss in Klammern stehen, weil Doppelpunkte bereits Host und Port trennen. RFC 3986 definiert die Form: `https://[2001:db8::9]:8443/v1/jobs`. Die Adresse lautet `2001:db8::9`; die Klammern teilen dem URI-Parser mit, wo dieser Host endet.

Diese Regel führt zu einem häufigen Fehler. Eine Entwicklerin übergibt `[2001:db8::9]` an einen Adressparser, erhält eine Ablehnung, entfernt Zeichen, bis es funktioniert, und akzeptiert später eine fehlerhafte Authority, weil beide Parser nicht mehr übereinstimmen. Die andere Variante desselben Fehlers speichert Klammern neben der Adresse, sodass ein Eintrag `[2001:db8::9]` und ein anderer `2001:db8::9` enthält. Beide hätten nie im selben Speicherfeld landen dürfen.

Parsen Sie an der Grammatikgrenze, an der der Wert eingetroffen ist. Parsen Sie bei einem HTTP-Ziel zuerst die vollständige URI mit einem standardkonformen URI-Parser. Extrahieren Sie Host, Port, Schema, Pfad und Query nach dessen Regeln. Übergeben Sie dann nur den Hostwert ohne URI-Klammern an den IPv6-Parser. Für ein direktes SSH-Hostargument verwenden Sie die Grammatik, die von der SSH-Befehlsoberfläche dokumentiert ist, statt so zu tun, als wäre es eine URI.

Die Reihenfolge ist wichtig. Betrachten Sie diese Anfrage:

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

Ein korrektes internes Ergebnis hat diese Form:

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

Eine Freigabeoberfläche kann nun `https://[2001:db8::9]:8443/admin` anzeigen. Sie sollte das nicht stillschweigend zu `2001:db8::9` verkürzen, denn Port und Pfad gehören zu dem, was die Person bewertet. Umgekehrt sollte sie die aufgefüllte Schreibweise des Aufrufers nicht nur deshalb beibehalten, weil sie zuerst eingegangen ist.

Ein bloßes Literal hat keine Klammern. Eine URI-Authority mit einem IPv6-Literal hat Klammern. Halten Sie diese Regel eng und vorhersehbar.

## Die kanonische Anzeige sollte RFC 5952 folgen

RFC 5952 empfiehlt kleingeschriebene Hexadezimalziffern, keine führenden Nullen in einem Feld und `::` für die längste zusammenhängende Folge von Nullfeldern. Haben zwei Nullfolgen dieselbe Länge, wählt der Standard die erste. Außerdem darf `::` nicht für ein einzelnes Nullfeld verwendet werden. Diese Regeln liefern für die menschenlesbare Darstellung ein stabiles Ergebnis.

Normalisieren Sie diese Werte beispielsweise so:

```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
```

Der Rat des Standards ist nützlicher, als er zunächst wirkt. Er gibt Betreibern eine einheitliche Schreibweise für die Suche in Logs und verhindert, dass ein Aufrufer `2001:0DB8::9` wie ein neues Ziel erscheinen lässt, nachdem `2001:db8::9` bereits freigegeben wurde.

Schreiben Sie keinen eigenen Formatierer, indem Sie an Doppelpunkten trennen und leere Zeichenfolgen zählen. Die IPv4-Endteilform, fehlerhafte doppelte Komprimierung und Bereichssyntax machen diesen Ansatz fragil. Verwenden Sie einen erprobten IPv6-Parser in der Sprache, die die Aktion ausführt, bewahren Sie dessen 16-Byte-Ausgabe auf und formatieren Sie aus diesen Bytes. Testen Sie den Formatierer mit Beispielen aus RFC 5952 sowie Fällen mit gleich langen Nullfolgen.

Überhöhen Sie RFC 5952 auch nicht. Er beschreibt eine empfohlene Textdarstellung. Er entscheidet nicht, ob `::ffff:192.0.2.7` und `192.0.2.7` dieselbe Autorisierung erhalten sollten. Das ist eine Produktentscheidung mit echten Folgen.

## IPv4-gemappte Formen brauchen eine ausdrückliche Vergleichsregel

`::ffff:192.0.2.7` ist eine IPv4-gemappte IPv6-Adresse. Die letzten 32 Bits enthalten eine IPv4-Adresse, und das vorangehende Muster kennzeichnet die gemappte Form. Betriebssysteme zeigen diese Form häufig, wenn eine Anwendung IPv4-Verbindungen auf einem IPv6-Socket akzeptiert. Sie erscheint oft genug in Logs, dass es später zwangsläufig zu Verwirrung führt, sie als seltsamen Sonderfall zu behandeln.

Es gibt zwei vertretbare interne Modelle. Wählen Sie für jede Autorisierungsgrenze eines aus und dokumentieren Sie es in der Oberfläche.

Das erste Modell bewahrt die Adressfamilien. `::ffff:192.0.2.7` bleibt eine 16-Byte-IPv6-Adresse mit dem Typ `ipv6`, während `192.0.2.7` eine Vier-Byte-IPv4-Adresse mit dem Typ `ipv4` bleibt. Sie werden nie als gleich verglichen. Das ist die sicherere Voreinstellung, wenn eine Freigabe eine Netzwerkverbindung in einer bestimmten Form beschreibt, weil die Entscheidung nicht stillschweigend über Adressfamilien hinweg ausgeweitet wird.

Das zweite Modell projiziert gemappte Adressen für einen eng definierten Anwendungsfall der Peer-Identität auf IPv4. In diesem Modell hält der Parser sowohl die ursprüngliche Familie als auch einen Wert `embedded_ipv4` fest. Der Vergleichscode legt bewusst fest, dass eine gemappte Form für genau diesen Zweck ihrer eingebetteten IPv4-Adresse entspricht. Die Audit-Aufzeichnung bewahrt dennoch die Rohform und erklärt, dass der Vergleich eine Projektion verwendet hat.

Was scheitert, ist eine unbeabsichtigte Projektion. Viele Standardbibliotheken bieten eine bequeme Methode, die eine gemappte Adresse in IPv4 umwandelt und keinen Hinweis darauf zurückgibt, wie sie eingetroffen ist. Das ist praktisch für Verbindungsprotokolle, aber gefährlich, wenn eine Entwicklerin sie für ein Freigabetoken wiederverwendet. Eine Regel für `192.0.2.7` kann dann `::ffff:192.0.2.7` freigeben, ohne dass jemand entschieden hat, dass sie es sollte.

Verwenden Sie Testpaare, die diese Entscheidung sichtbar machen:

```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
```

Behandeln Sie nicht jede IPv6-Zeichenfolge mit einem gepunkteten Endteil als gemappt. Der Parser muss das vollständige Präfix und die Position des IPv4-Teils validieren. RFC 4291 definiert IPv4-gemappte Adressen und erlaubt auch IPv4-kompatible Notation in IPv6-Text. Ihr Formatierer sollte genug typisierte Informationen bewahren, damit nicht alle Adressen mit gepunktetem Endteil als dasselbe bezeichnet werden.

## Zonenkennungen gehören zu einer lokalen Schnittstelle

Eine Zonenkennung macht aus einer sonst mehrdeutigen bereichsgebundenen Adresse ein nutzbares lokales Ziel. `fe80::1%en0` bedeutet eine Link-Local-Adresse auf der Schnittstelle namens `en0`. Ohne die Zone kann ein Host mit mehreren Netzwerkschnittstellen nicht wissen, welchen Link der Aufrufer meint.

RFC 4007 beschreibt dies als Konzept einer Bereichszone, nicht als Dekoration, die an eine Adresse angehängt wird. Der Schnittstellenname oder -index hat nur auf dem Host Bedeutung, der ihn auflöst. Auf einem anderen Rechner kann `en0` eine andere Schnittstelle oder gar keine benennen. Deshalb ist eine Zonenkennung ein schlechtes portables Freigabeziel.

Akzeptieren Sie bei einer direkten lokalen Socket-Aktion eine Zone nur, wenn der Plattform-Parser sie validiert und die Aktion auf demselben Rechner läuft. Speichern Sie den normalisierten numerischen Schnittstellenindex als Vergleichswert, wenn das Betriebssystem ihn bereitstellt. Den angegebenen Schnittstellennamen können Sie zusätzlich für die Audit-Spur bewahren, doch Namen können sich ändern, wenn sich Hardware und Netzwerkkonfiguration ändern.

Für eine HTTP-URI gelten strengere Regeln. RFC 6874 legt fest, dass das Prozentzeichen, das eine Zonenkennung einleitet, innerhalb des Literals in Klammern als `%25` kodiert wird. Eine URI verwendet daher eine Form wie diese:

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

Ein URI-Parser muss dies in der richtigen Phase dekodieren. Dekodieren Sie nicht die gesamte URL prozentweise, bevor Sie sie parsen, denn ein allgemeiner Decoder kann Trennzeichen verändern und daraus eine andere URL machen als die, die der Aufrufer übergeben hat. Parsen Sie zuerst die URI, extrahieren Sie das Hostliteral und wenden Sie anschließend die für diese Komponente nötigen Regeln für bereichsgebundene Literale an.

Die meisten HTTP-Freigabeflüsse für Agenten sollten bereichsgebundene Literale ablehnen und dem Aufrufer sagen, warum: Link-Local-Adressen sind nur zusammen mit einer bestimmten lokalen Schnittstelle sinnvoll. Wenn Sie den Agenten bitten, einen stabilen DNS-Namen, eine nicht bereichsgebundene Adresse oder einen bewusst konfigurierten lokalen Endpunkt zu verwenden, entsteht eine Freigabe, die andere Personen verstehen können. Erlauben Sie die Ausnahme nur dort, wo das Produkt tatsächlich mit lokaler Netzwerkausrüstung arbeitet und den Schnittstellenbereich deutlich zeigt.

## Ein Hostvergleich ist kleiner als eine Aktionsfreigabe

Die Normalisierung einer Adresse behebt eine eng begrenzte Klasse von Täuschungen. Sie macht `https://[2001:db8::9]/` nicht gleichwertig mit `https://[2001:db8::9]:9443/delete`, und sie macht ein SSH-Ziel nicht sicher, nur weil der Hosttext vertraut aussieht.

Das typisierte Freigabeziel sollte Grenzen bewahren, die der Rohtext verbirgt. Für HTTP bedeutet das gewöhnlich mindestens Schema, Hosttyp und -bytes, Port nach Auflösung des Standardports, Methode und eine Anfragebeschreibung, die den Pfad sichtbar macht. Ob Header und Body-Digest zur Entscheidung gehören, hängt von der Aktion ab. Kann ein Bearer-Zugangsdokument mehrere nicht zusammenhängende APIs aufrufen, gehört auch dessen Identität in den Prompt.

Trennen Sie bei SSH einen Netzwerkendpunkt vom entfernten Befehl. Eine Freigabe zum Öffnen einer SSH-Verbindung beschreibt nicht automatisch die Berechtigung, `sudo` auszuführen, Bereitstellungsdateien zu ändern oder einen Port weiterzuleiten. Kann ein Werkzeug nach einer Verbindung mehrere Aktionen ausführen, muss die Oberfläche sagen, was die Sitzungsautorisierung umfasst, und jeden Befehl einzeln protokollieren.

Hier zahlt sich ein sauberes Datenmodell aus. Es verhindert eine verlockende, aber falsche Empfehlung: kanonische Hosts in eine flache Allowlist zu schreiben und die Aufgabe für erledigt zu erklären. Flache Hostlisten sind beliebt, weil sie leicht zu erklären sind. Sie scheitern, weil ein Host nur ein Teil einer Aktion ist und eine spätere DNS-Antwort, ein Port, ein Pfad oder ein Befehl die Wirkung ändern kann.

Eine nützliche Freigabeaufzeichnung kann so aussehen:

```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
```

Die rohe Authority dokumentiert die Anfrage. Die typisierten Felder steuern den Vergleich. Das Anzeigefeld gibt der Person einen stabilen Satz zum Lesen. Verwenden Sie keines dieser Felder als Ersatz für die anderen.

## Ablehnungsregeln müssen langweilig und streng sein

Ein Parser sollte sicher scheitern, wenn eine Eingabe nicht zur Grammatik an ihrer Position passt. Die Fehlermeldung darf hilfreich sein, aber das System sollte eine Adresse nicht reparieren, indem es errät, was der Aufrufer meinte. Ein Normalisierer, der fast gültige Eingaben akzeptiert, schafft eine zweite Grammatik, die Sicherheitsprüfer nur schwer rekonstruieren können.

Lehnen Sie eine IP-Literal-Anfrage ab, wenn sie einen dieser Fehler hat:

- mehr als einen Komprimierungsmarker `::` oder nach der Erweiterung zu viele Felder
- nicht hexadezimale Zeichen in einem Hexadezimalfeld
- einen IPv4-Endteil außerhalb der in der IPv6-Textsyntax erlaubten Position
- Klammern, die an einen Parser für bloße Adressen übergeben werden, oder fehlende Klammern in einer URI-Authority
- eine Zonenkennung in einem Kontext, in dem die Aktion keine lokale Schnittstelle binden kann

Lehnen Sie außerdem ein Literal ab, das Ihr URI-Parser und Ihr Socket-Parser unterschiedlich interpretieren. Das ist keine theoretische Pedanterie. Verschiedene Bibliotheken haben sich im Lauf der Zeit bei Prozentkodierung, ungewöhnlichen IPv4-Formen und Hostparsing unterschiedlich verhalten. Wenn eine Komponente Text freigibt, zu dem eine andere eine Verbindung aufbaut, entsteht eine Autorisierungslücke.

Erstellen Sie einen kleinen ebenenübergreifenden Testkorpus. Führen Sie jeden Fall durch denselben URI-Parser, Host-Extraktor, IP-Parser, Formatierer, Vergleicher und Transport-Builder, die in der Produktion verwendet werden. Prüfen Sie bei akzeptierter Eingabe sowohl die kanonische Anzeige als auch das tatsächliche Socket-Ziel. Prüfen Sie bei abgelehnter Eingabe, dass kein Transportobjekt entsteht.

Ein kompakter Korpus sollte `::`, `::1`, eine vollständig ausgeschriebene Adresse, zwei gleich lange Nullfolgen, ein IPv6-Literal in URI-Klammern mit Port, einen gemappten IPv4-Wert, einen fehlerhaften gepunkteten Endteil, ein bereichsgebundenes Link-Local-Literal und eine kodierte `%25`-Zone in einer URI enthalten. Ergänzen Sie jede Eingabeform, die Agenten in Ihrer Umgebung tatsächlich erzeugt haben. Regressionstests aus echten Freigaben sind weniger glamourös als Parsertricks, fangen aber viel eher den nächsten Fehler ab.

## Die Freigabekarte sollte die Normalisierung sichtbar machen

Eine Person kann einen Endpunkt nicht beurteilen, wenn die Karte den Teil verbirgt, der sich geändert hat. Zeigen Sie das kanonische Ziel auffällig und danach die übergebene Schreibweise, wenn sie abweicht. Eine unaufdringliche Zeile wie `Übergeben als [2001:0DB8:0:0:0:0:0:9]:8443` liefert Prüfern Belege, ohne dass sie Felder im Kopf entschlüsseln müssen.

Die Karte sollte auch Sonderformen ausdrücklich benennen. Kennzeichnen Sie eine gemappte Adresse als `IPv4-gemapptes IPv6`, statt sie als gewöhnliches IPv6-Literal darzustellen. Kennzeichnen Sie eine bereichsgebundene Adresse mit dem Schnittstellennamen und sagen Sie, dass sie lokal zu dieser Schnittstelle ist. Wenn die Aktionsrichtlinie eine gemappte Adresse auf IPv4 projiziert, nennen Sie diese Entscheidung auf der Karte. Bei stillschweigender Gleichwertigkeit werden Prüfer überrascht.

Bei Aufrufen mit hohen Folgen sollte die Person die vollständige Aktion freigeben, nicht nur den kanonischen Host. Eine kompakte Darstellung kann trotzdem die nötigen Details enthalten:

```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 folgt dieser Trennung dort, wo es darauf ankommt: Der Agent erhält niemals das API- oder SSH-Geheimnis, während die App die Aktion ausführt und ihr Ergebnis zurückgibt. Die Autorisierung pro Sitzung kann festlegen, wer einen Lauf gestartet hat, und die Kontrollen pro Aufruf halten sensible Aktionen sichtbar, statt eine frühere Freigabe als Blankoscheck zu behandeln.

## Audit-Aufzeichnungen brauchen Rohtext und normalisierte Bedeutung

Für die Prüfung eines Vorfalls sind zwei Antworten nötig, die sich oft widersprechen, wenn Sie nur eine Darstellung gespeichert haben: Was hat der Agent tatsächlich gesendet, und welches Ziel hat der Transport verwendet? Speichern Sie beides. Machen Sie dann deutlich, welcher Wert die Entscheidung bestimmt hat.

Ein Audit-Ereignis sollte die Rohdaten, den geparsten Hosttyp, die kanonische Anzeigeform, die Adressbytes in einer eindeutigen Kodierung, die Bereichsidentität, falls vorhanden, sowie alle von der Freigabe abgedeckten Aktionsfelder enthalten. Das Ereignis nach der Serialisierung zu hashen ist nur nützlich, wenn die Serialisierung stabil definiert ist. Sonst kann dasselbe semantische Ereignis unterschiedliche Aufzeichnungen erzeugen, weil sich ein Formatierer geändert hat.

Löschen Sie nicht kanonische Eingaben nicht, nachdem Sie die Anzeigeform abgeleitet haben. Sie können auf einen fehlerhaften Client, einen Umgehungsversuch oder eine harmlose Eigenheit einer Bibliothek hinweisen, die später einen Vorfall erklärt. Bewahren Sie sie als Beleg auf, lassen Sie aber nicht zu, dass Such-Dashboards daraus eine irreführende zweite Identität für denselben Endpunkt machen.

Sallyports Activity journal und Sessions journal werden aus einem gemeinsamen verschlüsselten, hash-verketteten Audit-Log abgeleitet, und `sp audit verify` prüft diese Kette offline über Chiffretext. Diese Struktur ist besonders hilfreich, wenn Prüfer eine ungewöhnliche Schreibweise von einer anderen ausgeführten Aktion unterscheiden müssen.

Beginnen Sie mit einem Test, bevor Sie die gesamte Freigabeschicht umgestalten: Übermitteln Sie dasselbe Ziel in vollständig ausgeschriebener Form und in RFC-5952-Form. Wenn Ihr System unterschiedliche Freigabeidentitäten, unterschiedliche Audit-Suchergebnisse oder unterschiedliche Zulassungsentscheidungen erzeugt, liegt die Parsergrenze noch an der falschen Stelle.
