Uma chave de API de um gateway de pagamento aparece em texto puro numa linha de log de erro. O log vai para o agregador, o agregador retém 90 dias, e três times têm acesso de leitura ao índice. A chave só é revogada quarenta dias depois, quando alguém percebe o padrão numa busca por outra coisa. Não houve uso indevido detectado, o que é diferente de não ter havido uso indevido.
Este post-mortem é hipotético, mas a sequência de falhas é banal o bastante para servir de checklist. O ponto não é a chave: é que quatro controles independentes deveriam ter interrompido a cadeia e nenhum interrompeu.
A cadeia de falhas, na ordem em que aconteceu
O serviço fazia uma chamada HTTP ao gateway. Diante de um erro 4xx, o cliente HTTP logava o objeto de request inteiro para facilitar depuração — incluindo o dicionário de headers. O Authorization estava ali dentro. Em operação normal isso nunca aparecia, porque erros 4xx do gateway eram raros. Uma mudança de contrato do lado do gateway passou a rejeitar um campo, os 4xx dispararam, e o header foi parar em milhares de linhas.
A primeira falha é de design do cliente HTTP: logar o request completo é conveniente até o dia em que não é. A segunda é que o pipeline de ingestão de logs não tinha nenhuma regra de redação — nada procurava por padrões de secret na entrada. A terceira é que a credencial era estática, criada manualmente no painel do gateway dois anos antes, sem data de expiração e sem dono registrado. A quarta é que ninguém monitorava uso da chave por origem: se ela tivesse sido usada de outro IP, o alerta não existia para disparar.
Repare que as quatro falhas são de camadas diferentes — aplicação, plataforma, gestão de identidade e detecção. É por isso que corrigir só a primeira não resolve. Um logger.debug mal colocado em outro serviço reabre o mesmo buraco na semana seguinte.
Redação no ponto de saída, não no ponto de entrada
A correção intuitiva é ir ao código que logou e remover o header. Funciona uma vez. A correção que escala é filtrar no ponto onde o log é serializado, de forma que nenhum caminho de código consiga emitir um campo sensível — mesmo um caminho escrito por alguém que não leu a convenção.
Em Python, isso é um filtro no logger raiz; em Node, um serializer no pino ou equivalente. A lista de chaves proibidas é curta e deve ser tratada como configuração de plataforma, não de serviço.
Authorization, authorization e variações:
REDACT = {"authorization", "cookie", "set-cookie", "x-api-key",
"proxy-authorization", "password", "secret", "token"}
class RedactFilter(logging.Filter):
def filter(self, record):
if isinstance(record.args, dict):
record.args = {
k: ("[REDACTED]" if k.lower() in REDACT else v)
for k, v in record.args.items()
}
return True
logging.getLogger().addFilter(RedactFilter())
Duas ressalvas. A primeira: filtro por nome de chave não pega secret embutido no corpo de uma string já formatada — f"falhou: {request}" escapa de qualquer filtro estruturado. A regra que fecha esse caso é proibir interpolação de objetos de request em mensagem de log e passar contexto sempre como campo estruturado. A segunda: redação no cliente protege o que sai da aplicação, não o que já está armazenado. O expurgo do histórico no agregador é um trabalho separado, e a maioria dos agregadores não apaga por linha — apaga por índice inteiro.
A camada que realmente resolve: credencial sem valor duradouro
Redação reduz a chance de vazar. Credencial de vida curta reduz o valor do que vaza. As duas resolvem problemas diferentes e a segunda tem impacto maior, porque transforma um incidente de quarenta dias num incidente de quinze minutos.
| Modelo | Janela de exposição | Esforço de adoção | Rastreabilidade |
|---|---|---|---|
| Chave estática em variável de ambiente | Até alguém revogar manualmente | Nenhum | Nenhuma — a chave não sabe quem a usou |
| Secret manager com rotação agendada | Até a próxima rotação (dias) | Médio: exige recarregar credencial em runtime | Parcial — versão da credencial identifica a janela |
| Credencial efêmera via OIDC / STS | Minutos | Alto na primeira vez, baixo depois | Alta — cada token carrega identidade da carga de trabalho |
A recomendação, quando o provedor externo suporta federação de identidade: credencial efêmera. Quando não suporta — e muitos gateways de pagamento e ERPs brasileiros ainda não suportam —, secret manager com rotação e um cliente que releia a credencial sem redeploy. O anti-padrão é adotar secret manager e continuar carregando o valor uma única vez no boot: a rotação existe no papel e a aplicação segue usando a versão antiga até reiniciar, o que na prática significa que a revogação derruba produção.
Detecção: o alerta que faltava não era sobre o log
Varredura de secret em código e em log é útil e barata — uma etapa de gitleaks ou equivalente no pipeline, mais uma regra de padrão no agregador que dispara quando uma linha casa com o formato de chave de um provedor conhecido. Vale ter. Mas é detecção de sintoma.
O sinal com menor taxa de falso negativo é o uso da credencial fora do perfil esperado. Isso exige duas coisas que raramente estão prontas: saber de quais IPs ou identidades a chave deveria ser usada, e ter acesso ao log de uso do lado do provedor. Quando o provedor expõe esse log, uma regra simples — chamada originada de faixa não conhecida, ou volume fora do horário de operação — cobre o caso em que a chave vaza por um caminho que ninguém previu, incluindo o notebook de um contratado.
Sem esse acesso, a alternativa honesta é encurtar a janela em vez de tentar detectar. Rotacionar a cada sete dias não impede o vazamento, mas garante que a resposta a “há quanto tempo isso está exposto?” tenha um teto conhecido. Um teto conhecido é o que permite dimensionar a resposta ao incidente sem adivinhar.
Três correções tentadoras que não valem a prioridade
Reescrever o histórico do repositório. Se o secret nunca esteve versionado — e neste caso não esteve, ele veio de variável de ambiente —, limpar histórico é trabalho sem retorno. Mesmo quando o secret está no Git, a reescrita só faz sentido depois da revogação, e ela não desfaz os forks e clones existentes. Revogar é o que resolve; limpar histórico é higiene posterior.
Trocar o agregador de logs por um que ofereça mascaramento nativo. Migração de observabilidade consome trimestres e o mascaramento do lado do agregador roda depois da transmissão — o secret já saiu da rede da aplicação e já passou pelo agente de coleta. A redação precisa acontecer antes, no processo que gera a linha.
Bloquear logger.debug em produção via política. A tentação é tratar o incidente como indisciplina e responder com regra. O header vazou num log de erro, não de debug, e teria vazado igual com nível INFO. Controle que depende de alguém lembrar da regra falha na primeira semana em que o time está apertado — o filtro estrutural não depende.
O padrão comum às três: são respostas ao evento específico, não à classe de falha. A pergunta que separa uma da outra é se a correção impediria um vazamento de um secret diferente, num serviço diferente, escrito por alguém que não participou do post-mortem.
Ordem de execução e como medir
As quatro correções não têm o mesmo custo nem o mesmo retorno. A ordem que faz sentido para a maioria dos times:
- Filtro de redação no logger de plataforma — horas de trabalho, cobre todos os serviços que usam a biblioteca comum.
- Inventário de credenciais externas com dono e data de criação — desconfortável, porque quase sempre revela chaves que ninguém sabe de onde vieram e cujo desligamento ninguém quer testar.
- Rotação automatizada com releitura em runtime — a parte cara é o releitura, não a rotação.
- Federação de identidade onde o provedor suportar — migração por serviço, não big bang.
Três métricas dizem se o trabalho pegou, e nenhuma delas é “número de vazamentos”, que é raro demais para servir de sinal. A idade mediana das credenciais externas ativas — se não cai mês a mês, a rotação não está rodando de verdade. O percentual de credenciais com dono identificado, que deveria caminhar para 100% antes de qualquer automação. E o tempo entre a decisão de revogar e a revogação efetiva em produção, medido num exercício deliberado: escolher uma credencial, revogar, e cronometrar. Times que nunca fizeram esse exercício costumam descobrir que o número está em horas, não em minutos, e quase sempre por causa de um serviço que só relê o secret no boot.
O próximo passo concreto, se nada disso existe hoje: rodar o inventário. Sem saber quantas credenciais externas estão ativas e quem responde por cada uma, as outras três correções são exercício de arquitetura sobre um sistema imaginário.
Deixe um comentário