Eine Support-Ticket-API akzeptiert {"title": 7, "description": "<sehr langer Text>", "email": "alice@example.com"}, ohne die Struktur der Anfrage zu prüfen. Ein späterer Formatierungsschritt stürzt aufgrund des numerischen Titels ab, während eine Fehlerbehandlungsroutine die übermittelte Beschreibung und E-Mail-Adresse protokolliert. Der Angreifer benötigte weder ein Benutzerkonto noch eine Exploit-Kette; es genügte eine Anfrage, die die API-Grenze mit Daten passierte, welche der Dienst als vertrauenswürdig einstufte. Die Frage ist, an welcher Stelle der Dienst fehlerhafte, zu umfangreiche oder unerwartete Eingaben abweisen sollte, bevor diese die Geschäftslogik oder die Protokolle erreichen.
Wir werden während des gesamten Kurses einen einzigen FastAPI-Dienst verwenden. POST /tickets nimmt ein neues Ticket entgegen, GET /tickets/{id} ruft eines ab, POST /login authentifiziert einen Benutzer und GET /me gibt den authentifizierten Benutzer zurück. Jeder Endpunkt empfängt Daten, die von einem Client kontrolliert werden – selbst wenn es sich aktuell um einen vertrauenswürdigen Browser handelt. Daher benötigt jeder Endpunkt eine klare Trennung zwischen der empfangenen Eingabe und dem internen Zustand.
Bei der Eingabevalidierung handelt es sich um eine Regel an der Systemgrenze, die nur solche Daten akzeptiert, die dem Vertrag des Dienstes entsprechen. Für POST /tickets bedeutet dies: Eine gültige Anfrage enthält einen kurzen Texttitel, eine Beschreibung mit begrenzter Länge sowie eine E-Mail-Adresse im geforderten Format. Fehlerhaftes JSON führt bereits zu einem früheren Zeitpunkt zum Abbruch, da das Framework aus der ungültigen Syntax keinen Anfragekörper (Request Body) erstellen kann. Der Vorteil liegt nicht nur in einer geringeren Anzahl von Ausnahmen (Exceptions). Abgewiesene Eingaben gelangen niemals in den Ticket-Speicher, in die Antwortgenerierung oder in Diagnoseprotokolle, die personenbezogene Daten preisgeben könnten.
Der Dienst weist drei Grenzen auf, die wir bei der Entwicklung im Blick behalten sollten: das Parsen der Anfrage, die Authentifizierung bei POST /login sowie die Generierung der Antwort. Die erste Grenze bildet unseren Ausgangspunkt, da jede nicht authentifizierte Anfrage sie passiert. Verfolgen wir nun den Weg der vom Client kontrollierten Felder durch den Dienst:
Dieses Diagramm zeigt, dass title, description und email von außerhalb des Dienstes stammen, während das In-Memory-Repository nur Daten empfängt, die die Request-Grenze passiert haben.
Da das Diagramm Client-Daten von Repository-Daten trennt, benötigt POST /tickets Regeln, auf die sich der Handler verlassen kann, anstatt Annahmen, die über den nachfolgenden Code verstreut sind. Der Vertrag definiert die zulässigen Felder, deren Datentypen und Grenzwerte. Ein Titel könnte beispielsweise eine Länge von 1 bis 120 Zeichen zulassen, während eine Beschreibung bis zu 4.000 Zeichen umfassen darf. Grenzwerte dienen der Sicherheit, da sie Speicherbedarf, Verarbeitungsaufwand, Log-Umfang und Antwortgröße begrenzen, noch bevor ein Angreifer diese Werte exzessiv steigern kann.
Wenn der Dienst eine kanonische Form benötigt, gehört die Normalisierung in denselben Grenzbereich. Wir können zwar führende und nachgestellte Leerzeichen aus einem Titel entfernen, bevor wir ihn speichern, sollten jedoch keine ungültige E-Mail-Adresse stillschweigend korrigieren oder eine zu lange Beschreibung eigenmächtig kürzen. Eine Korrektur verändert die Absicht des Clients, während eine Ablehnung die Einhaltung des Vertrags erzwingt und sichtbar macht. Pydantic-Feldvalidatoren können akzeptierte Werte transformieren, müssen jedoch das akzeptierte Ergebnis zurückgeben; Regeln, die auf Rohdaten angewiesen sind, müssen zudem Eingaben beliebiger Typen berücksichtigen.
Was sollte POST /tickets zurückgeben, wenn der Wert für email ungültig ist (z. B. not-an-email)? Der Handler sollte kein Ticket 42 erstellen, und der Client benötigt eine stabile Fehlerstruktur anstelle eines internen Tracebacks. Das folgende Request-Modell verwendet begrenzte Felder und protokolliert das abgelehnte Feld, ohne dabei die ungültige E-Mail-Adresse selbst zu speichern: