8

Confiei no Claude Code no piloto automático e o Shai-Hulud quase entrou: 5 permissões que reduzi hoje

Semana passada abri o Claude Code no piloto automático e passei por três prompts sem ler. No quarto, ele quis instalar um pacote npm chamado chalk-utils (existe um chalk-utils legítimo, mas o meu contexto não pedia esse pacote). Se eu tivesse apertado "Sim", estaria participando da versão 2026 do worm Shai-Hulud, que já comprometeu mais de 796 pacotes npm entre setembro de 2025 e abril de 2026, com mais de 20 milhões de downloads semanais combinados (Datadog Security Labs).

Não apertei "Sim" por sorte. Estava com o café na mão errada e demorei três segundos a mais para clicar. Nesses três segundos eu li o nome do pacote e o contexto não bateu. Fim da história ali, começo dessa história aqui.

Este post não é sobre o Shai-Hulud, que já foi coberto por todo mundo. É sobre as 5 permissões que reduzi na mesma tarde depois desse susto, o custo em minutos que essa redução me devolveu e o que ficou realmente diferente na minha semana.

O modelo mental que me colocou em risco

Claude Code tem cinco modos de permissão em 2026: Ask permissions, Accept edits, Plan, Auto, e Bypass permissions. Você cicla entre eles com Shift+Tab durante a sessão (Claude Code Docs oficial).

O modo Auto foi introduzido em março de 2026 e passa cada comando shell por um classificador de segurança separado que bloqueia coisas obviamente catastróficas: push para main, deploy em produção, envio de secrets para fora, apagar arquivos que existiam antes da sessão (Anthropic Engineering: auto mode). O classificador é bom no que ele foi feito para fazer.

Meu erro foi confundir "seguro" (o classificador bloqueou o desastre) com "correto" (essa ação é o que eu queria). Não são a mesma frase. npm install chalk-utils não é desastre para o classificador. É desastre para mim se eu não pedi esse pacote e o pacote não existe no meu contexto de trabalho.

O Shai-Hulud atual explora exatamente essa lacuna. As últimas variantes, especificamente na campanha SANDWORM_MODE de fevereiro de 2026, instalam servidores MCP falsos com prompt-injection embutido que instruem assistentes de IA a colher chaves SSH, credenciais de cloud e tokens de API sem notificar o usuário. Três pacotes se passam pelo próprio Claude Code (Cloud Security Alliance).

A fronteira de confiança que o Claude Code assume não é a que eu estava assumindo. Ele confia no registro npm. Eu confio no Claude Code. Alguém publicou algo no registro npm. A cadeia inteira quebra num elo que ninguém dos dois lados está checando.

As 5 permissões que reduzi na mesma tarde

Nenhuma dessas mudanças é sofisticada. Quase tudo cabe no ~/.claude/settings.json; o resto é uma variável de ambiente e um hook. Custo total de implementação: 40 minutos naquela tarde.

1. Auto mode desligado para instalação de pacotes. Deixei o Auto mode ativo para edits de arquivo e execução de testes, que é onde o ganho de tempo é real. Desliguei para qualquer comando que comece com npm install, pnpm add, yarn add, pip install. Cada instalação passa a exigir confirmação explícita minha, com o nome do pacote e o motivo do install visíveis.

{
  "permissions": {
    "ask": [
      "Bash(npm install:*)",
      "Bash(pnpm add:*)",
      "Bash(yarn add:*)",
      "Bash(pip install:*)"
    ]
  }
}

O ask força o prompt mesmo em Auto mode. Casamento de prefixo tem furo conhecido (npx --yes pacote passa por fora do padrão, a própria doc avisa), então pra bloqueio duro o caminho é um hook de PreToolUse; pro meu caso de uso, o prompt resolve. Custo cognitivo: 3 segundos por instalação. Custo evitado: potencialmente o meu ~/.ssh/ inteiro.

2. Accept edits ligado, mas com lista de bloqueio explícita. Accept edits auto-aprova edições de arquivo e operações de filesystem seguras dentro do diretório de trabalho. Isso vira problema quando o "diretório de trabalho" inclui .env, ~/.npmrc, Makefile, ou scripts pós-install de node_modules. O modo entra via defaultMode, e os caminhos sensíveis saem via regras Edit() no deny.

