8 min de leitura

É seguro injetar cabeçalhos personalizados de autenticação?

Cabeçalhos personalizados de autenticação precisam de validação byte a byte antes que um gateway os injete. Rejeite quebras de linha, armadilhas Unicode e campos duplicados.

É seguro injetar cabeçalhos personalizados de autenticação?

Um gateway que injeta credenciais precisa tratar um cabeçalho HTTP como sintaxe de protocolo, não como um campo de texto conveniente. Se um agente, uma tela de configuração, uma importação do cofre ou um template puder colocar um byte inesperado nesse campo, o gateway poderá enviar uma solicitação com significado diferente daquele sugerido pela tela de aprovação, pelo registro de auditoria ou pelo serviço de destino.

A regra segura é restrita: reserve cada campo de autenticação para o gateway, valide os bytes finais imediatamente antes de construir a solicitação e rejeite entradas malformadas, em vez de tentar limpá-las. Isso captura quebras de linha, armadilhas Unicode, controles ocultos e nomes de campos duplicados antes que qualquer credencial deixe o armazenamento local.

A fronteira de injeção precisa de um contrato de bytes

Um cabeçalho personalizado de autenticação deve ter um contrato mais rigoroso do que o parsing HTTP genérico. O parsing genérico precisa aceitar uma internet ampla e cheia de casos difíceis. Um gateway que move um segredo do cofre para uma solicitação tem uma tarefa menor, portanto deve aceitar menos.

Comece separando quatro coisas que as equipes costumam juntar em um único «valor de cabeçalho»:

  1. O nome do campo configurado, como X-Api-Key.
  2. Os bytes secretos armazenados para esse campo.
  3. Os cabeçalhos da solicitação fornecidos pelo agente para uma ação específica.
  4. A lista final de cabeçalhos de saída entregue ao cliente HTTP.

Cada item tem um responsável diferente. Um administrador ou desenvolvedor configura o nome. O cofre é responsável pelo segredo. O agente pode solicitar uma operação HTTP e fornecer cabeçalhos comuns da aplicação. Somente o gateway monta o campo protegido final.

Essa distinção é importante porque um agente nunca deve receber um segredo apenas para concatená-lo com um nome de cabeçalho. Ela também evita um erro mais sutil: permitir que o agente forneça x-api-key enquanto o gateway adiciona X-Api-Key depois. Os nomes dos campos HTTP não diferenciam maiúsculas de minúsculas, portanto são duas instâncias concorrentes do mesmo campo, não dois cabeçalhos sem relação. A RFC 9110 define os nomes dos campos como não sensíveis a maiúsculas e minúsculas e descreve como singleton os campos que esperam um único membro.

Para um cabeçalho de credencial, torne o contrato de bytes explícito:

  • O nome usa apenas o conjunto de caracteres token do HTTP e é armazenado em uma forma canônica minúscula para comparação.
  • O valor é uma sequência não vazia de bytes ASCII imprimíveis entre 0x21 e 0x7e.
  • O valor não contém espaço, tabulação, CR, LF, NUL nem qualquer outro byte de controle.
  • O gateway adiciona exatamente uma instância do nome protegido.
  • Uma solicitação do agente que nomeia o campo protegido falha. Ela não é editada silenciosamente.

Essa política rejeita de propósito algum conteúdo de campo permitido pelo HTTP genérico. Esse é o objetivo. Uma chave de API, um token bearer ou uma credencial de fornecedor não deveria precisar de uma tabulação, de um espaço inicial ou de um caractere acentuado para viajar com segurança em um cabeçalho personalizado. Se um provedor tiver um formato de credencial que exija bytes arbitrários, codifique-os antes do armazenamento usando a representação de transporte documentada pelo provedor. Base64url é comum quando o protocolo exige um token imprimível. Não invente uma conversão com perda no gateway.

A alternativa mais tentadora é aceitar texto arbitrário, remover espaços, substituir controles por espaços e confiar que a biblioteca HTTP rejeitará o restante. Isso cria várias representações da mesma credencial. A interface pode mostrar uma string, o log pode conter outra e a biblioteca pode transmitir uma terceira. Um controle de segurança deve decidir uma vez se uma ação é válida. Ele não deve transformar a ação em outra coisa.

