Segurança do Loop Autônomo|INEMA.CLUB-PRO

Aula 5 · 20 min

Hooks como guarda: antes da ferramenta, contra apagar e colidir

Regra de permissão compara texto; um hook roda código antes de cada ferramenta e pode olhar o disco. Nesta aula você liga três guardas PreToolUse: uma nega o irreversível e os arquivos do juiz, outra mostra quantos arquivos um rm apagaria e pede confirmação, e a terceira avisa quando outra sessão mexeu no mesmo arquivo.

Começar a aula
Chave de segurança vermelha com tampa levantável ao lado de um botão de emergência num painel industrial.

Nesta aula você vai

  1. 1PreToolUse: deny, ask ou nada, antes da ferramenta
  2. 2guarda.py: push forçado, reset --hard, find -delete, arquivos do juiz, e o que ela não pega
  3. 3raio.mjs e colisao.mjs do inema-agent-runtime
  4. 4Testar o hook com JSON falso e depois numa sessão real

Objetivo: ligar um hook PreToolUse que nega o irreversível, pede confirmação ao apagar e avisa colisão entre sessões.

01Antes de cada ferramenta

Clique em cada etapa.

Clique em cada bloco do diagrama para ver o que ele faz.

Hooks de contexto e handoff são do curso 2; aqui o hook é guarda.

02O que cada guarda pega

Três arquivos, três trabalhos.

1. guarda.py (deny)

Nega o que não tem volta: push forçado, reset --hard, git clean -f, checkout -- ., git restore (da árvore), find -delete, rm recursivo na raiz ou no HOME (com aspas ou dentro de bash -c), pkill e killall, dd e mkfs.

E nega escrever em goal.md, tests/, .claude/ e na pasta da própria guarda, por Edit/Write e também pelo Bash: > e >>, tee, sed -i, cp, mv, rm e truncate (GUARDA_PROTEGIDOS muda a lista).

O que ela não pega: a guarda lê o texto do comando, não é sandbox. Um programa que grava sozinho (python3 -c "open('goal.md','w')", node -e, um script seu), caminho montado em variável ou eval passam. Por isso ela vem junto com o deny Edit(...) da aula 4, que cobre as ferramentas de arquivo, e com o sandbox da aula 6, que limita a escrita do próprio Bash.

Lição real desta máquinapkill -f com o padrão na mesma linha do shell matou o próprio shell duas vezes (15 e 16/09/2026): por isso pkill está na lista.

03Os hooks no settings.json

A guarda primeiro, depois o raio no Bash; guarda e colisão nas edições. Caminhos supondo os dois repositórios em ~/projetos.

Arquivo · kit/hooks/settings-exemplo.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 \"$HOME/projetos/loop-r-eng-seguranca-v72/kit/hooks/guarda.py\"", "timeout": 10 },
          { "type": "command", "command": "node \"$HOME/projetos/inema-agent-runtime/runtime/mods/runtime-guarda/hooks/raio.mjs\"", "timeout": 15 }
        ]
      },
      {
        "matcher": "Edit|Write|MultiEdit|NotebookEdit",
        "hooks": [
          { "type": "command", "command": "python3 \"$HOME/projetos/loop-r-eng-seguranca-v72/kit/hooks/guarda.py\"", "timeout": 10 },
          { "type": "command", "command": "node \"$HOME/projetos/inema-agent-runtime/runtime/mods/runtime-guarda/hooks/colisao.mjs\" pre", "timeout": 10 }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit|NotebookEdit",
        "hooks": [
          { "type": "command", "command": "node \"$HOME/projetos/inema-agent-runtime/runtime/mods/runtime-guarda/hooks/colisao.mjs\" pos", "timeout": 10 }
        ]
      }
    ]
  }
}

04Ensaio: chamadas falsas, decisões de verdade

O script manda aos hooks o mesmo JSON que o Claude Code mandaria e imprime a decisão. Nenhum modelo.