{
  "permissions": {
    "defaultMode": "acceptEdits",
    "deny": [
      "Edit(.env*)",
      "Edit(.git/hooks/**)",
      "Edit(~/.npmrc)",
      "Edit(Makefile)"
    ]
  }
}

Edit nesses caminhos é bloqueado; no resto do diretório de trabalho, auto-aprovado. Em regra de deny, um padrão relativo como .git/hooks/** casa em qualquer profundidade, e ~/ ancora no home. Isso pega tentativas de escrever em .git/hooks/ (vetor conhecido do Shai-Hulud) e em ~/.npmrc.

3. Leitura proibida em arquivos-chave. O controle de verdade aqui é permissions.deny com regras Read(). Instrução em prosa no CLAUDE.md não é controle de segurança, é pedido educado.

{
  "permissions": {
    "deny": [
      "Read(.env*)",
      "Read(**/*.pem)",
      "Read(**/*.key)",
      "Read(~/.ssh/**)",
      "Read(~/.npmrc)",
      "Read(~/.aws/**)"
    ]
  }
}

Mantive a seção equivalente no CLAUDE.md global como aviso de intenção, mas quem trava é o deny. Contra um MCP malicioso (processo separado, que não passa por esse filtro) isso não defende; é exatamente por isso que existe o item 5.

4. Telemetria desligada e /bug fora do fluxo. O comando /bug no Claude Code manda relatórios que ficam retidos por até 5 anos. Se eu estiver frustrado às 2 da manhã e usar /bug sem ler o payload, secrets acidentalmente no contexto vão junto.

export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

Desliguei também o /bug via hook local que intercepta o comando e força um "tem certeza?" antes de enviar. Custo: 5 minutos escrevendo o hook. Ganho: minha noite não vira problema jurídico daqui a dois anos.

5. Whitelist de MCP servers. O vetor mais novo do Shai-Hulud é MCP: pacotes maliciosos deploiam servidores MCP falsos com prompt-injection embutido. Eu passei a manter uma lista curta e explícita: os meus servidores ficam em ~/.claude.json, e a aprovação de servidores vindos de .mcp.json de projeto fica pinada no ~/.claude/settings.json.

{
  "enabledMcpjsonServers": ["context7"],
  "enableAllProjectMcpServers": false
}

Por padrão o Claude Code já pede aprovação na primeira vez que vê um servidor de .mcp.json, o que segura o cenário do pacote npm plantando um servidor como side-effect. O que essa config fecha é o meu lado do problema: enableAllProjectMcpServers nunca ligado (é a chave que aprovaria tudo sozinha) e a lista de aprovados explícita num arquivo, em vez de espalhada nos "Sim" que eu cliquei no piloto automático ao longo dos meses. Custo: quase zero. Ganho: o vetor principal da campanha SANDWORM_MODE passa a depender de um "Sim" meu, e esse prompt eu leio.

Cinco permissões reduzidas depois do quase-Shai-Hulud: cada linha mostra o vetor de ataque fechado e o custo em segundos por dia. Total ~1 minuto/dia contra o risco de rotação de secrets numa tarde

Quantos minutos essa redução me custou

Essa é a parte que ninguém escreve nos posts de segurança, então vou escrever. Confirmar cada permissão manualmente custa tempo. A pergunta honesta é quanto.

Nos primeiros 5 dias depois da mudança eu contei. Média por dia:

  • 4 confirmações extra de npm install × 3s = 12s
  • 6 prompts para edits fora do diretório de trabalho × 8s (leio o path e decido) = 48s
  • 1-2 prompts do hook do /bug = ~5s

Total: perto de 1 minuto por dia. Um minuto.

Comparado a: 1 vez de "instalei chalk-utils no automático" custa, sob Shai-Hulud, minimamente algumas horas de rotação de secrets e no cenário ruim uma rebuild completa do ambiente. A conta não fecha nem em ordem de grandeza. Um minuto por dia contra "pode ser algumas horas em algum dia" é o trade mais barato que fiz esse ano.