CR, LF e NUL devem falhar antes da construção da solicitação

Rejeite carriage return (0x0d), line feed (0x0a) e NUL (0x00) em qualquer posição de um valor de cabeçalho protegido. Rejeite um CR ou LF isolado com a mesma firmeza da conhecida sequência CRLF.

O HTTP/1.1 usa CRLF para delimitar linhas de campos. A RFC 9112 também permite que destinatários reconheçam um LF isolado em algumas circunstâncias. Essa tolerância é justamente o motivo pelo qual um gateway de saída não pode presumir que todos os componentes posteriores reagirão da mesma forma. A especificação diz que os emissores não devem criar CR isolado dentro de elementos do protocolo e descontinua as antigas linhas de campo dobradas.

A RFC 9110 é ainda mais clara sobre os valores dos campos: CR, LF e NUL são inválidos e perigosos porque as implementações podem analisá-los de maneiras diferentes. Ela diz que os destinatários devem rejeitá-los ou substituí-los por espaços antes de continuar o processamento. Substituir é uma regra de recuperação do receptor, não um bom projeto de gateway. O gateway sabe que está criando uma nova solicitação, portanto deve rejeitar a ambiguidade.

Considere um valor que chega ao construtor da solicitação assim:

team-secret\\r\\nX-Approval: bypassed

Um validador correto identifica dois bytes proibidos e nega a ação. Um validador fraco que procura apenas os caracteres literais \\r\\n vê barras invertidas inofensivas. Um validador fraco que executa a verificação antes da expansão do template pode inspecionar ${TOKEN} e nunca verificar o valor expandido. Um validador fraco que substitui a quebra de linha por um espaço transforma uma negação evidente em uma solicitação que carrega uma credencial malformada e alterada.

A única verificação útil opera sobre a sequência final de bytes, depois da decodificação e da interpolação, antes que a biblioteca HTTP aceite os cabeçalhos. O resultado deve ter uma forma semelhante a esta:

reject header value: field=x-api-key reason=forbidden-byte byte=0x0a offset=11

Essa mensagem é para o registro local da ação, não para um chamador externo. Ela identifica o campo configurado e o byte inválido sem copiar o valor. Evite registrar uma forma entre aspas, escapada em JSON, codificada em base64 ou normalizada do segredo. Todas essas formas ainda são material secreto e tendem a permanecer nos logs por mais tempo que a própria solicitação.

Não limite essa regra ao HTTP/1.1. O HTTP/2 transporta campos em blocos compactados, em vez de linhas de texto, mas a RFC 9113 ainda proíbe NUL, LF e CR em qualquer posição de um valor de campo. Ela alerta especificamente que campos não validados podem causar request smuggling quando um intermediário os traduz para HTTP/1.1. O HTTP/3 faz a mesma observação sobre campos que posteriormente atravessam um protocolo baseado em texto.

Os nomes dos cabeçalhos precisam de regras de propriedade ASCII

Rejeite nomes de cabeçalho não ASCII. Não os normalize, translitere nem tente decidir que um caractere parecido é «próximo o suficiente» de um nome protegido.

Os nomes dos campos HTTP são tokens. A regra genérica exclui espaços, controles, delimitadores e caracteres não ASCII. O HTTP/2 acrescenta uma regra de transporte segundo a qual os nomes precisam estar em minúsculas e rejeita ASCII não visível, letras maiúsculas e bytes acima de 0x7f. Um gateway pode usar uma regra ainda mais simples: valide um nome configurado como token HTTP ASCII, converta-o para minúsculas usando regras ASCII e retenha a grafia original apenas para exibição.

A comparação precisa operar sobre bytes. Se o nome protegido for x-api-key, os seguintes nomes fornecidos pelo agente devem colidir com ele após a conversão ASCII para minúsculas:

X-Api-Key
x-api-key
X-API-KEY

Os nomes a seguir devem falhar completamente na validação, não ser corrigidos:

