7 Min. Lesezeit

Content-Type-Verwechslung bei authentifizierten Agent-API-Aufrufen

Content-Type-Verwechslungen können authentifizierte Agent-Aufrufe an der eigentlichen Absicht vorbeiführen. Teste JSON, Formulare, Multipart und leere Bodies gegen ein einheitliches Schema.

Content-Type-Verwechslung bei authentifizierten Agent-API-Aufrufen

Authentifizierte Agent-Aufrufe müssen vom Netzwerk-Edge bis zum Aktions-Handler einheitlich interpretiert werden. Wenn das Gateway eine harmlose JSON-Anfrage sieht, die Autorisierungsschicht einen Satz von Feldern erhält und der Handler eine privilegierte Formularübermittlung verarbeitet, hat das Zugangsmittel funktioniert und die API ist trotzdem unsicher.

Das ist kein Randthema, das nur alte Browserformulare betrifft. Agents erzeugen HTTP direkt, wiederholen Anfragen sehr aggressiv, übernehmen häufig Beispiele aus Tool-Beschreibungen und arbeiten oft mit Zugangsdaten, die reale Systeme verändern können. Ein Anfrage-Body gehört immer dann zur Autorisierungsentscheidung, wenn er ein Ziel, einen Betrag, eine Umgebung, einen Befehl oder eine Berechtigung auswählt. Medientyp, Syntax und Schema müssen eindeutig sein, bevor du entscheidest, ob der Aufrufer handeln darf.

Eine authentifizierte Anfrage muss trotzdem genau eine Bedeutung haben

Authentifizierung beantwortet die Frage, wer ein Zugangsmittel vorgelegt hat. Autorisierung beantwortet, ob dieser Aufrufer eine Aktion ausführen darf. Keine der beiden Antworten sagt dir, ob alle Komponenten sich auf dieselben Argumente der Aktion geeinigt haben.

Betrachte einen Endpoint, der ein Deployment-Ziel ändert:

POST /v1/deployments/promote HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/json

{"environment":"staging","release":"2026.07.22"}

Der Autorisierungscode kann eine Beförderung nach staging erlauben, aber Produktion ablehnen. Das ist nur dann sicher, wenn er denselben Wert für environment erhält, den auch der Aktions-Handler verwendet. Liest eine Middleware JSON, greift der Handler später auf Formularparameter zu und dürfen beide dieselbe Anfrageobjekt-Struktur befüllen, hast du zwei Quellen der Wahrheit geschaffen.

Dafür ist kein fehlerhaftes kryptografisches Token nötig. Ein Agent mit einer legitimen Sitzung kann einen Body senden, den eine Schicht ignoriert und eine andere berücksichtigt. Ein kompromittierter Agent kann dasselbe tun. Das Ergebnis ist eine Umgehung der Autorisierung, die sich als Eingabeformat äußert.

RFC 9110 sagt, dass Content-Type den Medientyp der zugehörigen Darstellung angibt und sowohl das Datenformat als auch die vorgesehene Verarbeitung durch den Empfänger beschreibt. Der Header gehört damit zur Bedeutung der Anfrage und ist keine Dekoration. Derselbe RFC erlaubt einem Empfänger ohne Content-Type, octet-stream anzunehmen oder die Daten zu untersuchen. Das ist für allgemeine Dateiverarbeitung nützlich, aber ein schlechter Standard für geschützte Aktions-APIs.

Lege für einen Aktions-Endpoint diese Invariante fest:

Genau ein akzeptierter Medientyp ordnet die Anfrage-Bytes genau einem validierten Befehlsobjekt zu. Jede Sicherheitsentscheidung und jeder Seiteneffekt verwendet dieses Objekt.

Der Endpoint kann mehr als eine Darstellung unterstützen. Jede Darstellung braucht dann aber ihren eigenen Vertrag und ihre eigene Testsuite. Behandle mehrere Parser nicht als austauschbare Bequemlichkeit.

Ein Content-Type-Header ist kein Schema

Content-Type: application/json bedeutet nicht: „Das ist die erwartete Form der Anfrage.“ Der Header behauptet nur, dass der Body einen JSON-Medientyp verwendet. Du musst weiterhin entscheiden, ob dieser Typ für die Route unterstützt wird, ob Parameter erlaubt sind, ob der Body syntaktisch gültig ist und ob der dekodierte Wert zum Vertrag der Operation passt.