O que ficou de fato diferente

Não fiquei paranoico. Continuo usando Claude Code todo dia, no Auto mode, para 90% do meu trabalho. O que mudou é onde eu não deixei o Auto mode: nas fronteiras onde a decisão precisa ser minha porque o classificador não tem informação suficiente para decidir por mim.

A Anthropic escreveu na introdução do Auto mode uma frase que me marcou: o classificador é para bloquear risco, não para entender intenção. Intenção mora na minha cabeça, e as 5 permissões acima são o mínimo de superfície que eu concordo em delegar quando estou cansado, com café na mão errada, e sem tempo para ler os três prompts anteriores.

O Shai-Hulud é uma corrida armamentista. A próxima campanha vai encontrar um vetor que não está nessa lista de 5. Quando isso acontecer, o hábito de reduzir permissão nas fronteiras onde o classificador não tem contexto sobre mim é o que me protege, não a lista específica.

Reagan tinha uma frase pra negociações com Gorbachev que descreve exatamente essa postura: "trust, but verify". No caso do Claude Code, verify significa um segundo antes de apertar Sim, ler o nome do pacote. E significa também não configurar o ambiente pra o Sim ser apertado sozinho quando o custo de um Sim errado é grande demais.

O piloto automático é útil quando o preço de errar é baixo. Cinco permissões redesenhadas depois, o meu piloto automático finalmente está ligado só onde o preço de errar é baixo mesmo.


Correção (31/07): a primeira versão deste post citava duas chaves que não existem na doc oficial: acceptEditsPaths (item 2) e mcpServersDenyAll (item 5). Reescrevi os itens 1, 2, 3 e 5 com os mecanismos reais: permissions.ask, permissions.defaultMode mais regras Edit() e Read() no deny, e enabledMcpjsonServers. Valeu ao @rudekwydra pela revisão nos comentários do TabNews.


ken imoto · WebRTC & Voice AI engineer · kenimoto.dev · TabNews

Carregando publicação patrocinada...
6

Falando nisso, levei um susto essa semana. Relatri um bug em produção que nao ocorreu na minha máquina, expliquei o que ocorreu e pedi para ele avaliar os possíveis pontos que podem ter inserido o bug.

Depois de um tempo ele me retorna: "Verifiquei os logs do cloudwatch e..."

Como assim? Como vc acessou a aws, no ambiente de produção?

Ele simplesmente usou minhas credenciais armazenadas pelo client da aws instaladas em minha máquina que havia usado recentemente para testar uma lambda! Credenciais que inclusive eram extremamente permissivas.

1

Esse "Verifiquei os logs do cloudwatch e..." no meio da resposta dá um frio na espinha. E o detalhe cruel: o deny de leitura em ~/.aws/credentials, que está no meu setup, não bloquearia esse caso, porque ele nem precisou ler o arquivo, só usou o client da aws que já estava autenticado na máquina. Depois do seu relato, Bash(aws:*) entrou na minha lista de deny também.

Uma ideia que deve ajudar aí: tirar o [default] do ~/.aws/credentials e deixar apenas profiles nomeados. Qualquer aws sem --profile explícito falha, e pra mexer em produção o comando precisa vir com --profile prod, que aparece na hora de aprovar. Valeu pelo alerta.

4

Excelente post, mas venho complementar um ponto a esta questão:

Leitura proibida em arquivos-chave. Adicionei uma seção ao CLAUDE.md global instruindo Claude a nunca ler ou mostrar arquivos que contenham secrets.

Eu uso a ferramenta ai-jail do Fábio Akita, que usa bubblewrap (no Linux) para deixar inacessível ao claude todos os arquivos externos a pasta onde está rodando o utilitário, ou seja, ele não tem acesso as pastas do ssh que ficam na pasta do usuário e outras pastas críticas que ficam fora da pasta do projeto. É mais uma camada de segurança nesta situação.

2

