Autonomous Loop Security|INEMA.CLUB-PRO

Lesson 5 · 20 min

Hooks as a guard: before the tool, against deleting and colliding

A permission rule compares text; a hook runs code before every tool and can look at the disk. In this lesson you turn on three PreToolUse guards: one denies the irreversible and the judge's files, another shows how many files an rm would delete and asks for confirmation, and the third warns when another session touched the same file.

Start the lesson
Red safety key with a lift-up cover next to an emergency button on an industrial panel.

In this lesson you will

  1. 1PreToolUse: deny, ask or nothing, before the tool
  2. 2guarda.py: forced push, reset --hard, find -delete, the judge's files, and what it doesn't catch
  3. 3raio.mjs and colisao.mjs from inema-agent-runtime
  4. 4Test the hook with fake JSON and then in a real session

Goal: turn on a PreToolUse hook that denies the irreversible, asks for confirmation before deleting, and warns about collisions between sessions.

01Before every tool

Click each step.

Click each block in the diagram to see what it does.

Context and handoff hooks belong to course 2; here the hook is a guard.

02What each guard catches

Three files, three jobs.

1. guarda.py (deny)

It denies what can't be undone: forced push, reset --hard, git clean -f, checkout -- ., git restore (of the tree), find -delete, recursive rm at the root or in HOME (quoted or inside bash -c), pkill and killall, dd and mkfs.

And it denies writing to goal.md, tests/, .claude/ and the guard's own folder, through Edit/Write and also through Bash: > and >>, tee, sed -i, cp, mv, rm and truncate (GUARDA_PROTEGIDOS changes the list).

What it doesn't catch: the guard reads the text of the command, it is not a sandbox. A program that writes on its own (python3 -c "open('goal.md','w')", node -e, a script of yours), a path built in a variable, or eval gets through. That's why it comes together with the deny Edit(...) from lesson 4, which covers the file tools, and with the sandbox from lesson 6, which limits what Bash itself can write.

A real lesson from this machinepkill -f with the pattern on the same shell line killed the shell itself twice (Sept 15 and 16, 2026): that is why pkill is on the list.

03The hooks in settings.json

The guard goes first, then raio.mjs on Bash; guard and collision on edits. Paths assume both repositories are in ~/projetos.

File · 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 }
        ]
      }
    ]
  }
}

04Rehearsal: fake calls, real decisions

The script sends the hooks the same JSON that Claude Code would send and prints the decision. No model involved.

Terminal · guarda.py, raio.mjs and colisao.mjs
[ -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
Example from the repository
## 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            → -

"-" means no decision: the normal flow (rules and mode) decides. pytest with output going to log.txt and git restore --staged pass, because they don't touch the protected files. The python3 -c line is the declared hole: the guard doesn't see what a program writes. An rm of a folder that doesn't exist doesn't ask anything, because there is nothing to delete.

05Real: the guard in a session that allows everything

claude -p in bypassPermissions with settings-exemplo.json, asking for git reset --hard and rm -r build in a rehearsal repository with an uncommitted change. You check the disk.

Terminal · one real call
bash ~/projetos/loop-r-eng-seguranca-v72/kit/hooks/real.sh; echo "saída: $?"
Example from the repository
app.txt: mudança não commitada preservada
build/a.o: continua lá
saída: 0

06Practice

Rehearsal, real session, and the guard in your project.

E12 · Decisions of the three hooks

10 min

Run the rehearsal. It clones inema-agent-runtime into ~/projetos if it's missing.

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

E13 · The guard in a real session

5 min

Run the real block (one call, uses your subscription quota).

Done when: app.txt: mudança não commitada preservada, build/a.o: continua lá and saída: 0.

E14 · The guard in your project

15 min

In your project's .claude/settings.json, combine the permissions from executar.json in lesson 4 (deny Edit(goal.md), Edit(tests/**), Edit(.claude/**)) with the hooks block from settings-exemplo.json: the layers add up. If your judge has other files, export GUARDA_PROTEGIDOS before opening Claude Code.

Done when: 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 and grep -c 'Edit(goal.md)' .claude/settings.json → 1, run in the project folder.

07Lesson quiz

Question 1 of 4

What does a PreToolUse hook return to block?skill

08In three sentences

A hook runs codeBefore each tool call, it can deny, ask, or let it through.
Test it with JSONFake call on stdin, decision on stdout: no model, no quota.
Guard the guardJudge, goal, settings and the hook protected in three layers: deny Edit from lesson 4, the guard (also against writing via Bash) and the sandbox from lesson 6.

Going further

Previous lessonNext lesson