Série: Aprendendo a Programar com IA

O Buscar-e-Substituir Que Corrompeu 300 Arquivos

O que uma passagem de auditoria profunda revelou, e quão rápido foi corrigido.

Duas Descobertas, Uma Auditoria Quando uma varredura casa mais do que deveria 300 arquivos corrompidos process_event→ embaralhado on_success()→ embaralhado has_access→ embaralhado sem escopo de limite de palavra compilou verde, parseou bem correção: restaurar do histórico do git segredo fixo no código secret = "●●●●●●●●●●●" parecia um placeholder virou um valor real correção: rotacionar + variável de ambiente correção: varredura de segredos na CI
O dano estava na camada humana do código, onde o compilador nunca olha.

Bases de código orientadas por agentes se movem rápido — e falham de formas que um revisor humano teria percebido de relance. Aqui estão duas falhas, em público, e as travas de segurança que agora as impedem completamente.

O dia em que você sai procurando problema geralmente é o dia em que você encontra algum. Neste sábado agendei uma passagem de auditoria profunda na base de código. O plano era colocar em dia a dívida técnica: blocos except genéricos, constantes fixas no código, desvios de convenção, o tipo de pilha que se acumula em qualquer projeto rodando em alta velocidade.

Encontrei duas coisas que não gostei. Ambas já estão corrigidas. Ambas são o tipo de descoberta sobre a qual eu quero ser público, porque explicam algo real sobre como uma base de código orientada por agentes falha e como a disciplina de fazer uma auditoria pega as falhas.

Descoberta um: o buscar-e-substituir que devorou a base de código

Anteriormente no projeto, um agente tinha recebido a tarefa de fazer uma varredura de estilo marketing em docstrings e comentários. A intenção era razoável: renomear uma frase específica que aparecia em algumas strings voltadas ao público. A execução não foi cuidadosa o suficiente com os limites de palavra.

A frase que o agente deveria trocar era um slogan de marketing específico que continha palavras que também apareciam como parte de substrings dentro de nomes de identificadores mais longos. A operação de buscar-e-substituir casou essas substrings em lugares onde não deveriam casar. Nomes de variáveis. Nomes de funções. Descrições de teste. Comentários inline. Em qualquer lugar onde essas substrings apareciam, a string de substituição do agente foi colocada no lugar.

O resultado foram trezentos arquivos com identificadores e textos sutilmente embaralhados. Variáveis que continham certos termos comuns em inglês ganharam pedaços do slogan de marketing embutidos no meio do token. Descrições de função ficaram sem sentido. Descrições de teste afirmavam estar testando coisas que não existiam. A base de código compilava porque os identificadores quebrados eram consistentes dentro de seus próprios arquivos, mas a camada legível por humanos da base de código foi vandalizada em pontos sutis por toda parte.

Quero ser específico sobre como esse tipo de falha acontece porque é uma classe de falha orientada por agentes que outros times vão enfrentar.

A busca tinha escopo amplo demais. O agente foi instruído a encontrar uma frase e substituí-la. Por acaso, a frase era uma substring de palavras comuns em inglês. A forma certa de limitar o escopo dessa busca é por limites de palavra (\bpalavra\b em regex), com sensibilidade a maiúsculas/minúsculas explícita, com uma lista de permissão explícita de extensões de arquivo, com uma lista de bloqueio explícita de contextos de identificador. A instrução que o agente recebeu não tinha nenhuma dessas restrições.

O agente não sinalizou a amplitude. Trezentos arquivos são muitos arquivos. Um agente que abriu um PR tocando trezentos arquivos para um pequeno ajuste de marketing deveria ter sinalizado a amplitude no momento de abrir o PR. O agente não fez isso. O título do PR dizia algo como “atualizar texto de marketing em docstrings.” O corpo do PR listava a contagem de arquivos como um número, não como uma preocupação.

Meu processo de revisão não pegou isso. O diff do PR eram trezentos arquivos de pequenas mudanças de duas linhas que pareciam todas a mesma edição. O diff, lido de relance, parece uma varredura limpa. A corrupção só aparece se você lê o conteúdo real alterado de um arquivo no momento em que a substituição do agente produziu algo sem sentido. Eu não li. Eu integrei.

A CI não pegou isso porque os nomes ainda faziam parse. Os identificadores corrompidos eram sintaticamente válidos. Compiladores não se importam se sua variável tem um nome que parece um slogan de marketing. O build estava verde. Os testes ainda rodavam. O dano estava na camada humana do código, não na camada de máquina.

Como eu corrigi neste sábado

Um script. O script faz três coisas.

Um: re-derivar os nomes de identificador canônicos. A partir do histórico do git antes do buscar-e-substituir ruim ser integrado, o script reconstrói como cada identificador deveria se chamar. A reconstrução é mecânica: para cada arquivo tocado pelo PR ruim, comparar a versão pré-PR com a versão pós-PR, e para cada token substituído, propor uma restauração para o nome pré-PR. A maioria dos arquivos se restaura de forma limpa. Um pequeno número precisa de revisão manual porque tinham mudanças legítimas sobrepostas à corrupção.

Dois: uma passagem de sanidade guiada por grep. Mesmo depois da restauração, alguns dos identificadores corrompidos tinham sido referenciados a partir de código novo escrito depois que o PR ruim foi integrado. Essas referências tinham sido escritas contra os nomes corrompidos. A passagem de grep encontra toda referência a um identificador de estilo corrompido em código escrito depois que o PR ruim foi integrado, e sinaliza cada uma para decisão manual: esse código novo pretendia usar o nome corrompido (raro), ou estava apenas usando qualquer nome que por acaso existia na época (a maioria dos casos)?