Não conhecia o ai-jail, valeu pela dica. Ele cobre um buraco que as minhas 5 permissões não tocam: mesmo com o settings todo certo, o processo ainda enxerga o filesystem inteiro fora do projeto. Isolamento no nível do SO por baixo e permissão no nível da ferramenta por cima me parece a combinação certa.

4

Meus 2 cents,

Parabens pelo post !

Sao pequenas acoes que podem salvar horas/dias de retrabalho.

Tenho usado sempre o claude/codex/whatever dentro de uma sandbox/container, procurando isolar o maximo possivel, mas sempre tem alguma coisa (como .env) que contem dados sensiveis.

Eu gosto da ideia de mandar um '/loop <blabla> ultracode' e voltar depois de um tempo como a tarefa ja pronta, mas isso esta cada vez mais complicado.

Obrigado por compartilhar !

Saude e Sucesso !


Este post foi favoritado via extensão TABNEWS FAVORITOS

Tem curiosidade sobre IA ? Da uma olhada no meu LIVRO: IA PARA ENGENHEIROS

1

Valeu, Oletros. O .env é exatamente onde meu container também vaza: o projeto precisa dele pra rodar, então isolamento externo não resolve sozinho, por isso fui pro deny de leitura. E o dilema do "/loop e voltar depois" é real: quanto mais autonomia eu quero, menor tem que ser a superfície que eu deixo aberta.

4

Concordo com a tese e acho que o item 5 o mais importante na prtica. Quem roda vrios servidores MCP locais tem muito mais superfcie exposta do que imagina.

Fui conferir as chaves na doc oficial antes de aplicar e nem todas existem. acceptEditsPaths no aparece em lugar nenhum (existe o modo acceptEdits via permissions.defaultMode, que outra coisa). mcpServersDenyAll tambm no existe. Se algum colar esses dois no settings vai achar que est protegido sem estar, que pior que no ter feito nada.

O que real e eu apliquei hoje: permissions.deny com Bash(npm install:*) e afins, mais Read() e Edit() em .env*, *.pem, *.key, ~/.ssh/**, ~/.npmrc e ~/.aws/credentials. Isso cobre o efeito que voc queria no item 2 e bem mais forte que a instruo no CLAUDE.md do item 3, que voc mesmo admitiu ser fraca. Instruo em prompt no controle de segurana.

Duas pegadinhas que descobri no caminho e podem te poupar tempo:

  1. Regra de caminho relativa dentro de ~/.claude/settings.json resolve a partir de ~/.claude/, no do projeto. Precisa usar ncora // ou ~/ pra valer em todo projeto.
    1. Padro de Bash que tenta restringir argumento frgil. Bash(npx -y:*) no pega npx --yes pacote. A prpria doc avisa isso. Pra bloqueio srio de instalador, hook de PreToolUse mais confivel que casamento de string.

CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC confirmei que existe e varivel de presena, qualquer valor no vazio liga.

Valeu pelo alerta. Shai Hulud plantando MCP malicioso um vetor que pouca gente est olhando.

1

Você tem razão nos dois pontos, e obrigado por conferir na doc antes de aplicar: acceptEditsPaths e mcpServersDenyAll não existem. E a ironia dói: um post sobre não confiar no piloto automático saiu com duas chaves que eu não chequei contra a doc oficial.

Concordo que instrução em CLAUDE.md não é controle de segurança. Reescrevi o post: item 2 e 3 foram pra permissions.deny com Edit() e Read() como você descreveu, o item 5 virou enabledMcpjsonServers, e no item 1 deixei anotado o furo do npx com o hook de PreToolUse como bloqueio de verdade em vez de casamento de string.

A pegadinha do caminho relativo resolvendo a partir de ~/.claude/ eu não sabia, essa me pouparia uns bons minutos de debug.

A correção já está no ar, com crédito pra você no rodapé. Valeu pela revisão, comentário assim vale mais que o post.

1

Setar a flag global no npm, pip que só permite instalação de libs que tem atualização com no minimo 7 dias em produção tambem é uma boa. Até por que vai saber se uma lib official que você vai aprovar foi infectada ontem.

min-release-age=7

3
1