Les en-têtes d'authentification personnalisés peuvent-ils être injectés sans danger ?
Les en-têtes d'authentification personnalisés doivent être validés au niveau des octets avant leur injection par une passerelle. Rejetez les sauts de ligne, les pièges Unicode et les champs en double.

Une passerelle qui injecte des identifiants doit traiter un en-tête HTTP comme une syntaxe de protocole, pas comme un simple champ de texte. Si un agent, un écran de configuration, un import depuis un coffre ou un modèle peut introduire un octet inattendu dans ce champ, la passerelle peut envoyer une requête dont le sens diffère de ce que suggèrent l'écran d'approbation, l'audit ou le service destinataire.
La règle sûre est étroite : réservez chaque champ d'authentification à la passerelle, validez les octets finaux juste avant de construire la requête et rejetez les entrées mal formées au lieu de les nettoyer. Vous détecterez ainsi les sauts de ligne, les pièges Unicode, les caractères de contrôle masqués et les noms de champs en double avant qu'un identifiant ne quitte le stockage local.
La limite d'injection a besoin d'un contrat au niveau des octets
Un en-tête d'authentification personnalisé doit suivre un contrat plus strict que l'analyse HTTP générique. L'analyse générique doit prendre en charge un Internet vaste et hétérogène. Une passerelle qui transfère un secret d'un coffre vers une requête a une tâche plus limitée et devrait donc accepter moins de choses.
Commencez par séparer quatre éléments que les équipes confondent souvent sous le même terme de « valeur d'en-tête » :
- Le nom du champ configuré, par exemple
X-Api-Key. - Les octets secrets stockés pour ce champ.
- Les en-têtes de requête fournis par l'agent pour une action donnée.
- La liste finale des en-têtes sortants remise au client HTTP.
Chacun a un propriétaire différent. Un administrateur ou un développeur configure le nom. Le coffre possède le secret. L'agent peut demander une opération HTTP et fournir des en-têtes applicatifs ordinaires. La passerelle est la seule à assembler le champ protégé final.
Cette distinction est importante, car un agent ne devrait jamais recevoir un secret simplement pour le concaténer avec un nom d'en-tête. Elle évite aussi une erreur plus subtile : laisser l'agent fournir x-api-key pendant que la passerelle ajoute ensuite X-Api-Key. Les noms de champs HTTP ne tiennent pas compte de la casse. Il s'agit donc de deux instances concurrentes du même champ, pas de deux en-têtes indépendants. RFC 9110 définit les noms de champs comme insensibles à la casse et décrit les champs qui n'acceptent qu'un seul membre comme des champs singleton.
Pour un en-tête d'identification, rendez le contrat au niveau des octets explicite :
- Le nom utilise uniquement le jeu de caractères des jetons HTTP et est conservé sous une forme canonique en minuscules pour les comparaisons.
- La valeur est une séquence non vide d'octets ASCII imprimables compris entre
0x21et0x7e. - La valeur ne contient ni espace, ni tabulation, ni CR, ni LF, ni NUL, ni autre octet de contrôle.
- La passerelle ajoute exactement une instance du nom protégé.
- Une requête d'agent qui nomme le champ protégé échoue. Elle n'est pas modifiée silencieusement.
Cette politique rejette volontairement certains contenus de champs autorisés par HTTP en général. C'est précisément l'objectif. Une clé API, un jeton bearer ou un identifiant fournisseur ne devrait pas avoir besoin d'une tabulation, d'un espace initial ou d'un caractère accentué pour circuler sans danger dans un en-tête personnalisé. Si un fournisseur utilise un format nécessitant des octets arbitraires, encodez-les avant le stockage avec la représentation de transport documentée par ce fournisseur. Base64url est courant lorsqu'un protocole exige un jeton imprimable. N'inventez pas de conversion avec perte dans la passerelle.
L'alternative la plus tentante consiste à accepter du texte arbitraire, à supprimer les espaces, à remplacer les caractères de contrôle par des espaces et à compter sur la bibliothèque HTTP pour rejeter le reste. Vous créez alors plusieurs représentations d'un même identifiant. L'interface peut afficher une chaîne, le journal en contenir une autre et la bibliothèque en transmettre une troisième. Un contrôle de sécurité doit décider une fois si une action est valide. Il ne doit pas transformer l'action en autre chose.
CR, LF et NUL doivent échouer avant la construction de la requête
Rejetez le retour chariot (0x0d), le saut de ligne (0x0a) et NUL (0x00) partout dans la valeur d'un en-tête protégé. Rejetez un CR ou un LF isolé avec la même fermeté que la séquence CRLF bien connue.
HTTP/1.1 utilise CRLF pour délimiter les lignes de champs. RFC 9112 permet aussi aux destinataires de reconnaître un LF isolé dans certaines circonstances. Cette tolérance explique précisément pourquoi une passerelle sortante ne peut pas supposer que tous les composants en aval réagiront de la même façon. La norme précise que les émetteurs ne doivent pas créer de CR isolé dans les éléments du protocole et elle déconseille les anciennes lignes de champs repliées.
RFC 9110 est encore plus explicite pour les valeurs de champs : CR, LF et NUL sont invalides et dangereux, car les implémentations peuvent les analyser différemment. La norme indique que les destinataires doivent les rejeter ou les remplacer par des espaces avant tout traitement ultérieur. Le remplacement est une règle de récupération destinée au destinataire, pas une bonne conception de passerelle. Une passerelle sait qu'elle crée une nouvelle requête. Elle devrait donc rejeter l'ambiguïté au lieu de la transmettre.
Considérez une valeur qui arrive au constructeur de requête comme ceci :
team-secret\r\nX-Approval: bypassed
Un validateur correct voit deux octets interdits et refuse l'action. Un validateur faible qui recherche uniquement les caractères littéraux \\r\\n voit des antislashs inoffensifs. Un validateur exécuté avant l'expansion des modèles peut examiner ${TOKEN} sans jamais inspecter la valeur développée. Un validateur qui remplace le saut de ligne par un espace transforme un refus évident en une requête contenant un identifiant mal formé et modifié.
Le seul contrôle utile s'effectue sur la séquence finale d'octets, après le décodage et l'interpolation, avant que la bibliothèque HTTP n'accepte les en-têtes. Il devrait produire un résultat de cette forme :
reject header value: field=x-api-key reason=forbidden-byte byte=0x0a offset=11
Ce message est destiné à l'enregistrement local de l'action, pas à un appelant externe. Il identifie le champ configuré et le mauvais octet sans recopier la valeur. Évitez d'enregistrer une forme entre guillemets, échappée en JSON, encodée en base64 ou normalisée du secret. Ces formes restent des éléments secrets et ont tendance à survivre plus longtemps dans les journaux que la requête elle-même.
Ne limitez pas cette règle à HTTP/1.1. HTTP/2 transporte les champs dans des blocs compressés plutôt que dans des lignes de texte, mais RFC 9113 interdit toujours NUL, LF et CR à toute position d'une valeur de champ. La norme avertit explicitement que des champs non validés peuvent provoquer une désynchronisation de requête lorsqu'un intermédiaire les convertit en HTTP/1.1. HTTP/3 fait la même observation pour les champs qui franchissent ensuite la frontière d'un protocole textuel.
Les noms d'en-têtes ont besoin de règles de propriété ASCII
Rejetez les noms d'en-têtes non ASCII. Ne les normalisez pas, ne les translittérez pas et n'essayez pas de décider qu'un caractère ressemblant est « suffisamment proche » d'un nom protégé.
Les noms de champs HTTP sont des jetons. La règle générique exclut les espaces, les caractères de contrôle, les délimiteurs et les caractères non ASCII. HTTP/2 ajoute une règle de transport : les noms doivent être en minuscules et les caractères ASCII non visibles, les majuscules et les octets supérieurs à 0x7f sont rejetés. Une passerelle peut appliquer une règle encore plus simple : valider le nom configuré comme un jeton HTTP ASCII, le convertir en minuscules selon les règles ASCII, puis conserver son orthographe d'origine uniquement pour l'affichage.
La comparaison doit s'effectuer au niveau des octets. Si le nom protégé est x-api-key, les noms fournis par l'agent ci-dessous doivent tous entrer en collision avec lui après conversion ASCII en minuscules :
X-Api-Key
x-api-key
X-API-KEY
Les noms suivants doivent échouer directement à la validation, sans être réparés :
x-api‐key // Unicode hyphen
x-api-kеy // Cyrillic e
x-api-key\n
x api key
Le commentaire affiché dans ce scénario est important. Une personne peut ne pas voir la différence entre un trait d'union ASCII et un trait d'union Unicode, ou entre un e ASCII et un е cyrillique. Un analyseur ne devrait pas avoir besoin d'être expert en typographie. Refusez tous les noms d'en-têtes non ASCII et l'ambiguïté disparaîtra.
La propriété est distincte de la syntaxe. Un nom peut être syntaxiquement valide tout en étant interdit parce que la passerelle en est propriétaire. Conservez un ensemble de noms protégés en minuscules ASCII. Comparez chaque en-tête de requête fourni par l'agent à cet ensemble avant de combiner les collections d'en-têtes. Construisez ensuite la liste sortante à partir de zéro :
en-têtes ordinaires de l'agent ayant passé la validation
+ en-tête d'identification appartenant à la passerelle
+ métadonnées de requête appartenant à la passerelle, le cas échéant
Ne partez pas de la liste de l'agent pour remplacer ensuite certaines entrées. Certaines bibliothèques HTTP conservent les valeurs répétées, certaines les regroupent et certaines exposent les en-têtes sous forme de carte, ce qui fait perdre la duplication d'origine. Dès qu'un champ protégé est entré dans la mauvaise collection, il est déjà trop tard pour faire confiance à un simple remplacement.
La normalisation Unicode n'est pas une opération de réparation
Ne normalisez pas les valeurs d'authentification en Unicode au moment de l'injection. La normalisation a un rôle légitime dans les systèmes qui définissent l'égalité entre des textes, mais un secret est généralement une séquence opaque d'octets dont l'émetteur décide le sens.
Les formes de normalisation Unicode, spécifiées par l'Annexe Unicode standard n° 15, décrivent des transformations entre des représentations canoniquement ou volontairement équivalentes. Cela ne rend pas ces transformations sûres pour un identifiant. Les deux chaînes café et café peuvent sembler identiques tout en utilisant des séquences de points de code différentes. Un fournisseur peut rejeter l'une, accepter l'autre, appliquer un hachage différent ou les considérer comme deux secrets distincts. Une passerelle ne peut pas deviner le comportement choisi par le fournisseur.
Il faut distinguer deux questions de normalisation :
Ne normalisez les champs d'identité que lorsque leur protocole le définit
Un produit peut définir la manière de comparer les noms d'utilisateur, les libellés d'affichage ou les métadonnées applicatives. Appliquez cette règle à la frontière où le produit possède ce sens et conservez une trace claire de la représentation choisie. Ne réutilisez pas cette règle pour l'injection d'identifiants HTTP simplement parce que les deux entrées sont arrivées sous forme de chaînes.
Les noms d'en-têtes sont plus simples. Ce sont des identifiants de protocole, pas du texte destiné à être lu par un utilisateur. Rejetez les entrées non ASCII et utilisez les minuscules ASCII pour les comparaisons. NFC, NFD, NFKC, NFKD, la conversion de casse Unicode et les squelettes de caractères confondables n'ont pas leur place dans cette décision.
N'utilisez jamais la comparaison de caractères ressemblants pour fusionner des en-têtes protégés
La norme technique Unicode n° 39 fournit des données de détection des caractères confondables pour les audits de sécurité. Elle précise explicitement que les correspondances de squelettes confondables ne doivent pas devenir une normalisation des identifiants. Cet avertissement est fondé : une correspondance qui aide à signaler un libellé suspect n'est pas une règle sûre pour modifier silencieusement une entrée de protocole.
Utilisez la détection des caractères confondables dans une interface de configuration si vous souhaitez avertir qu'une personne a saisi un libellé étrange. Ne l'utilisez pas dans le chemin de requête. Celui-ci doit dire : ce nom de champ est ASCII et réservé, ou il est invalide. Cette valeur est en ASCII imprimable et exacte, ou elle est invalide.
Tester la normalisation Unicode reste nécessaire, même avec une politique d'identifiants ASCII. Cela permet de révéler les conversions cachées dans les interfaces et les couches de stockage. Faites passer une valeur décomposée, son équivalent composé, un espace insécable, un caractère de largeur nulle et un caractère de contrôle de droite à gauche dans chaque chemin d'entrée. Pour une valeur d'authentification personnalisée, le résultat correct est un rejet avec la même catégorie générique : octet ou caractère non ASCII. Le résultat ne doit pas dépendre de l'origine du texte, qu'il provienne d'un écran de réglages, d'un fichier importé ou d'un argument d'agent.
Les champs en double constituent un échec d'autorisation
Pour un nom d'authentification protégé, un champ en double doit refuser l'action même si les deux valeurs sont identiques. Un seul propriétaire est plus simple à auditer, à approuver et beaucoup moins dépendant du comportement du destinataire.
On entend souvent que les doublons sont inoffensifs lorsque les valeurs sont identiques. Cet argument concerne le nettoyage des données, pas l'autorisation. Un doublon peut apparaître avant ou après qu'un proxy réordonne les champs. Le destinataire peut choisir la première instance, la dernière, concaténer les instances, rejeter la requête ou appliquer une règle propre au champ. Un intermédiaire peut faire un choix différent. L'approbation de l'action ne décrit alors plus une requête unique et non ambiguë.
Ne résolvez pas ce problème en considérant Authorization comme spécial et chaque en-tête X-... comme secondaire. Un fournisseur peut définir son champ d'identification personnalisé comme étant à valeur unique, et la plupart des mécanismes d'authentification le sont. La passerelle doit conserver une liste explicite des noms protégés pour chaque connexion ou liaison d'identifiant. Cette liste peut inclure authorization, x-api-key, x-api-token ou un champ fournisseur configuré par le développeur. Elle ne doit pas dépendre d'une convention de nommage.
La détection des doublons doit avoir lieu avant que la bibliothèque cliente HTTP ne transforme la liste d'en-têtes dans sa propre représentation. De nombreuses API pratiques utilisent une carte associant un nom insensible à la casse à une liste de chaînes. Cette structure conserve les doublons si elle est utilisée avec précaution, mais peut masquer l'ordre et la propriété des sources. D'autres API proposent une méthode set qui remplace une valeur et une méthode add qui en ajoute une autre. Ces opérations ne sont acceptables qu'après le rejet par la passerelle des tentatives de l'agent de toucher aux noms protégés.
Utilisez ce tableau de décision pour chaque en-tête fourni par l'agent :
| Condition | Résultat de la passerelle |
|---|---|
| Nom de champ invalide | Refuser l'action |
| Nom protégé, quelle que soit sa casse ASCII | Refuser l'action |
| Nom ordinaire en double, si le protocole destinataire l'autorise | Le conserver uniquement au moyen d'une règle explicite par nom |
| Nom ordinaire en double aux sémantiques inconnues | Refuser l'action ou exiger une règle propre à la connexion |
| Champ d'identification de la passerelle | L'ajouter exactement une fois après validation des en-têtes de l'agent |
La troisième ligne est volontairement étroite. Accept peut avoir une sémantique de liste. Un champ applicatif personnalisé peut ne pas l'avoir. Si la passerelle ignore si la répétition modifie le sens, elle ne doit pas inventer de règle. C'est ainsi qu'une fonction générique de « transfert de tous les en-têtes » devient discrètement un moyen de contourner l'intention de la personne qui a approuvé l'action.
Validez chaque frontière de transformation, puis le résultat final
Un contrôle précoce ne suffit pas, car les valeurs de requête changent souvent de forme après le premier contrôle. Chaque frontière de transformation capable de créer, décoder, joindre ou substituer des octets a besoin d'un invariant local, et le constructeur final de requête doit effectuer la validation décisive.
Un flux pratique comporte cinq frontières :
- Lorsqu'une personne configure le nom d'en-tête, validez sa syntaxe de jeton ASCII et réservez sa forme en minuscules.
- Lorsqu'un secret entre dans le stockage, validez le contrat de l'identifiant et stockez exactement les octets approuvés.
- Lorsqu'un agent fournit une opération, validez les noms et valeurs des en-têtes ordinaires, puis rejetez les noms protégés.
- Après les modèles, les lectures de fichiers, l'expansion de l'environnement et le décodage d'une requête structurée, validez encore les valeurs, car ces opérations peuvent avoir créé de nouveaux octets.
- Juste avant l'appel à la bibliothèque HTTP, validez la liste complète des champs et vérifiez que chaque nom protégé apparaît une seule fois.
Le contrôle final fait autorité, car il voit l'objet sortant réel. Les contrôles précédents servent à fournir de bons messages d'erreur et à empêcher l'entrée d'un état invalide dans le système. Ils ne le remplacent pas.
C'est aussi pourquoi la validation doit se trouver dans le cœur de la passerelle plutôt que dans la description d'un outil MCP, une invite ou un composant auxiliaire de l'agent. Ces endroits peuvent décrire une requête souhaitée. Ils ne peuvent pas garantir ce qui atteint le socket. La barrière qui possède l'injection de l'identifiant doit posséder le dernier contrôle des octets.
Sallyport applique la partie utile de cette séparation : l'application effectue elle-même l'injection des identifiants HTTP et l'agent reçoit le résultat, pas le secret. Pour un identifiant d'en-tête personnalisé, la couche d'action doit néanmoins rejeter toute copie du champ réservé fournie par l'agent avant qu'elle n'atteigne le point d'injection.
Gardez les erreurs de validation déterministes. Une requête qui contient un CR à l'octet 8 et un en-tête protégé en double doit signaler le premier échec selon un ordre fixe, par exemple nom invalide, collision avec un nom protégé, valeur invalide, puis cardinalité finale. Le déterminisme rend les tests fiables et empêche un attaquant d'utiliser des différences de messages pour apprendre davantage sur les identifiants stockés.
Rendez le validateur assez petit pour être audité
Un validateur court avec un contrat strict est plus sûr qu'un assainisseur général doté d'une longue liste d'exceptions. L'exemple Go suivant n'autorise volontairement que les octets ASCII imprimables dans une valeur d'authentification personnalisée. Il ne supprime pas les espaces, ne normalise pas, ne décode pas et ne réécrit pas l'entrée.
package authheader
import (
"fmt"
"strings"
)
func canonicalName(name string) (string, error) {
if name == "" {
return "", fmt.Errorf("empty field name")
}
for i := 0; i < len(name); i++ {
b := name[i]
ok := b == '!' || b == '#' || b == '$' || b == '%' || b == '&' ||
b == '\'' || b == '*' || b == '+' || b == '-' || b == '.' ||
b == '^' || b == '_' || b == '`' || b == '|' || b == '~' ||
('0' <= b && b <= '9') || ('A' <= b && b <= 'Z') ||
('a' <= b && b <= 'z')
if !ok {
return "", fmt.Errorf("invalid field-name byte 0x%02x at offset %d", b, i)
}
}
return strings.ToLower(name), nil
}
func validateCredentialValue(value string) error {
if len(value) == 0 {
return fmt.Errorf("empty credential value")
}
for i := 0; i < len(value); i++ {
b := value[i]
if b < 0x21 || b > 0x7e {
return fmt.Errorf("invalid credential byte 0x%02x at offset %d", b, i)
}
}
return nil
}
func rejectProtected(headers [][2]string, protected map[string]struct{}) error {
for _, h := range headers {
name, err := canonicalName(h[0])
if err != nil {
return err
}
if _, found := protected[name]; found {
return fmt.Errorf("agent supplied protected field %q", name)
}
}
return nil
}
Cet exemple accepte : dans une valeur, car les caractères ASCII imprimables l'incluent. Cela convient à de nombreux jetons API. Si un destinataire n'accepte qu'un sous-ensemble documenté, appliquez la règle du fournisseur dans un validateur propre à la connexion. Ne demandez pas à une politique globale de la passerelle de considérer qu'un deux-points, une barre oblique ou un signe égal est suspect.
L'utilisation de string en Go ne rend pas le validateur conscient d'Unicode. L'indexation d'une chaîne Go renvoie des octets. Une séquence UTF-8 multioctet contient des octets supérieurs à 0x7e, ce contrat la rejette donc proprement. Si votre environnement stocke des valeurs Unicode scalaires plutôt que des chaînes d'octets, encodez-les d'abord dans la représentation exacte des octets sortants, puis effectuez la validation.
L'appelant ne doit pas exposer validateCredentialValue comme une fonction de nettoyage. Les seuls résultats valides sont l'approbation d'octets inchangés ou une erreur. Toute méthode nommée sanitizeHeader, cleanHeader ou normalizeToken mérite une attention particulière lors de la revue, car elle invite les appelants à transformer un secret tout en croyant le protéger.
Une matrice de tests a besoin de représentations hostiles, pas seulement de chaînes hostiles
Une suite de tests doit prouver que chaque chemin vers la requête sortante atteint le même validateur final. Elle ne doit pas seulement appeler directement le validateur avec une entrée CRLF évidente.
Utilisez d'abord des tests unitaires pilotés par table pour les valeurs brutes :
cases := []struct {
name string
value string
want bool
}{
{"ordinary token", "mF_9.B5f-4.1JqM", true},
{"carriage return", "abc\rdef", false},
{"line feed", "abc\ndef", false},
{"crlf", "abc\r\ndef", false},
{"nul", "abc\x00def", false},
{"leading space", " abc", false},
{"trailing tab", "abc\t", false},
{"composed Unicode", "caf\u00e9", false},
{"decomposed Unicode", "cafe\u0301", false},
}
Testez ensuite le chemin complet de l'action avec des scénarios qui semblent ordinaires pour le code applicatif :
- Une requête JSON dans laquelle un saut de ligne arrive après le décodage de
\n. - Un import de configuration dans lequel un fichier se termine par un saut de ligne après le jeton.
- Le résultat d'un modèle dans lequel une variable absente devient une chaîne vide.
- Une requête d'agent contenant à la fois
X-Api-Keyetx-api-key. - Une requête d'agent utilisant un caractère non ASCII ressemblant dans le nom et devant échouer à la syntaxe du nom plutôt que contourner la comparaison avec le nom protégé.
Le cas du fichier se terminant par un saut de ligne révèle une habitude réelle. Les développeurs copient un jeton dans un fichier texte, ajoutent un saut de ligne final sans le remarquer et utilisent une substitution de commande ou un importeur qui le conserve. Une commande shell peut supprimer les sauts de ligne finaux dans un chemin, tandis qu'un lecteur de fichiers les conserve dans un autre. Le comportement correct de la passerelle est cohérent : elle ne supprime pas les espaces. Elle demande à l'opérateur de corriger l'identifiant stocké.
Ajoutez des tests de propriétés autour du constructeur final. Générez de courtes chaînes d'octets contenant chaque caractère de contrôle, des octets supérieurs à 0x7f et des caractères ASCII imprimables. Vérifiez que seules les chaînes non vides entièrement comprises dans la plage autorisée passent. Générez toutes les variantes de casse de chaque nom de champ protégé et vérifiez que chacune refuse la propriété de l'agent. Ces tests détectent les erreurs comme la vérification de \r\n uniquement lorsqu'ils apparaissent ensemble, l'oubli du LF isolé ou la conversion en minuscules effectuée après la recherche dans l'ensemble protégé.
Enfin, utilisez un écouteur d'intégration qui capture les en-têtes réellement reçus par votre bibliothèque HTTP. Il ne devrait jamais voir de valeur protégée mal formée, car la passerelle devrait refuser l'action auparavant. L'écouteur vérifie la construction, pas la politique de sécurité. Si ce test d'intégration découvre un second champ protégé, considérez-le comme un défaut de la passerelle, même si le serveur destinataire aurait rejeté la requête.
Un repli permissif masque le problème à corriger
Remplacer les octets interdits par des espaces, supprimer les espaces ou conserver le dernier doublon est populaire parce que cela permet aux démonstrations de fonctionner. Cela rend aussi difficile la reconstitution de l'origine d'un identifiant sortant.
Prenons le cas de la suppression des espaces. Un opérateur importe token-42\n depuis un fichier. Un chemin le nettoie et envoie token-42. Un autre stocke la valeur brute et la rejette plus tard. L'opérateur voit alors l'action fonctionner dans un test local mais échouer dans la passerelle. Quelqu'un finira par ajouter la suppression des espaces à la passerelle pour éliminer cette incohérence. Quelques mois plus tard, un modèle émet token-42\r\nX-Role: admin ; le même code peut supprimer uniquement le saut de ligne final et laisser le CRLF interne intact. Ce comportement « utile » a masqué à la fois l'erreur initiale et la limite de sécurité.
La bonne correction est simple. Définissez un format d'identifiant accepté, rejetez toutes les autres valeurs et faites expliquer aux outils d'import pourquoi les octets échouent. Un écran de configuration peut afficher trailing LF at offset 8 sans révéler le jeton. Un importeur en ligne de commande peut afficher la même catégorie et terminer avec un code différent de zéro. L'utilisateur corrige la source au lieu d'apprendre à chaque couche suivante à deviner.
Les enregistrements d'audit doivent distinguer un refus d'autorisation d'une défaillance de transport. Notez que le processus agent a demandé une action, que l'action a été refusée avant l'envoi, quelle liaison d'identifiant configurée était concernée et quelle était la catégorie de validation. N'appelez pas le destinataire et ne créez pas une réponse HTTP artificielle qui ressemble à un refus distant. Aucune requête distante n'a été envoyée.
Les enregistrements séparés de sessions et d'activités de Sallyport offrent un emplacement utile pour ce type de refus : la session montre quelle exécution d'agent a tenté l'action, tandis que l'enregistrement de l'action peut indiquer que la validation locale a interrompu l'envoi. L'enregistrement doit rester utile sans conserver le secret rejeté ni une approximation échappée de celui-ci.
La règle est stricte parce que la passerelle possède un secret
Un client HTTP ordinaire peut tolérer des entrées larges, car il envoie souvent des données que l'appelant possède déjà. Une passerelle d'identifiants a une autre responsabilité. Elle décide si un agent peut provoquer une action soutenue par un secret et doit prendre cette décision sur une requête dont la syntaxe ne peut pas changer en cours de route.
Rejetez les caractères de contrôle et les caractères non ASCII dans les valeurs d'authentification personnalisées. Réservez les noms d'authentification et comparez-les avec les règles des minuscules ASCII. Refusez les champs protégés en double. Ne normalisez et ne supprimez jamais les espaces d'un identifiant au moment de sa sortie. Validez chaque chemin d'entrée, puis validez une dernière fois la liste finale des en-têtes.
Si le format d'authentification d'un fournisseur ne peut pas respecter ce contrat, documentez son encodage spécifique et appliquez-le avant que le secret n'atteigne le constructeur de requête. N'assouplissez pas la passerelle pour accepter du texte ambigu parce qu'une intégration est arrivée avec un jeton mal formé. La première valeur incorrecte doit s'arrêter à la barrière, avec un refus qui explique le problème d'octet et laisse le secret invisible.
FAQ
Une passerelle doit-elle rejeter CRLF et un simple saut de ligne dans la valeur d'un en-tête HTTP ?
Rejetez les deux. Un retour chariot et un saut de ligne peuvent modifier la manière dont un destinataire HTTP/1.1 interprète les limites des champs, et les différents destinataires n'ont pas toujours accepté les mêmes fins de ligne. Ne corrigez aucun de ces caractères dans un en-tête d'identification. Refusez l'action avant de construire la requête sortante.
Dois-je bloquer les octets NUL dans les en-têtes d'authentification personnalisés ?
NUL n'a aucune place légitime dans la valeur d'un en-tête d'authentification et HTTP le considère comme invalide. Rejetez-le avec CR et LF, puis inscrivez dans l'événement d'audit le décalage de l'octet et le rôle du champ, sans enregistrer le secret lui-même.
Les clés API doivent-elles être normalisées en Unicode avant leur ajout à un en-tête ?
Ne normalisez pas un identifiant au moment de l'injection. La normalisation Unicode peut faire comparer deux chaînes comme égales tout en modifiant les octets attendus par le fournisseur. Stockez et injectez les octets approuvés tels quels, ou exigez un encodage de transport ASCII comme base64url avant l'entrée du secret dans le coffre.
Comment comparer correctement des noms d'en-têtes HTTP en double ?
Traitez les noms de champs comme des jetons de protocole ASCII, puis comparez leur forme convertie en minuscules ASCII. N'appliquez ni la conversion de casse Unicode ni NFKC aux noms de champs. Un nom non ASCII doit échouer à la validation plutôt que devenir une version ressemblante d'un champ protégé.
Une passerelle doit-elle supprimer ou rejeter un en-tête Authorization en double ?
Pour un champ d'authentification injecté, rejetez la requête. Supprimer silencieusement la copie fournie par l'agent masque une erreur d'autorisation et peut laisser différentes couches avec des interprétations différentes. Un champ protégé doit avoir un seul propriétaire, la passerelle.
Est-il sûr d'autoriser les tabulations ou les caractères Unicode dans la valeur d'un en-tête de clé API ?
Un analyseur HTTP générique peut accepter un éventail de contenus plus large que celui prévu par votre contrat d'identifiant. Pour une authentification personnalisée, une politique limitée aux caractères ASCII visibles, sans espace initial ni final, est généralement plus facile à tester et plus sûre entre les versions HTTP et les intermédiaires.
Quels cas de test permettent de détecter les failles d'injection d'en-têtes HTTP ?
Testez CR seul, LF seul, CRLF, NUL, les espaces initiaux et finaux, les tabulations, les octets non ASCII, l'Unicode décomposé, l'Unicode composé et les noms en double avec des casses ASCII différentes. Exécutez les mêmes cas sur chaque chemin de configuration, de coffre, de modèle et de construction de requête.
Où la validation des en-têtes doit-elle avoir lieu dans une passerelle d'agents ?
La valeur doit être vérifiée juste avant sa remise à la bibliothèque HTTP, car un décodage, une interpolation ou une sérialisation peut contourner les contrôles précédents. Validez le nom d'en-tête configuré lors de son enregistrement, mais validez la séquence finale d'octets pour chaque action sortante.
Les sauts de ligne dans les en-têtes sont-ils dangereux uniquement avec HTTP/1.1 ?
HTTP/2 supprime le cadrage textuel CRLF utilisé par HTTP/1.1, mais interdit toujours CR, LF et NUL dans les valeurs de champs. Une passerelle peut aussi franchir une limite de protocole par l'intermédiaire d'un proxy. La validation ne doit donc pas dépendre de la version du transport utilisée.
Que doit inscrire un journal d'audit lorsqu'une valeur d'en-tête échoue à la validation ?
Enregistrez l'action comme refusée, le nom configuré du champ protégé, la catégorie du rejet et la position de l'octet invalide. N'enregistrez ni la valeur d'en-tête fournie, ni une copie normalisée, ni une représentation échappée de l'identifiant. La notation d'échappement peut devenir une seconde voie de fuite du secret.