x-api‐key       // hífen Unicode
x-api-kеy       // e cirílico
x-api-key\\n
x api key

O comentário exibido nesse exemplo é importante. Uma pessoa pode não perceber a diferença entre um hífen ASCII e um hífen Unicode, ou entre um e ASCII e um е cirílico. Um parser não deveria precisar ser bom em tipografia. Recuse todos os nomes não ASCII e a ambiguidade desaparece.

A propriedade é separada da sintaxe. Um nome pode ser sintaticamente válido e ainda assim ser proibido porque pertence ao gateway. Mantenha um conjunto de nomes protegidos em ASCII minúsculo. Verifique cada cabeçalho fornecido pelo agente contra esse conjunto antes de combinar as coleções. Depois, construa a lista de saída do zero:

cabeçalhos comuns do agente que passaram pela validação
+ cabeçalho de credencial pertencente ao gateway
+ metadados da solicitação pertencentes ao gateway, se houver

Não comece com a lista do agente e substitua entradas selecionadas. Algumas bibliotecas HTTP preservam valores repetidos, algumas os combinam e outras expõem os cabeçalhos como um mapa que perde a duplicação original. Quando um campo protegido já entrou na coleção errada, é tarde demais para confiar em uma substituição casual.

A normalização Unicode não é uma operação de correção

Não normalize valores de autenticação em Unicode no momento da injeção. A normalização tem uma função legítima em sistemas que definem igualdade textual, mas um segredo normalmente é uma sequência opaca de bytes cujo significado é decidido pelo emissor.

As formas de normalização Unicode, especificadas no Unicode Standard Annex #15, descrevem transformações entre representações de texto canonicamente ou compativelmente equivalentes. Isso não torna essas transformações seguras para uma credencial. As strings café e café podem parecer idênticas enquanto usam sequências diferentes de pontos de código. Um provedor pode rejeitar uma, aceitar outra, gerar um hash para uma delas ou tratá-las como valores secretos distintos. O gateway não pode adivinhar qual comportamento o provedor escolheu.

Há duas perguntas separadas sobre normalização:

Normalize campos de identidade apenas quando o protocolo definir essa regra

Um produto pode definir como compara nomes de usuário, rótulos de exibição ou metadados da aplicação. Aplique essa regra no limite em que o produto é responsável por esse significado e mantenha um registro claro da representação escolhida. Não reutilize a regra para a injeção de credenciais HTTP apenas porque as duas entradas chegaram como strings.

Os nomes dos cabeçalhos são mais simples. Eles são identificadores de protocolo, não prosa escrita pelo usuário. Rejeite entradas não ASCII e use minúsculas ASCII para a comparação. NFC, NFD, NFKC, NFKD, Unicode case folding e esqueletos de caracteres parecidos não têm lugar nessa decisão.

Nunca use correspondência de caracteres parecidos para mesclar cabeçalhos protegidos

O Unicode Technical Standard #39 fornece dados para detectar caracteres parecidos em análises de segurança. Ele afirma explicitamente que os mapeamentos de esqueletos de caracteres parecidos não devem se tornar uma normalização de identificadores. O alerta é correto: um mapeamento que ajuda a sinalizar um rótulo suspeito não é uma regra segura para alterar silenciosamente uma entrada de protocolo.

Use a detecção de caracteres parecidos na interface de configuração se quiser avisar que alguém digitou um rótulo estranho. Não a use no caminho da solicitação. Esse caminho deve dizer: o nome do campo é ASCII e reservado, ou é inválido. O valor é ASCII imprimível e exato, ou é inválido.

Ainda é necessário testar a normalização Unicode, mesmo sob uma política de credenciais ASCII. Esses testes revelam conversões ocultas nas interfaces e nas camadas de armazenamento. Envie um valor decomposto, um equivalente composto, um espaço inseparável, um caractere de largura zero e um controle de direção da direita para a esquerda por cada rota de entrada. O resultado correto para um valor de autenticação personalizado é uma rejeição com a mesma categoria genérica: byte ou caractere não ASCII. O resultado não deve depender de o texto ter vindo de uma tela de configurações, de um arquivo importado ou de um argumento do agente.