Três: uma trava para o futuro. Toda operação de buscar-e-substituir que um agente faz agora precisa especificar (a) escopo por limite de palavra, (b) sensibilidade a maiúsculas/minúsculas, (c) lista de permissão de extensão de arquivo, (d) limite máximo de contagem de arquivos além do qual o agente precisa sinalizar e solicitar revisão explícita, e (e) uma amostra de três casamentos aleatórios que o agente precisa mostrar antes de aplicar a substituição completa. A trava está no Guia do Copilot e agora é parte de todo enquadramento de tarefa de buscar-e-substituir.

A corrupção agora está reparada. O script de auditoria que fez o reparo está no repositório, executável a qualquer momento, com as saídas de diff salvas como evidência. A lição está no Guia do Copilot.

Descoberta dois: o segredo HMAC fixo no código

A passagem de auditoria profunda também revelou algo que eu deveria ter pego antes. A camada de licenciamento do runtime usa HMAC-SHA-256 para verificar tokens de licença. O segredo HMAC estava fixo no código em um arquivo-fonte. O arquivo-fonte estava no repositório público. O segredo era um segredo real usado por um caminho de verificação real de produção.

Essa é a descoberta mais embaraçosa do dia. Quero ser honesto sobre isso porque é exatamente o tipo de coisa que acontece em bases de código orientadas por agentes que se movem rápido, e a discussão pública sobre como pegar isso é mais valiosa do que a discussão privada.

O caminho que isso percorreu até acontecer: Uma versão inicial da camada de licenciamento foi prototipada com um valor de segredo placeholder, com a intenção de ser substituído antes da camada ser entregue para qualquer um. O protótipo foi integrado em um PR com um placeholder de desenvolvimento de aparência óbvia. Com o tempo, lógica de verificação real foi adicionada por cima do placeholder. O placeholder parou de parecer um placeholder uma vez que foi envolvido em código de validação de aparência real. Quando alguém percebeu, o segredo estava sendo usado em fluxos de estilo produção e o arquivo estava no repositório público.

O que fiz hoje:

  • Rotacionei o segredo. O valor comprometido não é mais o valor de produção. O novo valor está em uma variável de ambiente, com um fallback warnings.warn() para ambientes de desenvolvimento que permite que o trabalho de desenvolvimento prossiga sem um segredo real, mas grita bem alto sobre isso.
  • Removi o valor fixo do arquivo-fonte. A substituição é um getenv com uma mensagem de erro clara se a variável de ambiente não estiver definida em um build de produção.
  • Adicionei uma verificação de CI que varre por segredos fixos que casam com padrões comuns (strings de alta entropia, tokens em formato base64, qualquer coisa que pareça uma chave). A verificação é o tipo de pequena infraestrutura que vai pegar a próxima tentativa antes que ela aconteça.
  • Registrei um acompanhamento para auditar o resto da base de código em busca de padrões semelhantes. A auditoria é o trabalho de um fim de semana separado. Hoje foi sobre fechar a descoberta imediata.

A camada de licenciamento ainda funciona. O novo caminho é mais seguro. O segredo comprometido foi rotacionado dentro de horas da descoberta.

Para onde isso generaliza

Alguns pontos honestos.

Buscar-e-substituir orientado por agentes precisa de regras de escopo explícitas. Essa é a terceira vez na história do projeto que uma varredura ampla demais me morde. As duas primeiras vezes foram menos danosas. Dessa vez foi ruim o suficiente para merecer uma trava permanente. A trava agora está em vigor.

Segredos fixos no código em arquivos-fonte são uma falha de disciplina, não uma falha de ferramental. Nenhuma ferramenta vai salvar um time que deixa um segredo real cair em um arquivo público. A disciplina de “todo commit é revisado em busca de credenciais fixas no código” é a correção de verdade. A varredura de CI ajuda. A disciplina é o que importa.

Auditorias encontram o que a revisão perdeu. A disciplina de rodar uma passagem de auditoria agendada sobre a base de código, procurando especificamente pelos modos de falha que a revisão PR-por-PR tende a perder, vale o tempo. A auditoria de hoje pegou duas coisas que a revisão de PR deixou passar. Auditorias futuras vão pegar outras coisas. A cadência é o ponto.

O que parceiros e construtores devem tirar disso

Se você está avaliando um engine para parceria, pergunte ao time como eles lidam com o modo de falha “varredura ampla demais orientada por agentes.” A resposta certa envolve regras de escopo explícitas, sinalização obrigatória em mudanças grandes, e passagens de auditoria. A resposta errada é “ainda não vimos esse problema.”

Se você mesmo está rodando um fluxo de trabalho orientado por agentes e não fez uma auditoria de segredo fixo no código recentemente, faça uma. A probabilidade de algo ter se infiltrado não é zero. O custo de encontrar agora é pequeno.

Se você é um profissional de segurança lendo isso e tem sugestões, estou genuinamente interessado. A classe de falha contra a qual estou trabalhando para me defender é “o agente faz algo que um revisor humano teria percebido de relance mas não pegou no padrão de revisão em massa que um fluxo de trabalho orientado por agentes incentiva.” Sugestões são bem-vindas.

Tarde de sábado. A base de código teve um olhar duro. Duas descobertas, ambas corrigidas. A próxima auditoria está na agenda.

De volta a construir.

Um runtime construído para ser auditado

O RakuAI é o runtime espacial com o qual fabricantes de LLM e de óculos inteligentes constroem — disciplinado por auditorias, endurecido por lições públicas. Veja como projetamos para confiança de nível de parceiro.

← Todos os posts