A sua chave de API foi para o host errado — o Hermes corrige o encaminhamento do base_url


Finalmente conseguiu aprovar o relay interno. Uma linha em ~/.hermes/config.yaml — model.base_url — aponta o Hermes para o gateway da empresa, a revisão de segurança dá luz verde e, durante semanas, tudo parece bem: as respostas chegam, as ferramentas correm, a fatura vai para o departamento certo. Até que alguém da equipa de redes faz uma pergunta simples: porque é que a credencial do seu relay aparece colada ao tráfego que sai para api.vendor.example? Volta a verificar a configuração. Continua a dizer o seu relay. E é exatamente esse o problema: em quatro caminhos de código, essa linha nunca foi lida.

O que model.base_url promete

Cada provider no Hermes tem um host canónico — https://api.x.ai/v1, o endpoint do OpenRouter, chatgpt.com/backend-api/codex. Um model.base_url configurado é a substituição: «não fales com o host canónico, fala com este». As pessoas definem-no por razões que têm todas a ver com controlo:

  • um relay da empresa ou auto-alojado, que regista, limita o ritmo e fatura internamente;
  • um endpoint regional, quando os dados têm de permanecer numa jurisdição;
  • um proxy à frente de um fornecedor, ou um gateway compatível com OpenAI que distribui por vários laboratórios.

Quando esta substituição é descartada, o pedido não falha de forma ruidosa. Tem sucesso — contra o host errado, vestindo a credencial que emitiu para o outro. É isto que duas correções integradas a 2026-09-25 reparam.

Bug um: quatro caminhos que nunca consultavam a configuração

PR #121614 consolida quatro erros reportados separadamente que partilham todos a mesma forma — um base_url configurado é descartado e o código chega ao host canónico do fornecedor, enviando o pedido e a chave de API para um sítio que o utilizador nunca escolheu.

  • Resolução em runtime (xAI). O resolvedor em runtime tem uma tabela de atalhos para providers «simples». A entrada da xAI nessa tabela trazia um URL predefinido vazio, pelo que a regra que normalmente se aplica — se a linha do pool ainda contém o valor predefinido do registo, usa model.base_url — nunca corria para a xAI, e a linha alimentada pelo ambiente a partir de https://api.x.ai/v1 ganhava sempre.
  • Entradas de fallback. Quando um provider principal falha e o Hermes ativa um fallback, o base_url da entrada é encaminhado. Dois providers com os seus próprios ramos de resolução (OpenRouter e o braço Anthropic) deitavam-no fora, enquanto todos os outros o respeitavam.
  • Listagem do seletor de modelos. A listagem mostrada por /model ia diretamente aos fetchers canónicos de cada provider e nunca perguntava se tinha apontado esse provider a um relay, por isso o catálogo do seu relay era invisível.
  • Validação de /model --provider. O caminho de validação tinha o mesmo ponto cego.

Três dos quatro já tinham correções da comunidade, que foram cherry-picked para a consolidação com a autoria intacta. A consequência prática para quem corre um relay: a descoberta e a validação de modelos passam agora a sondar o endpoint que a inferência realmente usa, em vez de um host do qual se afastou deliberadamente.

Bug dois: um gateway Codex personalizado continuava a chamar chatgpt.com

A segunda correção, PR #121751, é a que se lê como um incidente de segurança. Se corre o Codex através de um gateway personalizado — HERMES_CODEX_BASE_URL, ou um gateway declarado apenas por model.base_url — vários caminhos secundários tinham chatgpt.com/backend-api/codex codificado de forma fixa e depois anexavam ao pedido a credencial resolvida (a do gateway).

A lista é mais longa do que se esperaria, porque a maior parte dela é invisível no uso normal:

  • a sonda de comprimento de contexto que corre em cada turno de conversa;
  • a listagem do Codex no seletor /model;
  • o fluxo Codex de hermes model;
  • o cliente Codex do plugin de imagens;
  • o cliente Codex auxiliar;
  • a sonda de quota da credencial em pool;
  • /usage.