Campos duplicados são uma falha de autorização

Revogue um agente em execução
Revogue uma execução de agente no diário de sessões quando o acesso HTTP precisar ser interrompido.

Para um nome de autenticação protegido, um campo duplicado deve negar a ação mesmo que os dois valores sejam iguais. Um único responsável é mais fácil de auditar, aprovar e muito menos dependente do comportamento do destino.

É comum argumentar que duplicatas são inofensivas quando os valores coincidem. Esse raciocínio trata de limpeza de dados, não de autorização. Uma duplicata pode aparecer antes ou depois de um proxy reordenar os campos. O destino pode escolher a primeira instância, a última, concatenar as instâncias, rejeitar a solicitação ou aplicar uma regra específica do campo. Um intermediário pode fazer uma escolha diferente. A aprovação da ação deixa de descrever uma solicitação única e inequívoca.

Não resolva isso dizendo que Authorization é especial enquanto todo cabeçalho X-... é informal. Um fornecedor pode definir seu campo de credencial personalizado como de valor único, e a maioria dos esquemas de autenticação faz isso. O gateway deve manter uma lista explícita de nomes protegidos por conexão ou vínculo de credencial. A lista pode incluir authorization, x-api-key, x-api-token ou um campo do fornecedor configurado pelo desenvolvedor. Ela não deve depender de uma convenção de nomes.

A detecção de duplicatas precisa ocorrer antes que a biblioteca HTTP transforme a lista de cabeçalhos em sua própria representação. Muitas APIs convenientes usam um mapa de nomes que não diferenciam maiúsculas e minúsculas para uma lista de strings. Isso preserva duplicatas se for usado com cuidado, mas pode ocultar a ordem e a origem. Outras APIs oferecem um método set, que substitui um valor, e um método add, que acrescenta outro. Essas operações só são seguras depois que o gateway rejeitar tentativas do agente de tocar em nomes protegidos.

Use esta tabela de decisão para cada cabeçalho fornecido pelo agente:

CondiçãoResultado do gateway
Nome de campo inválidoNegar a ação
Nome protegido em qualquer combinação ASCII de maiúsculas e minúsculasNegar a ação
Nome comum duplicado cujo protocolo de destino permite repetiçãoPreservar apenas por meio de uma regra explícita por nome
Nome comum duplicado com semântica desconhecidaNegar a ação ou exigir uma regra específica da conexão
Campo de credencial do gatewayAdicionar exatamente uma vez depois que os cabeçalhos do agente passarem pela validação

A terceira linha é deliberadamente restrita. Accept pode ter semântica de lista. Um campo personalizado da aplicação pode não ter. Se o gateway não souber se a repetição altera o significado, não deve inventar uma regra. É nesse ponto que um recurso genérico de «passar todos os cabeçalhos» se transforma silenciosamente em uma forma de contornar a intenção humana.

Valide cada fronteira de transformação e depois o resultado final

Uma única verificação inicial não basta porque os valores da solicitação frequentemente mudam de forma depois da primeira checagem. Cada fronteira de transformação que possa criar, decodificar, juntar ou substituir bytes precisa de uma invariável local, e o construtor final da solicitação precisa fazer a validação decisiva.

Um fluxo prático tem cinco fronteiras:

  1. Quando alguém configura o nome do cabeçalho, valide sua sintaxe de token ASCII e reserve sua forma minúscula.
  2. Quando um segredo entra no armazenamento, valide o contrato da credencial e armazene exatamente os bytes aprovados.
  3. Quando um agente fornece uma operação, valide os nomes e valores dos cabeçalhos comuns e rejeite nomes protegidos.
  4. Depois de templates, leituras de arquivos, expansão de ambiente e decodificação de solicitações estruturadas, valide os valores novamente, pois esses processos podem ter criado novos bytes.
  5. Imediatamente antes da chamada à biblioteca HTTP, valide a lista completa de campos e confirme que cada nome protegido ocorre uma vez.