Ein geschützter Endpoint sollte die erlaubte Menge an Darstellungen bewusst klein halten. Viele Command-Endpoints sollten ausschließlich JSON akzeptieren. Ein Upload-Endpoint kann ausschließlich Multipart akzeptieren. Eine Aktion ohne Argumente sollte überhaupt keinen Body annehmen. Je größer die akzeptierte Menge, desto mehr Parserpfade musst du beherrschen.

Das OWASP REST Security Cheat Sheet gibt eine klare praktische Empfehlung: Unterstützte Content-Types dokumentieren und unerwartete oder fehlende Typen ablehnen. Ein fehlender Content-Type kann für eine Anfrage mit einer Länge von null Bytes erlaubt werden. Außerdem müssen Body und deklarierter Typ zusammenpassen, damit Produzent und Empfänger die Daten nicht unterschiedlich interpretieren.

Für authentifizierte Aktionen braucht diese Empfehlung eine Ergänzung. Gleiche einen behaupteten Typ nicht ab, indem du das erste Zeichen untersuchst und danach einen Parser auswählst. Ein Body, der mit { beginnt, ist keine Erlaubnis, eine als Formulardaten deklarierte Anfrage als JSON zu verarbeiten. Das Erraten des Typs macht aus einem klaren Vertrag eine Implementierungsvermutung.

Ein sinnvoller Routenvertrag sieht so aus:

RouteErlaubter Anfrage-MedientypBody-Regel
POST /v1/deployments/promoteapplication/jsonErforderliches JSON-Objekt passend zu PromoteRequest
POST /v1/artifactsmultipart/form-dataErforderliche Teile passend zu ArtifactUpload
POST /v1/sessions/revokekeinerMuss null Bytes enthalten

Sei bei Parametern des Medientyps genau. Wenn dein JSON-Parser application/json; charset=utf-8 akzeptiert, dokumentiere das und normalisiere Parameter über eine einzige Bibliothek. Akzeptiert er nur das reine application/json, lehne den Parameter ab, statt Proxy und Anwendung unterschiedliche Regeln verwenden zu lassen. Welche Variante du wählst, ist weniger wichtig als ihre einheitliche Anwendung.

Trenne außerdem die Antwortpräferenz Accept vom Content-Type der Anfrage. Ein Client kann eine JSON-Antwort anfordern und gleichzeitig einen ungültigen Anfrage-Body senden. Lass den Accept-Header niemals erweitern, welche Anfrageformate ein Aktions-Endpoint verarbeitet.

JSON braucht mehr Regeln als gültige Syntax

Ein JSON-Parser kann Eingaben erfolgreich verarbeiten, die deine API trotzdem ablehnen muss. Doppelte Elementnamen sind das offensichtliche Beispiel:

{"environment":"staging","environment":"production","release":"2026.07.22"}

RFC 8259 sagt, dass Objektnamen eindeutig sein sollten, und erklärt auch den Grund: Empfänger gehen mit doppelten Namen nicht einheitlich um. Viele behalten den letzten Wert, manche brechen ab und andere geben jedes Paar aus. Das ist ein dokumentiertes Interoperabilitätsproblem, keine theoretische Stilfrage.

Angenommen, eine Autorisierungs-Middleware verwendet einen Parser, der den ersten Wert für environment behält, während ein nachgelagerter Decoder den letzten verwendet. Die Middleware genehmigt staging, der Handler befördert aber nach Produktion. Das lässt sich nicht mit besseren Rollennamen oder einem weiteren Token-Claim reparieren. Lehne die Anfrage ab, bevor eine der beiden Komponenten eine Entscheidung trifft.

Führe dieselbe Prüfung für Werte durch, die in einer lockeren Sprachbindung harmlos aussehen:

  • Lehne unbekannte Objektfelder bei Aktionsanfragen ab, sofern es keinen ausdrücklich begründeten Kompatibilitätsgrund gibt, sie zu behalten.
  • Verlange den erwarteten JSON-Typ. Ein Boolean ist kein String, der zufällig true sagt, und eine ganzzahlige ID ist keine Gleitkommazahl.
  • Begrenze die Body-Größe vor dem Parsen. Ein Schema-Validator schützt keinen Speicher, den du beim Einlesen eines riesigen Bodys bereits erschöpft hast.
  • Lege fest, ob ein Feld fehlen, null oder ein leerer String sein darf. Das sind drei verschiedene Zustände.
  • Lehne nachfolgende Daten und Parser-Erweiterungen wie Kommentare, NaN oder nicht quotierte Namen ab, sofern deine Bibliothek sie anbietet.

Autorisiere nicht direkt aus einer generischen Map. Dekodiere in einen Request-Typ mit explizitem Schema, führe semantische Validierung durch und erstelle anschließend einen internen Befehlstyp, der keine rohen Parser-Artefakte behält. Ein Handler, der PromoteCommand { environment, release } erhält, kann Eingaben schwerer neu interpretieren als ein Handler, der eine Map, eine Query-Sammlung, ein Request-Objekt und den rohen Body erhält.

Bei Zahlen ist besondere Sorgfalt nötig. Die JSON-Grammatik erlaubt große numerische Literale, aber viele Laufzeitumgebungen dekodieren Zahlen standardmäßig als Gleitkommadarstellung. Wenn ein Wert Geld, Quoten, Datenbankeinträge oder eine signierte Nutzlast bezeichnet, verwende ein String-Format oder einen Integer-Parser mit dokumentiertem Wertebereich. Lass nicht zu, dass eine Schicht eine Zahl rundet, bevor eine andere sie vergleicht.

Formular-Bodies erzeugen versteckte Regeln für Arrays und Verschachtelung

application/x-www-form-urlencoded wirkt einfach, weil es einer Query-Zeichenfolge ähnelt. Es ist nicht mehr einfach, sobald Bibliotheken wiederholten Namen, Klammernotation, Pluszeichen und leeren Werten eine Bedeutung geben.

Betrachte diese Bodies:

role=user&role=admin
role[]=user&role[]=admin
role[user]=1&role[admin]=1
role=user%26role%3Dadmin

Unterschiedliche Frameworks können daraus einen letzten oder ersten Skalar, ein Array, ein Objekt, wörtliche Feldnamen oder einen Parserfehler machen. Manche Middleware parst Formulare für jede HTTP-Methode. Manche Anwendungsframeworks führen Query- und Formularparameter in einem Komfortobjekt zusammen. Genau dort verlieren geschützte APIs den Überblick darüber, was der Aufrufer tatsächlich gesendet hat.

Die Testempfehlungen von OWASP zu HTTP-Parameter-Pollution weisen darauf hin, dass das Verhalten vom Zusammenspiel aus Anwendung, Webserver, WAF und Middleware abhängt. Genau deshalb solltest du rohe wiederholte Parameter testen, statt dich auf die Parserdokumentation eines einzelnen Frameworks zu verlassen.

Die verbreitete Empfehlung, aus Gründen der „Client-Kompatibilität“ jeden Endpoint für JSON und URL-kodierte Formulare zu öffnen, ist meistens falsch. Sie hält sich, weil sie das Schreiben eines Demo-Clients erleichtert und viele Frameworks sie standardmäßig aktivieren. Gleichzeitig verdoppelt sie die Darstellungsverträge für jede Aktion und fügt stillschweigend einen dritten Vertrag hinzu, sobald Query-Felder mit dem Body zusammengeführt werden.

Wenn du einen Formular-Endpoint unterstützen musst, gib ihm eine eigene Parser-Richtlinie:

  1. Lehne wiederholte Namen ab, sofern das Schema das Feld nicht als Liste definiert.
  2. Lehne Klammer-Syntax ab, sofern das Schema nicht deren exakte Kodierung definiert und dein Parser sie konsistent implementiert.
  3. Halte Query-Parameter und Formularfelder getrennt. Keine Quelle darf die andere überschreiben.
  4. Wandle die geparsten Felder erst nach der Validierung in denselben typisierten internen Befehl um, den auch die JSON-Route verwendet.
  5. Teste Prozentkodierung, + im Vergleich zu %20, leere Werte, fehlendes = und doppelte Felder über den produktiven Anfragepfad.

Löse das nicht mit der Auswahl „first wins“ oder „last wins“. Das erzeugt zwar innerhalb einer Komponente eine deterministische Antwort, erhält aber die Uneinigkeit an anderer Stelle. Ein geschütztes skalares Feld sollte genau einmal vorkommen.

Multipart ist ein Upload-Protokoll, kein flexibles JSON

Einen Agent-Lauf widerrufen
Das Sitzungsjournal erfasst jeden Agent-Lauf und ermöglicht es dir, eine laufende Sitzung sofort zu widerrufen.

multipart/form-data hat eine legitime Aufgabe: Es überträgt mehrere unabhängig mit Headern versehene Teile, oft mit Dateiinhalt. RFC 7578 definiert es für Formularwerte und verlangt einen Boundary-Parameter, der die Teile trennt. Jeder Teil kann außerdem eigene Header und Dateinamen-Metadaten enthalten.

Diese Struktur macht Multipart zu einer schlechten Fallback-Darstellung für gewöhnliche authentifizierte Befehle. Sie bringt mehr Syntax, mehr Anforderungen an die Größenverwaltung, mehr Stellen für doppelte Feldnamen und mehr Möglichkeiten mit, dass ein Gateway einen Teil prüft, während die Anwendung einen anderen auswählt.

Ein häufig schlechtes Design akzeptiert neben einer Datei einen JSON-Teil metadata und zusätzlich Formularfelder auf oberster Ebene, die die Metadaten überschreiben können:

Content-Disposition: form-data; name="metadata"

{"project":"alpha","visibility":"private"}

Content-Disposition: form-data; name="visibility"

public

Eine Komponente kann anhand von metadata.visibility autorisieren. Eine andere bindet den späteren Formularteil an den Parameter visibility des Handlers. Die Anfrage enthält dann zwei Werte für eine sicherheitsrelevante Eigenschaft, ausgedrückt in zwei Grammatiken.

Entwirf Multipart-Endpoints mit benannten Teilen und klar getrennten Aufgaben. Akzeptiere beispielsweise genau einen Teil file und genau einen Teil manifest. Verlange, dass manifest JSON mit einem eigenen strikten Schema enthält. Lehne jeden Teilnamen ab, der nicht im Upload-Vertrag aufgeführt ist, lehne doppelte Einzelteile ab, setze getrennte Grenzen für die gesamte Body-Größe und die Dateigröße und lege fest, ob Content-Types auf Teilebene erforderlich sind.

Vertraue einem Dateinamen nicht als Pfad, einem MIME-Claim nicht als Dateiklassifizierung und dem Verhalten temporärer Dateien eines Multipart-Parsers nicht als Sicherheitskontrolle. Das sind eigenständige Upload-Probleme. Die Regel für Parser-Verwechslungen bleibt einfacher: Autorisierungseingaben müssen aus einer einzigen benannten und validierten Quelle stammen. Wenn manifest.project bestimmt, wo eine Datei landet, darf kein anderer Teil, kein Query-Parameter und kein Header dieses Projekt ändern.

Wenn ein Befehl keine Datei enthält, akzeptiere kein Multipart. Jeder zusätzliche akzeptierte Medientyp ist eine zusätzliche Möglichkeit, dass zwei Komponenten sich nicht einig sind.

Ein leerer Body ist ein Vertrag, nicht das Fehlen einer Validierung

Manche authentifizierten Aktionen benötigen keine Argumente. Das Widerrufen der aktuellen Sitzung, das Rotieren einer serverseitig erzeugten Nonce oder das Bestätigen eines festen Ereignisses kann einen leeren Anfrage-Body verwenden. In solchen Fällen muss die Leere durchsetzbar sein.

Ein Endpoint ohne Body sollte alle folgenden Anfragen ablehnen:

POST /v1/sessions/revoke HTTP/1.1
Content-Type: application/json
Content-Length: 2

{}
POST /v1/sessions/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

scope=other
POST /v1/sessions/revoke HTTP/1.1
Transfer-Encoding: chunked

0

Das letzte Beispiel enthält keinen Inhalt, verwendet aber weiterhin einen Framing-Mechanismus, den dein Vertrag ohne Body möglicherweise verbieten soll. Ob du es ablehnst, hängt von deinem HTTP-Stack ab. Entscheide das am Edge und teste es dort. Lass keinen Proxy ein Framing durchreichen, das die Anwendung anders behandelt.

Für eine Route ohne Body gelten vor der Geschäftslogik diese Regeln:

  • Die Anfrage enthält keine Content-Bytes.
  • Die Route akzeptiert keinen Content-Type, außer eine ausdrücklich dokumentierte Kompatibilitätsregel erlaubt ihn.
  • Die Route führt Query-Parameter nicht in den Befehl zusammen, sofern nicht jeder erlaubte Query-Name ein eigenes Schema besitzt.
  • Der Server zeichnet die Aktion als argumentfrei auf, statt ein generisches Request-Objekt zu protokollieren, das spätere Leser fälschlich für Eingabe halten könnten.

RFC 9110 beschreibt Anfrageinhalte anhand der Semantik der Methode. Ein Body erhält nicht allein deshalb eine universelle Bedeutung, weil die Anfrage POST verwendet. Diese Bedeutung liefert dein Ressourcenvertrag.

Der schwierige Fall ist eine Client-Bibliothek, die immer {} sendet. Erweitere den Endpoint nicht nur deshalb. Repariere den Client oder gib ihm eine separate, dokumentierte Route. Ein Body, der derzeit keine Wirkung hat, wird durch eine spätere Änderung am Handler leicht zu einem unbeabsichtigten Eingabekanal.

Vor der Autorisierung validieren und den validierten Befehl ausführen

Jeden neuen Prozess autorisieren
Verwende standardmäßig eine Autorisierung pro Sitzung, statt jeden neuen Agent-Prozess stillschweigend zu vertrauen.

Die sicherste Anfragepipeline verläuft in eine Richtung. Rohe Bytes kommen hinein. Eine Route wählt genau einen erlaubten Parser. Der Parser erzeugt einen typisierten Wert. Die Validierung erzeugt einen kanonischen Befehl. Die Autorisierung bewertet diesen Befehl. Der Executor erhält denselben Befehl.

raw HTTP request
  -> route and media-type check
  -> bounded body read
  -> one strict parser
  -> schema and semantic validation
  -> canonical command
  -> authorization
  -> execution and audit record

Vertausche die mittleren beiden Schritte nicht. Die Autorisierung braucht oft Felder wie Projekt-ID, Umgebung, Empfänger oder Befehlsmodus. Deshalb sind Teams versucht, frühzeitig lose geparste Eingaben zu prüfen. Dadurch entsteht ein Parser vor der Autorisierung, dessen Verhalten für immer mit dem finalen Decoder übereinstimmen müsste. Nur wenige Systeme halten dieses Versprechen.

Der kanonische Befehl ist eine praktische Grenze, kein Diagrammmuster. Er sollte nur die Werte enthalten, die der Executor benötigt, und rohen Body-Text, Formularsammlungen, Framework-Request-Objekte und Aliase ausschließen. Wenn dein Executor target_environment erhält, darf er später nicht req.query.environment heranziehen, nur weil das Ziel fehlte oder unbequem war.

Das verbessert auch Audit-Aufzeichnungen. Protokolliere den authentifizierten Principal, Endpoint, akzeptierten Medientyp, einen Request-Digest, die sicher speicherbaren Felder des kanonischen Befehls, die Autorisierungsentscheidung und das Ergebnis. Das standardmäßige Protokollieren roher Request-Bodies schafft ein zweites Problem, weil Bodies Zugangsdaten, hochgeladene Dateien und Benutzerdaten enthalten können. Ein Digest ermöglicht die Zuordnung zu gesicherten Belegen, ohne Logs in einen Geheimnisspeicher zu verwandeln.

Für das Signieren von Anfragen gilt dieselbe Disziplin. Wenn ein Client Bytes signiert, der Server aber ein normalisiertes Objekt autorisiert, müssen sowohl die Regeln für die signierte Darstellung als auch die Regeln für die Kanonisierung aufgezeichnet werden. Wenn der Client ein kanonisches Objekt signiert, lehne alle alternativen Kodierungen ab, bevor du die Signatur prüfst. Andernfalls können zwei Bytefolgen dieselbe Geschäftsanforderung tragen oder eine Folge nach dem Parsen eine andere Bedeutung erhalten.

Teste die Uneinigkeit, nicht nur den erfolgreichen Parser

Unit-Tests, die ein gültiges JSON-Fixture deserialisieren, beweisen fast nichts über die Übereinstimmung von Parsern. Dein Testziel ist der öffentliche Anfragepfad: Load Balancer oder Reverse Proxy, Gateway, Framework-Middleware, Routen-Handler und jeder Service, der den Body erneut parst.

Erstelle für jede authentifizierte Operation eine kompakte Sammlung negativer Fälle. Sie sollte in CI gegen eine kurzlebige Umgebung laufen und sowohl die Antwort als auch das Ausbleiben von Seiteneffekten prüfen. Eine 400-Antwort reicht nicht, wenn bereits eine Queue-Nachricht, ein Audit-Ereignis oder ein teilweiser Dateischreibvorgang entstanden ist.

Beginne mit diesem Shell-Harness. Es sendet absichtlich rohe Bodies, statt sich auf einen generierten Client zu verlassen, der fehlerhafte Eingaben ablehnt:

base=https://api.test.example
bearer='test-token'

send() {
  name=$1
  type=$2
  body=$3
  code=$(curl -sS -o "/tmp/${name}.out" -w '%{http_code}' \
    -X POST "$base/v1/deployments/promote" \
    -H "Authorization: Bearer $bearer" \
    -H "Content-Type: $type" \
    --data-binary "$body")
  printf '%-28s %s\n' "$name" "$code"
}

send valid_json 'application/json' \
  '{"environment":"staging","release":"2026.07.22"}'
send duplicate_json 'application/json' \
  '{"environment":"staging","environment":"production","release":"2026.07.22"}'
send form_body 'application/x-www-form-urlencoded' \
  'environment=production&release=2026.07.22'
send false_json 'application/json' \
  'environment=production&release=2026.07.22'

Die erwartete Form der Ausgabe sollte aus einem Erfolg und drei Ablehnungen durch den Client bestehen:

valid_json                   200
 duplicate_json               400
form_body                    415
false_json                   400

Deine tatsächliche Statuskonvention kann für eine syntaktisch gültige Anfrage, die am Schema scheitert, 422 zurückgeben. Wichtig ist die Unterscheidung: Ein Medientyp-Konflikt darf nie einen Fallback-Parser erreichen, und ein doppeltes JSON-Element darf nie die Autorisierung erreichen.

Erweitere die Sammlung um Fälle, die Grenzen zwischen Komponenten testen:

FallErwartetes Verhalten
Fehlender Content-Type bei nicht leerem BodyVor dem Parsen ablehnen
JSON-Objekt mit unbekanntem FeldAblehnen oder dokumentiertes Kompatibilitätsverhalten protokollieren
Wiederholter skalarer FormularwertAblehnen
Query-Wert widerspricht JSON-WertAblehnen oder Query gemäß Routenvertrag ignorieren
Multipart enthält zwei manifest-TeileAblehnen
Route ohne Body erhält {}Ablehnen

Prüfe anschließend die Audit-Spur. Jede abgelehnte Eingabe sollte eine Spur hinterlassen, die Route und Ablehnungsklasse nennt, ohne vertrauliche Anfrageinhalte zu protokollieren. Jede akzeptierte Eingabe sollte genau einen kanonischen Befehl erzeugen. Wenn die Logs zeigen, dass das Gateway ein Ziel sah und der Handler ein anderes aufzeichnete, hast du eine Uneinigkeit gefunden, selbst wenn der Test eine 2xx-Antwort erhalten hat.

Proxies und Middleware sind ebenfalls Parser

Das Aktions-Gateway betreiben
Halte die Grenze für Zugangsdaten in einer signierten, ständig laufenden macOS-Menüleisten-App.

Teams zeigen oft auf den Parser der Anwendung und vergessen die Komponenten davor. Reverse Proxies können Header normalisieren. API-Gateways können JSON untersuchen, um eine Regel anzuwenden. WAFs können Formulardaten parsen. Observability-Middleware kann einen Body lesen und neu zusammensetzen. Ein Framework kann Query-, Formular- und JSON-Felder befüllen, bevor der Routen-Handler läuft.

OWASPs Hinweise zu HTTP-Request-Smuggling beschreiben die größere Variante dieses Problems: Intermediäre und Backend-Server können Anfragegrenzen unterschiedlich interpretieren, besonders bei Protokollübersetzung und Framing. Content-Type-Verwechslung braucht kein Request-Smuggling, um gefährlich zu sein. Beide Fehler entstehen jedoch dadurch, dass verschiedene Schichten inkompatible Parsing-Entscheidungen treffen dürfen.

Erfasse jeden Body-Leser im Aktionspfad. Notiere für jeden, welche Medientypen er parst, ob er doppelte Werte behält, ob er Inhalte dekomprimiert, ob er eine Größenbegrenzung durchsetzt und ob er den Body umschreiben kann. Wenn niemand diese Fragen beantworten kann, ist der Endpoint noch nicht für Agent-Zugangsdaten bereit.

Halte die Rolle des Gateways eng. Es kann Routen-Level-Bodysizes erzwingen und Medientypen blockieren, die eine Route niemals akzeptiert. Es kann auch fehlerhafte Header vor der Anwendung ablehnen. Verwende eine Gateway-Transformation jedoch nicht, um Formulardaten in JSON umzuwandeln oder doppelte Felder zu „bereinigen“. Die Anwendung muss Mehrdeutigkeit anhand genau der Semantik ablehnen, die sie ausführt.

Teste die HTTP-Versionen und Deployment-Pfade, die in der Produktion tatsächlich verwendet werden. Eine Anfrage, die sich gegen einen lokalen Entwicklungsserver korrekt verhält, kann sich ändern, wenn ein HTTP/2-Client einen Proxy erreicht, der HTTP/1.1 an die Anwendung weiterleitet. Es geht nicht darum, ein Forschungslabor für Angriffe aufzubauen. Die Produktionskette muss nachweisen, dass sie für jede akzeptierte Anfrage genau ein Befehlsobjekt erzeugt.

Agent-Gateways müssen diese Grenze bewahren

Ein Agent-Gateway sollte Zugangsdaten vom Modell fernhalten und eine Aufzeichnung der Aktion bewahren. Es kann eine großzügige Ziel-API aber nicht allein sicher machen. Das Gateway muss eine Darstellung senden, die die Zielroute ausdrücklich unterstützt, und das Ziel muss diese Darstellung validieren, bevor es die Berechtigung bewertet.

Sallyports HTTP-Kanal fügt Zugangsdaten ein, während API-Schlüssel außerhalb des Agents bleiben. So kann ein Agent eine Aktion anfordern, ohne das Geheimnis selbst zu erhalten. Das ist eine nützliche Grenze für Zugangsdaten. Kombiniere sie mit Endpoint-Verträgen, die mehrdeutige Bodies ablehnen, denn geschützte Zugangsdaten autorisieren weiterhin die Anfrage, die die API erreicht.

Gib Agents Tools, die den Vertrag abbilden, statt für sensible Systeme eine generische Aktion „beliebige HTTP-Anfrage senden“ bereitzustellen. Ein Promotion-Tool sollte typisierte Argumente environment und release akzeptieren. Seine Implementierung sollte ein JSON-Objekt, einen Medientyp und nichts anderes serialisieren und Tool-Eingaben ablehnen, die das API-Schema nicht erfüllen können. Der empfangende Service muss die Validierung wiederholen. Tool-Schemas reduzieren Fehler, ersetzen aber nicht das Misstrauen auf der Serverseite.

Wenn ein Agent einen Upload benötigt, mache daraus ein eigenes Tool mit einer benannten Datei und einem benannten Manifest. Wenn er eine Aktion ohne Argumente braucht, biete überhaupt kein Body-Feld an. Diese kleinen Einschränkungen machen die beabsichtigte Anfrage leichter prüfbar, genehmigbar, in einer Testumgebung wiederholbar und später auditierbar.

Genehmige keine vage Fähigkeit und erwarte dann, dass Parser die fehlende Präzision liefern. Sorge dafür, dass der Endpoint genau eine Bedeutung akzeptiert, dass der Agent diese Bedeutung sendet und dass jede alternative Schreibweise abgelehnt wird, bevor ein Zugangsmittel sie autorisieren kann.

FAQ

Was bedeutet Content-Type-Verwechslung in einer API?

Eine Uneinigkeit zwischen Komponenten darüber, was eine authentifizierte HTTP-Anfrage bedeutet. Gateway, Schema-Validator, Framework-Parser, Autorisierungsprüfung und Handler können dieselben Bytes unterschiedlich interpretieren. Dadurch kann eine Anfrage unter einer Bedeutung eine Kontrolle passieren und unter einer anderen ausgeführt werden.

Warum ist Content-Type-Verwechslung für KI-Agents riskant?

Agents verschärfen das Risiko, weil sie schnell viele authentifizierte Anfragen senden können und eine Sitzung möglicherweise weitreichende Aktionsrechte besitzt. Die API muss jeden Aufruf als nicht vertrauenswürdige Eingabe behandeln, selbst wenn ein Mensch den Agent-Prozess freigegeben hat.

Garantiert application/json eine sichere JSON-Anfrage?

Nein. application/json gibt nur den deklarierten Medientyp an. Es garantiert weder gültiges JSON noch eindeutige Objektschlüssel, korrekte Feldtypen oder eine erlaubte Anfrageform. Verarbeite JSON streng, lehne doppelte Namen ab und prüfe den resultierenden Wert anschließend gegen das Endpoint-Schema.

Sollte eine JSON-API Anfragen mit application/x-www-form-urlencoded akzeptieren?

Lehne sie ab, sofern der Endpoint Formulardaten nicht ausdrücklich akzeptiert und dafür einen eigenen, vollständigen Vertrag besitzt. Wandle Formularfelder nicht vor der Autorisierung in ein JSON-ähnliches Objekt um, weil wiederholte Felder und Klammer-Syntax je nach Bibliothek unterschiedliche Bedeutungen haben können.

Wann sollte eine authentifizierte API multipart/form-data erlauben?

Nur wenn der Endpoint Datei-Uploads benötigt oder ein bestehendes Client-Protokoll Multipart voraussetzt. Behandle Multipart als eigenen Parser- und Schema-Pfad, beschränke Teilnamen und Teil-Header und akzeptiere es nicht stillschweigend als eine andere Schreibweise für eine JSON-Aktion.

Kann ein authentifizierter POST-Endpoint einen leeren Body verlangen?

Ja, wenn die Operation keine Anfrageparameter benötigt. Ein Endpoint mit leerem Body braucht einen ausdrücklichen Vertrag ohne Body und muss Content-Types, Transfer-Encoding und Bytes ablehnen, die daraus eine andere Operation machen wollen.

Welchen Statuscode sollte eine API bei einem falschen Content-Type zurückgeben?

Ein strikter Endpoint gibt einen Clientfehler zurück, bevor er die Autorisierung prüft oder Geschäftslogik ausführt. Verwende 415 für einen nicht unterstützten Medientyp, 400 für fehlerhafte Syntax und 422, wenn syntaktisch gültige Daten am Endpoint-Schema scheitern, sofern diese Unterscheidung zu euren API-Konventionen passt.

Wie teste ich eine API auf Uneinigkeit zwischen Parsern?

Teste rohe Anfragen über denselben öffentlichen Edge-, Gateway- und Anwendungspfad wie in der Produktion. Variiere für jede geschützte Operation Medientyp, doppelte Felder, wiederholte Parameter, Multipart-Teil-Header, Body-Länge und Content-Encoding. Prüfe anschließend, dass jede ungültige Variante vor der Aktionsschicht scheitert.

Kann ein API-Gateway oder eine WAF Parser-Verwechslungen allein lösen?

Nein. Eine WAF oder ein API-Gateway kann offensichtlich fehlerhafte Eingaben ablehnen, ist aber selbst ein Parser in der Kette und kann eine andere Interpretation einführen. Die Anwendung, die die Aktion autorisiert und ausführt, muss weiterhin eine kanonische Darstellung selbst parsen und validieren.

Sollte eine menschliche Genehmigung die Schema-Prüfung pro Anfrage ersetzen?

Die Genehmigung sollte den Agent-Prozess und die ihm erteilte Fähigkeit abdecken. Die API muss aber weiterhin jeden Anfrage-Body prüfen, als stamme er von feindseligem Code. Eine menschliche Genehmigung ist kein Format-Validator und kann Mehrdeutigkeiten nicht reparieren, die nach dem Senden der Anfrage entstehen.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov