1

Claude Code no homelab: CLAUDE.md é pedido, deny no settings.json é trava

Uso o Claude Code nos containers do meu homelab, até pelo celular. Uma ferramenta que roda comando sozinha dentro do servidor pede regras claras, e descobri que há dois tipos de regra: a que é só pedido e a que é bloqueio de verdade.

Primeiro, o acesso

A regra mais forte não é texto. No meu homelab, o Claude Code trabalha nos containers de serviço e nas aplicações, mas não no host Proxmox. O host é a única peça que derruba tudo de uma vez. O que a IA não alcança, ela não estraga.

As três regras que repito

  1. Segredos em /etc/<app>/*.env, com permissão 600. Nunca no repositório, nunca em log.
  2. Nada externo e nenhum gasto sem minha confirmação. Publicar, enviar, criar recurso em nuvem: passa por mim.
  3. Não mexer em produção sem pedir.

Parecem óbvias. O problema é que uma IA tentando resolver a tarefa acha o caminho mais curto, e às vezes o caminho mais curto é colar a senha no código ou reiniciar o serviço de produção para testar.

CLAUDE.md: o lugar das regras

O CLAUDE.md é carregado no começo de toda sessão. O ~/.claude/CLAUDE.md vale para todos os projetos do usuário; o ./CLAUDE.md vale só para o projeto e vai para o git. Regras gerais no primeiro, regras do app no segundo. A documentação recomenda ficar abaixo de 200 linhas por arquivo, e o /context mostra o que foi carregado.

Mas CLAUDE.md não trava nada

A documentação é clara: o CLAUDE.md é tratado como contexto, não como configuração obrigatória. Para bloquear de verdade, use as regras de permissão do settings.json, que o próprio Claude Code aplica:

{
  "permissions": {
    "deny": [
      "Bash(git push *)",
      "Read(./.env)",
      "Read(./secrets/**)"
    ]
  }
}

A ordem é sempre deny, depois ask, depois allow, e um allow não abre exceção num deny.

Atualização: o deny tem furo, o sandbox fecha

O @plugandship apontou aqui nos comentários dois furos, e a documentação confirma os dois:

  • Regra de Bash casa com o texto do comando. O Bash(git push *) pega git push, mas não /usr/bin/git push nem sh -c 'git push'.
  • Regra de Read não segura subprocesso. Vale para as ferramentas do Claude e para cat, head e afins, mas um script Python que abre o .env passa direto.

Para os dois, o caminho é o sandbox do Bash (/sandbox na sessão ou sandbox.enabled no settings.json). Ele age no sistema operacional, para o comando e para os processos que ele abre. Só que, por padrão, deixa ler quase tudo, inclusive ~/.ssh, então o que fica de fora precisa ir no denyRead:

{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["~/.ssh", "~/.aws", "~/**/.env", "/etc/meuapp"]
    }
  }
}

O allowUnsandboxedCommands: false impede o Claude de tentar de novo fora do sandbox quando um comando falha. E a rede do sandbox só libera domínios permitidos, então um push não chega ao GitHub sem você liberar.

Para o push, fico com a conclusão dele: nada de credencial com permissão de escrita dentro do container. Regra pode ter furo; credencial que não existe, não.

As camadas

  1. CLAUDE.md: o que eu espero.
  2. deny no settings.json: recusa as ferramentas do Claude e os comandos do jeito que ele costuma escrever.
  3. Sandbox do Bash: vale no sistema, para o que estiver listado.
  4. Permissão de arquivo e usuário no Linux.
  5. Acesso: o host fica de fora, e credencial de escrita também.

E por baixo de tudo, git e backup. Todo projeto meu tem repositório no GitHub, e os containers entram no vzdump da noite.

Vocês deixam a IA rodar comando no homelab? Que regras usam, e onde elas ficam?

O post completo está no meu blog: https://blog.brumarino.com/claude-code-regras-homelab/?de=tabnews

Carregando publicação patrocinada...
1

Sobre o Read não pegar script que abre o arquivo sozinho: o Claude Code tem um sandbox pro Bash (/sandbox na sessão, ou sandbox.enabled no settings.json) que fecha esse buraco no nível do sistema. Ele vale pro comando e pros processos que o comando abre, então um python lendo o .env também bate na parede se o caminho estiver no filesystem.denyRead. Só que por padrão ele deixa ler quase tudo, inclusive o ~/.ssh, então tem que listar o que fica de fora.

O Bash(git push *) também tem furo. A própria doc avisa que deny de Bash não pega o programa chamado pelo caminho completo nem dentro de sh -c. Pra push eu confiaria mais em não deixar credencial com permissão de escrita dentro do container.

1

Valeu demais, @plugandship! Fui conferir na documentação e você tem razão nos dois pontos. O deny de Bash casa com o texto do comando, então /usr/bin/git push e sh -c escapam. E o deny de Read não segura um script que abre o arquivo sozinho. A frase do post que dizia que o push era “recusado sempre” estava errada.

Atualizei o post aqui e no blog com uma seção nova, com crédito pra você. Ela fala do sandbox com denyRead listando ~/.ssh, .env e companhia, já que por padrão ele deixa ler quase tudo. Também incluí dois detalhes que achei na documentação: o allowUnsandboxedCommands: false, pro Claude não tentar rodar o comando fora do sandbox quando ele falha, e a rede do sandbox, que só libera os domínios permitidos.

E fiquei com a sua conclusão sobre o push: regra pode ter furo, credencial que não existe, não. Valeu por melhorar o post!