A verificação final tem autoridade porque enxerga o objeto real de saída. As verificações anteriores existem para fornecer mensagens melhores e impedir que estados inválidos entrem no sistema. Elas não a substituem.

É também por isso que a validação pertence ao núcleo do gateway, não à descrição de uma ferramenta MCP, a um prompt ou a um auxiliar do agente. Esses lugares podem descrever uma solicitação pretendida. Eles não podem garantir o que chega ao socket. O componente que controla a injeção da credencial precisa controlar a última verificação de bytes.

O Sallyport segue a parte útil dessa fronteira: o próprio aplicativo executa a injeção da credencial HTTP, enquanto o agente recebe o resultado, não o segredo. Para uma credencial de cabeçalho personalizado, a camada de ação ainda deve rejeitar uma cópia do campo reservado fornecida pelo agente antes que ela chegue ao ponto de injeção.

Mantenha os erros de validação determinísticos. Uma solicitação que contenha um CR no byte 8 e um cabeçalho protegido duplicado deve informar a primeira falha segundo uma ordem fixa, como nome inválido, colisão com nome protegido, valor inválido e cardinalidade final. O determinismo torna os testes confiáveis e impede que um invasor use diferenças nas mensagens para aprender mais sobre as credenciais armazenadas.

Faça o validador pequeno o suficiente para ser auditado

Mantenha um registro no nível da ação
O diário de atividades registra ações HTTP individuais no mesmo registro de auditoria criptografado das sessões dos agentes.

Um validador curto com um contrato rígido é mais seguro do que um sanitizador genérico com uma longa lista de exceções. O exemplo em Go a seguir permite deliberadamente apenas bytes ASCII imprimíveis em um valor de autenticação personalizado. Ele não remove espaços, normaliza, decodifica nem reescreve a entrada.

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
}

Este exemplo aceita : em um valor porque ASCII imprimível inclui esse caractere. Isso é adequado para muitos tokens de API. Se um destino aceitar apenas um subconjunto documentado, aplique a regra do provedor em um validador específico da conexão. Não faça a política global do gateway presumir que dois-pontos, barra ou sinal de igual são suspeitos.

O uso de string em Go não significa que o validador se tornou consciente de Unicode. Indexar uma string em Go produz bytes. Uma sequência UTF-8 multibyte contém bytes acima de 0x7e, portanto esse contrato a rejeita de forma limpa. Se o runtime armazenar pontos de código Unicode em vez de strings de bytes, converta primeiro para a representação exata de bytes de saída e execute a validação dos bytes depois.

O chamador não deve expor validateCredentialValue como auxiliar de limpeza. Seus únicos resultados válidos são aprovação dos bytes inalterados ou erro. Qualquer método chamado sanitizeHeader, cleanHeader ou normalizeToken merece atenção durante a revisão, pois incentiva os chamadores a transformar um segredo enquanto acreditam estar protegendo-o.

Uma matriz de testes precisa de representações hostis, não apenas de strings hostis

A suíte de testes deve provar que toda rota até a solicitação de saída chega ao mesmo validador final. Não deve apenas chamar o validador diretamente com uma entrada CRLF evidente.

Use testes unitários orientados por tabela para os valores brutos primeiro:

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},
}

Depois, teste o caminho completo da ação com fixtures que parecem comuns para o código da aplicação:

  • Uma solicitação JSON em que uma quebra de linha chega por meio da decodificação de \n.
  • Uma importação de configuração em que o arquivo termina com uma quebra de linha depois do token.
  • Um resultado de template em que uma variável ausente se torna uma string vazia.
  • Uma solicitação do agente com X-Api-Key e x-api-key ao mesmo tempo.
  • Uma solicitação que usa um caractere não ASCII parecido no nome e precisa falhar na sintaxe do nome, em vez de contornar a comparação com o nome protegido.

O caso do arquivo que termina com uma quebra de linha detecta um hábito real. Desenvolvedores copiam um token para um arquivo de texto, acrescentam uma quebra de linha final sem perceber e usam uma substituição de comando ou um importador que a preserva. Um comando de shell pode remover quebras finais em um caminho, enquanto um leitor de arquivos as preserva em outro. O comportamento correto do gateway é consistente: ele não remove espaços. Ele pede ao operador que corrija a credencial armazenada.