Terminal · guarda, raio e colisão
[ -d ~/projetos/inema-agent-runtime ] || git clone -q https://github.com/inematds/inema-agent-runtime ~/projetos/inema-agent-runtime
bash ~/projetos/loop-r-eng-seguranca-v72/kit/hooks/provar-guarda.sh
Exemplo do repositório
## guarda.py (nega o irreversível)
git push --force origin main       → deny
git reset --hard HEAD~3            → deny
find . -name '*.o' -delete         → deny
rm -rf ~                           → deny
pkill -f servidor                  → deny
python3 -m pytest -q               → -
Edit tests/test_x.py               → deny
Edit src/app.py                    → -
## guarda.py: escrita via Bash nos protegidos
sed -i s/x/y/ tests/test_x.py      → deny
echo pronto > goal.md              → deny
echo {..} > .claude/settings.local → deny
mv goal.md /tmp/                   → deny
bash -c 'rm -rf ~'                 → deny
git restore .                      → deny
pytest -q tests/ > log.txt         → -
git restore --staged app.py        → -
## o que a guarda NÃO pega (para isso: deny Edit/Write da aula 4 e sandbox da aula 6)
python3 -c open('goal.md','w')     → -
## raio.mjs (apagar pede confirmação, com a contagem)
rm -r build                        → ask
rm -r nada                         → -
## colisao.mjs (outra sessão editou há pouco)
sessão B edita o mesmo arquivo    → ask
sessão A edita de novo            → -

"-" é sem decisão: o fluxo normal (regras e modo) decide. pytest com saída em log.txt e git restore --staged passam, porque não mexem nos protegidos. A linha do python3 -c é o furo declarado: a guarda não vê o que um programa grava. O rm de uma pasta que não existe não pede nada, porque não há o que apagar.

05Real: a guarda numa sessão que libera tudo

claude -p em bypassPermissions com o settings-exemplo.json, pedindo git reset --hard e rm -r build num repositório de ensaio com uma mudança não commitada. Confere-se o disco.

Terminal · uma chamada real
bash ~/projetos/loop-r-eng-seguranca-v72/kit/hooks/real.sh; echo "saída: $?"
Exemplo do repositório
app.txt: mudança não commitada preservada
build/a.o: continua lá
saída: 0

06Prática

Ensaio, sessão real e a guarda no seu projeto.

E12 · Decisões dos três hooks

10 min

Rode o ensaio. Ele clona o inema-agent-runtime em ~/projetos se faltar.

Pronto quando: bash ~/projetos/loop-r-eng-seguranca-v72/kit/hooks/provar-guarda.sh | grep -c '→ deny' → 12; e rm -r build → ask, sessão B edita o mesmo arquivo → ask e sessão A edita de novo → -.

E13 · A guarda numa sessão real

5 min

Rode o bloco real (uma chamada, sai da sua cota).

Pronto quando: app.txt: mudança não commitada preservada, build/a.o: continua lá e saída: 0.

E14 · A guarda no seu projeto

15 min

No .claude/settings.json do seu projeto, junte as permissions do executar.json da aula 4 (deny Edit(goal.md), Edit(tests/**), Edit(.claude/**)) com o bloco hooks do settings-exemplo.json: as camadas se somam. Se o seu juiz tem outros arquivos, exporte GUARDA_PROTEGIDOS antes de abrir o Claude Code.

Pronto quando: K=~/projetos/loop-r-eng-seguranca-v72/kit; printf '{"cwd":"%s","tool_name":"Bash","tool_input":{"command":"echo x > goal.md"}}' "$PWD" | python3 $K/hooks/guarda.py | grep -c deny → 1 e grep -c 'Edit(goal.md)' .claude/settings.json → 1, rodados na pasta do projeto.

07Teste da aula

Pergunta 1 de 4

O que um hook PreToolUse devolve para bloquear?competência

08Em três frases

Hook roda códigoAntes de cada ferramenta, pode negar, perguntar ou deixar seguir.
Teste pelo JSONChamada falsa no stdin, decisão no stdout: sem modelo, sem cota.
Guarde a guardaJuiz, objetivo, settings e o hook protegidos em três camadas: deny Edit da aula 4, a guarda (também contra escrita via Bash) e o sandbox da aula 6.

Para ir além

Aula anteriorPróxima aula