Três relatos chegaram na mesma manhã (2026-09-24) — um sobre os caminhos de conversa, um sobre o caminho de imagens, e um a fazer com que a sonda e o seletor construíssem os URLs do catálogo a partir da base resolvida — e depois um revisor apontou a fuga que restava: os caminhos do seletor e das imagens ainda reconstruíam o destino a partir de variáveis de ambiente do sistema, por isso um gateway declarado apenas em model.base_url continuava a vazar. Os utilizadores diretos do OAuth do ChatGPT nunca foram afetados; este era um problema de gateways auto-alojados.

A correção, numa frase

Credencial e destino são agora resolvidos como um par. O resolvedor reporta o host para o qual uma credencial em pool realmente encaminha, usando uma única regra de precedência em todo o lado — substituição por variável de ambiente, depois model.base_url, depois o próprio URL da linha do pool — e a proteção que «recusa uma chave que não seja JWT» passa a aplicar-se apenas a sondas dirigidas a chatgpt.com. Uma chave de gateway pode consultar o seu próprio gateway; já não pode viajar até ao público.

Como verificar a sua própria configuração

Não precisa de ler o código-fonte para descobrir se foi afetado. Precisa de saber a que host o seu tráfego realmente chegou.

# o que diz realmente a sua configuração?
hermes config get model.base_url

# se corre o Codex através de um gateway, a substituição está definida nesta shell?
echo "$HERMES_CODEX_BASE_URL"

# e o que tinha esta shell no ambiente quando o agent arrancou?
grep -n "base_url" ~/.hermes/config.yaml

Depois vá ao lado dos logs, porque a configuração não é a saída de tráfego:

  1. O log de acesso do seu relay é a fonte de verdade. Se o seu gateway registou o tráfego, a resposta à auditoria é um grep. Se o host do fornecedor aparece nos logs de uma ferramenta enquanto o seu relay não mostra nada, estava no caminho errado.
  2. Verifique o tráfego das sondas, não só o das conversas. A sonda de comprimento de contexto dispara a cada turno, o que a torna a evidência mais sonora de uma fuga — e a mais fácil de não ver, porque nunca aparece na transcrição.
  3. Volte a verificar depois de atualizar. Os dois PR estão apenas em main neste momento — foram integrados a 2026-09-25, um dia depois de a tag v0.21.5 ser criada, por isso não estão nessa tag (a página de lançamento da v0.21.5 regista o que lá está). Chegam às instalações através da próxima release ou de um hermes update que segue main.
  4. Considere que «respondeu» não é evidência nenhuma. Os dois bugs produziam respostas normais e bem formadas. A única verificação fiável é do lado do destino.

Se usa model.base_url por razões de residência de dados ou conformidade, isto merece uma reverificação explícita em vez de uma suposição: aponte para um relay que registe, envie uma mensagem e confirme onde foram os bytes. Quem corre um proxy à frente de um laboratório — a configuração abordada no guia do provider de subscrição Claude — está exatamente no grupo que estas duas correções protegem. Se também encaminha a descoberta de modelos ou hooks através de um endpoint personalizado, o guia dos hooks de plugin de provider mostra até onde essa substituição deve chegar, e as notas sobre a cache de configuração explicam porque é que um valor alterado só pode ser visto no arranque seguinte.

O que retirar daqui

Um endpoint personalizado não é uma definição cosmética; é uma afirmação sobre onde os seus dados e as suas credenciais podem ir. O padrão por trás das duas correções é o mesmo que vale a pena levar para a sua própria automatização: um pedido que tem sucesso não prova que foi para onde pediu. Sempre que uma ferramenta, provider ou plugin aceita uma substituição ao estilo base_url, a pergunta de auditoria não é «funcionou?» mas sim «que host respondeu, e que credencial viu?» Neste projeto, a resposta é agora resolvida uma só vez, num único lugar, com uma ordem de precedência documentada — e a proteção que recusa enviar uma chave de gateway a um host público está limitada ao host para o qual foi escrita.