Adicione testes de propriedades em torno do construtor final. Gere strings curtas contendo todos os bytes de controle, bytes acima de 0x7f e ASCII imprimível. Verifique que apenas strings não vazias, inteiramente dentro da faixa permitida, passam. Gere variações de maiúsculas e minúsculas de cada nome protegido e confirme que todas negam a propriedade ao agente. Esses testes encontram erros como verificar apenas \r\n juntos, esquecer um LF isolado ou converter para minúsculas depois de consultar o conjunto protegido.

Por fim, use um listener de integração que capture os cabeçalhos realmente recebidos pela sua biblioteca HTTP. Ele nunca deve ver um valor protegido malformado, porque o gateway deve negar antes. O listener verifica a construção, não a política de segurança. Se esse teste de integração encontrar um segundo campo protegido, trate-o como um defeito do gateway, mesmo que o servidor de destino tivesse rejeitado a solicitação.

Um fallback permissivo esconde o bug que você precisa corrigir

Aprove primeiro a execução do agente
Aprove um novo processo de agente antes que suas ações HTTP possam usar credenciais armazenadas no Sallyport.

Substituir bytes proibidos por espaços, remover espaços ou manter a última duplicata é popular porque faz as demonstrações funcionarem. Também torna difícil reconstruir a origem de uma credencial de saída.

Veja o caso da remoção de espaços. Um operador importa token-42\n de um arquivo. Um caminho remove a quebra e envia token-42. Outro armazena o valor bruto e depois o rejeita. O operador vê uma ação funcionar em um teste local, mas falhar no gateway. Alguém acabará adicionando a remoção ao gateway para eliminar a inconsistência. Meses depois, um template produzirá token-42\r\nX-Role: admin; o mesmo código talvez remova apenas a quebra final e deixe o CRLF interno intacto. O comportamento «prestativo» terá ocultado tanto o erro de origem quanto a fronteira de segurança.

O reparo correto é simples. Defina um formato de credencial aceito, rejeite todos os outros valores e faça as ferramentas de importação mostrarem por que os bytes falham. Uma tela de configuração pode exibir trailing LF at offset 8 sem revelar o token. Um importador de linha de comando pode imprimir a mesma categoria e sair com código diferente de zero. O usuário corrige a origem, em vez de ensinar cada camada posterior a adivinhar.

Os registros de auditoria devem distinguir uma negação de autorização de uma falha de transporte. Registre que o processo do agente solicitou uma ação, que a ação foi negada antes do envio, qual vínculo de credencial configurado estava envolvido e qual foi a categoria da validação. Não chame o destino nem crie uma resposta HTTP sintética que pareça uma rejeição remota. Não houve solicitação remota.

Os registros separados de sessão e atividade do Sallyport oferecem um lugar útil para esse tipo de negação: a sessão mostra qual execução do agente tentou realizar a ação, enquanto o registro da ação pode indicar que a validação local interrompeu o envio. O registro deve continuar útil sem preservar o segredo rejeitado nem uma aproximação escapada dele.

A regra é rígida porque o gateway controla um segredo

Um cliente HTTP comum pode tolerar entradas amplas porque muitas vezes envia dados que o chamador já possui. Um gateway de credenciais tem outra responsabilidade. Ele decide se um agente pode provocar uma ação apoiada por um segredo e precisa tomar essa decisão sobre uma solicitação cuja sintaxe não possa mudar depois.

Rejeite controles e caracteres não ASCII em valores de autenticação personalizados. Reserve os nomes de autenticação e compare-os usando regras ASCII de conversão para minúsculas. Recuse campos protegidos duplicados. Nunca normalize nem remova espaços de uma credencial ao enviá-la. Valide todos os caminhos de entrada e depois valide mais uma vez a lista final de cabeçalhos.

