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 dehttps://api.x.ai/v1ganhava sempre. - Entradas de fallback. Quando um provider principal falha e o Hermes ativa um fallback, o
base_urlda 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
/modelia 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:
- 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.
- 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.
- Volte a verificar depois de atualizar. Os dois PR estão apenas em
mainneste 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 umhermes updateque seguemain. - 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.