O Phoenix 21.0.0, painel aberto de trace e avaliação de LLM, saiu em 30/10/2026 com a linha “enable auth by default” na release. Fui conferir. No padrão, escutando em 127.0.0.1, mandei um trace com uma chave falsa no prompt e li de volta sem senha nenhuma: 4 de 4 chamadas sem credencial responderam 200, e a chave voltou inteira. Com PHOENIX_HOST=0.0.0.0, as mesmas 4 chamadas deram 401.
A senha vem ligada, sim. Mas só quando o painel escuta fora do loopback. O código e o teste estão em demos/12-phoenix-auth-padrao, no nursia-research-lab.
Por que painel de trace aberto é problema?
Trace de LLM guarda o prompt e a resposta. Se alguém colou uma chave, um CPF ou um trecho de contrato na conversa, isso fica no span. Quem abre o painel lê tudo isso. Ferramenta de observabilidade de IA costuma juntar muito texto sensível num lugar só, e nem sempre recebe o cuidado de um banco de produção.
Por isso a mudança da 21.0.0 importa. Antes, ligar a autenticação exigia definir PHOENIX_SECRET; a release da 21 diz “without requiring PHOENIX_SECRET”. Na 21, a autenticação pode ligar sem essa variável. A pergunta que eu queria responder: liga em que caso?
Como eu testei
Python 3.11, arize-phoenix==21.0.0 instalado do PyPI num venv limpo, CPU, sem chave de API e sem conta. O script sobe o phoenix serve quatro vezes, cada uma com banco novo e portas livres:
PHOENIX_HOST=127.0.0.1, nenhuma variável de auth (o padrão);PHOENIX_HOST=0.0.0.0, nenhuma variável de auth;0.0.0.0comPHOENIX_ENABLE_AUTH=false;0.0.0.0comPHOENIX_DEFAULT_ADMIN_INITIAL_PASSWORDdefinida.
Em cada cenário, sem credencial nenhuma, ele manda 1 trace OTLP com um span de LLM cujo prompt carrega sk-SINTETICO-0000-nao-e-chave-real-7f3a. Depois lista projetos, lê os spans, consulta o GraphQL e tenta entrar com admin@localhost / admin. No cenário 4, entra com a senha inicial e repete a listagem com o cookie da sessão, como controle. Por fim, tenta o /healthz pelo IP de rede da máquina, não pelo 127.0.0.1.
O trace é montado direto em protobuf, sem SDK de instrumentação, pra o teste não depender de provedor de modelo:
span = Span(
trace_id=os.urandom(16), span_id=os.urandom(8), name="llm.chamado-sintetico",
kind=Span.SPAN_KIND_CLIENT, start_time_unix_nano=agora - 1_000_000,
end_time_unix_nano=agora,
attributes=[kv("openinference.span.kind", "LLM"), kv("input.value", PROMPT),
kv("output.value", "Resumo sintético do chamado.")],
)
st, _, _ = http("POST", base + "/v1/traces", trace_sintetico(), "application/x-protobuf")
st, corpo, _ = http("GET", base + "/v1/projects/default/spans?limit=50")
r["segredo_na_resposta_sem_credencial"] = SEGREDO in corpo
O que deu
Saída de results/resultado.json, sem retoque:
| Chamada sem credencial | 1 · 127.0.0.1 | 2 · 0.0.0.0 | 3 · 0.0.0.0 + auth=false | 4 · 0.0.0.0 + senha inicial |
|---|---|---|---|---|
| gravar trace | 200 | 401 | 200 | 401 |
| listar projetos | 200 | 401 | 200 | 401 |
| ler spans | 200 | 401 | 200 | 401 |
| chave falsa na resposta | sim | não | sim | não |
| GraphQL | 200 | 401 | 200 | 401 |
| login admin@localhost / admin | 405 | 403 | 405 | 401 |
| login com a senha inicial | – | – | – | 204 |
| listar projetos com a sessão | – | – | – | 200 |
| /healthz pelo IP da máquina | sem conexão | 200 | 200 | 200 |
Três leituras.
No padrão, o loopback é a única proteção. O painel não aceita conexão pelo IP de rede (última linha), então quem está em outra máquina não chega nele. Quem está na mesma máquina lê tudo. O 405 no login é a rota /auth/login que nem existe quando a auth está desligada.
Fora do loopback, fecha nas 4 chamadas testadas. As 4 chamadas deram 401: o envio do trace foi recusado, e a leitura também. O login com admin@localhost / admin deu 403: a senha padrão continua sendo admin, mas o Phoenix não abre sessão com ela e manda trocar antes (POST /auth/password-reset). Disso eu gostei. A senha fraca existe, mas não serve pra entrar.
Uma variável reabre as 4 chamadas. PHOENIX_ENABLE_AUTH=false em 0.0.0.0 deu o pior cenário dos quatro: aberto pra rede e sem senha. A variável explícita manda mais que a regra do host.
A regra está numa função curta em phoenix/config.py:
def auth_enabled_for_host(host: Optional[str]) -> bool:
"""Use PHOENIX_ENABLE_AUTH if set; otherwise require auth for non-loopback hosts."""
if (explicit := _bool_val(ENV_PHOENIX_ENABLE_AUTH)) is not None:
return explicit
return not is_loopback_host(host)
Onde isso falha
A função olha o host configurado, não quem está conectando. Daí sai o caso que me preocupa: um Phoenix em 127.0.0.1 com um nginx, um ssh -L ou um kubectl port-forward na frente. Pro Phoenix, ele continua em loopback e segue sem senha. Pra quem acessa pelo proxy, é um painel aberto. Isso eu tirei da leitura do código. Não montei o proxy e não medi.
O mesmo vale pra máquina compartilhada: servidor de pesquisa, VM de time, devcontainer com porta encaminhada pelo editor. Loopback protege da rede, não de quem já está dentro.
O que não funcionou
Na primeira rodada, o controle positivo do cenário 4 deu 401 mesmo depois do login. Achei que fosse bug do Phoenix. Era meu: o dict() que eu fiz dos cabeçalhos juntava os vários Set-Cookie numa string só, e o cookie de acesso chegava quebrado. Troquei por headers.get_all("Set-Cookie") e o login com sessão deu 200. Fica o registro: antes de dizer que a ferramenta falhou, confira o seu cliente HTTP.
Limites do teste
- Não rodei a versão 20.x. O “antes” vem da release e do guia de migração.
- Não rodei a imagem Docker, o Helm nem o Databricks. O cenário 2 imita o
PHOENIX_HOST=::que o guia diz que o Docker usa. - Testei 4 chamadas da API, não todas as rotas nem a interface web.
- Não testei
PHOENIX_SECRET. Pelo guia, sem ela, quem tem cópia do banco decifra as chaves de provedor salvas no Phoenix. - Licença: Elastic License 2.0. Não é OSI. Pelo texto da licença, uso interno é permitido; oferecer o Phoenix como serviço gerenciado a terceiros é restrito. Leia a licença antes.
O que eu faria
O que foi bom: a senha admin não abre sessão, e fora do loopback nada passa sem credencial nas chamadas que testei. O que não foi: a proteção depende do host configurado, e a release fala em “auth by default” sem esse detalhe na frase. Pra quem serve: time que já usa LLM e quer ver prompt e resposta de cada chamada sem mandar trace pra fora de casa.
Se você roda Phoenix no próprio notebook, sozinho, o padrão da 21 resolve. Se roda em servidor, VM compartilhada ou atrás de proxy, eu definiria as três coisas no primeiro deploy, sem esperar o padrão decidir:
export PHOENIX_ENABLE_AUTH=true
export PHOENIX_DEFAULT_ADMIN_INITIAL_PASSWORD="$(openssl rand -base64 24)"
export PHOENIX_SECRET="$(openssl rand -base64 32)"
phoenix serve
Dessas três, só a senha inicial entrou no teste (cenário 4); PHOENIX_ENABLE_AUTH=true e PHOENIX_SECRET eu não rodei isoladas. O padrão novo é bom, mas ele depende de um detalhe (o host) que muda sem ninguém perceber quando o projeto sai do notebook.
O que custa implementar
- Os clientes quebram primeiro. Com a auth ligada, o envio de trace sem credencial deu 401. Todo app instrumentado precisa de uma API key do Phoenix antes do deploy, senão o painel fica vazio e ninguém percebe.
- Script que entrava com admin para. O login com a senha padrão volta 403 e um token de troca. Automação de deploy que fazia login assim precisa da senha inicial definida antes do primeiro start.
- Trocar o
PHOENIX_SECRETcusta redigitar chave. Pelo guia de migração, definir ou trocar a variável deixa ilegíveis as credenciais já salvas, e você digita de novo as chaves de provedor que ainda usa. Isso eu não testei. - Exige um dono. Alguém do time guarda a senha e o segredo num cofre, gera e revoga API key e decide quem entra no painel. Sem essa pessoa, a auth vira mais um segredo num
.envesquecido.
Quando eu não usaria
- O padrão loopback, sem auth explícita, em máquina compartilhada, VM de time ou atrás de proxy.
PHOENIX_ENABLE_AUTH=falseem qualquer lugar que não seja o meu notebook.- O Phoenix como produto que eu ofereço a clientes como serviço gerenciado: a Elastic License 2.0 restringe isso.
- O painel como única defesa de dado pessoal. O trace grava o prompt cru: a chave falsa voltou inteira no cenário aberto. Se o prompt pode ter CPF, o certo é anonimizar antes de gravar o span, não confiar só na senha do painel.
E fica a pergunta: no seu time, quem tem acesso ao painel onde ficam os prompts?
Fonte e como reproduzir
Código, resultado e teste: github.com/rogeriorrodrigues/nursia-research-lab/tree/main/demos/12-phoenix-auth-padrao (MIT).
pip install -r requirements.txt
python3 demo.py
python3 -m pytest tests -q -s
Fontes primárias: release 21.0.0 do Phoenix (30/10/2026) e guia de migração v20 para v21. Todo dado do teste é sintético; a “chave” é inválida por construção.
English summary: Phoenix 21.0.0 turns authentication on by default only when it listens on a non-loopback host. On the default 127.0.0.1, 4 of 4 unauthenticated calls returned 200 and a synthetic API key in the prompt came back in the spans response; with PHOENIX_HOST=0.0.0.0 the same calls returned 401 and admin/admin login returned 403. A reverse proxy in front of a loopback Phoenix is still open (read from code, not measured).
Demo publicada no repositório do NursIA (PPGINFOS/UFSC, bolsa FAPESC). Publicado em 06/11/2026.