Se o formato de autenticação de um provedor não couber nesse contrato, documente a codificação específica do provedor e aplique-a antes que o segredo chegue ao construtor da solicitação. Não torne o gateway permissivo a ponto de aceitar texto ambíguo porque uma integração chegou com um token malformado. O primeiro valor inválido deve parar no gateway, com uma negação que explique o problema do byte e mantenha a credencial invisível.

FAQ

Um gateway deve rejeitar CRLF e também uma quebra de linha isolada em um valor de cabeçalho HTTP?

Rejeite os dois. Um carriage return e um line feed podem alterar a forma como um destinatário HTTP/1.1 interpreta os limites dos campos, e diferentes destinatários historicamente aceitaram terminações de linha diferentes. Não corrija nenhum dos dois caracteres em um cabeçalho de credencial. Recuse a ação antes de construir a solicitação de saída.

Preciso bloquear bytes NUL em cabeçalhos personalizados de autenticação?

NUL não tem lugar legítimo em um valor de cabeçalho de autenticação e o HTTP o trata como inválido. Rejeite-o junto com CR e LF e registre o deslocamento do byte e a função do campo no evento de auditoria, sem registrar o próprio segredo.

Devo normalizar chaves de API em Unicode antes de adicioná-las a um cabeçalho?

Não normalize uma credencial no momento da injeção. A normalização Unicode pode fazer duas strings serem consideradas iguais enquanto altera os bytes esperados pelo provedor. Armazene e injete os bytes exatos aprovados, ou exija uma codificação de transporte ASCII, como base64url, antes que o segredo entre no cofre.

Como comparar nomes de cabeçalhos HTTP duplicados com segurança?

Trate os nomes dos campos do cabeçalho como tokens de protocolo ASCII e compare a forma convertida para minúsculas com regras ASCII. Não aplique Unicode case folding nem NFKC aos nomes dos campos. Um nome não ASCII deve falhar na validação, em vez de se tornar uma versão parecida de um campo protegido.

O gateway deve remover ou rejeitar um cabeçalho Authorization duplicado?

Para um campo de autenticação injetado, rejeite a solicitação. Remover silenciosamente a cópia fornecida pelo agente esconde um erro de autorização e pode deixar camadas diferentes com interpretações diferentes do ocorrido. Um campo protegido deve ter um único responsável, o gateway.

É seguro permitir tabulações ou Unicode no valor de um cabeçalho de chave de API?

Um analisador HTTP genérico pode aceitar uma faixa de conteúdo maior do que a permitida pelo contrato da sua credencial. Para autenticação personalizada, uma política de caracteres ASCII visíveis, sem espaços no início ou no fim, costuma ser mais fácil de testar e mais segura entre versões do HTTP e intermediários.

Quais casos de teste detectam falhas de injeção em cabeçalhos HTTP?

Teste CR bruto, LF bruto, CRLF, NUL, espaços no início e no fim, tabulações, bytes não ASCII, Unicode decomposto, Unicode composto e nomes duplicados com diferentes combinações de maiúsculas e minúsculas ASCII. Execute os mesmos casos por todos os caminhos de configuração, cofre, template e construção da solicitação.

Onde a validação de cabeçalhos deve ocorrer em um gateway de agentes?

O valor precisa ser verificado imediatamente antes de ser entregue à biblioteca HTTP, pois verificações anteriores podem ser contornadas por alterações de decodificação, interpolação ou serialização. Valide o nome configurado ao salvá-lo, mas valide a sequência final de bytes em cada ação de saída.

As quebras de linha nos cabeçalhos são perigosas apenas no HTTP/1.1?

O HTTP/2 remove o enquadramento textual CRLF usado pelo HTTP/1.1, mas ainda proíbe CR, LF e NUL nos valores dos campos. Um gateway também pode atravessar limites de protocolo por meio de um proxy, portanto a validação não pode depender da versão de transporte atual.

O que um log de auditoria deve registrar quando um valor de cabeçalho falha na validação?

Registre a ação como negada, o nome configurado do campo protegido, a categoria da rejeição e a posição do byte inválido. Não registre o valor enviado, uma cópia normalizada nem uma representação com escapes da credencial. A notação de escape pode se tornar outro caminho para o vazamento do segredo.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov