feat(safety): PreToolUse hook gating destructive tool calls (FNDG-04b) #3

Open
olivier wants to merge 2 commits from security/fndg-04b-dangerously-skip-permissions into main
Owner

Summary

FNDG-04b Option A (decided by Olivier 2026-04-19): deploy a PreToolUse hook into every project before launching a Claude Code agent. The hook blocks destructive Bash/Edit/Write patterns so --dangerously-skip-permissions cannot be leveraged via MCP sessions.send into arbitrary execution.

  • New internal/safety/ package — go:embed hook script + EnsureHookDeployed(projectDir) that writes the hook (0555) and merges .claude/settings.json without touching other keys.
  • Wired into internal/dispatcher/dispatcher.go launchAgent (fail-closed) and internal/switcher/account_switcher.go relaunchDedicatedSessions (fail-open — initial deploy remains in place).
  • Blocks: rm -rf on root/home, dd of=/dev/*, mkfs*, git push --force (allows --force-with-lease), git reset --hard on main/master/production, sudo outside allowlist, curl|sh, fork bomb, chmod 777 on system paths, writes to .claude/settings*.json, .claude/hooks/, ~/.ssh/authorized_keys, shell rc, /etc/sudoers*, /etc/systemd/*.
  • Warns (logged, not blocked): kubectl delete, helm uninstall, terraform destroy, destructive SQL.

Scope note: This is Option A only. Option C (two separate pools ccl-auto-* + ccl-safe-*) will be handled in a separate task.

Cross-repo coordination: FNDG-04 input sanitization in secuaas-mcp is the first line of defence; this PR is the second line. The bash dispatcher lib-common.sh (in dev-management/agent-orchestrator/) also needs to source this hook — that wiring is tracked as a follow-up task.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test ./internal/safety/... — 8 tests pass (file layout, idempotence, merge preserving existing security-gate.sh, matcher refresh, empty-dir rejection, 9 destructive inputs blocked, 7 benign inputs allowed)
  • go test ./internal/dispatcher/... — new TestDispatchProjectDeploysSafetyHook verifies the hook is written before any claude invocation
  • go test ./... full suite pass
  • Manual: launch a test session, send rm -rf ~ via sessions.send, verify blocked + logged in ~/.claude/safety-hook.log (to be done post-merge on staging)
  • Update secuaas-mcp audit report docs/audit-mcp-stdio-2026-04-18.md to mark FNDG-04b as "CORRIGÉ partiellement (Option A) commit $(git rev-parse HEAD) — Option C en cours" (follow-up commit in secuaas-mcp)

References

  • Task: .agent-queue/inbox/20260418-211102-fndg-04b-dangerously-skip-permissions-sessions-headless.md
  • FNDG-04 audit: secuaas-mcp branch audit/mcp-stdio-2026-04-18
  • Docs: docs/security/claude-safety-hook.md

🤖 Generated with Claude Code

## Summary FNDG-04b Option A (decided by Olivier 2026-04-19): deploy a `PreToolUse` hook into every project before launching a Claude Code agent. The hook blocks destructive Bash/Edit/Write patterns so `--dangerously-skip-permissions` cannot be leveraged via MCP `sessions.send` into arbitrary execution. - New `internal/safety/` package — `go:embed` hook script + `EnsureHookDeployed(projectDir)` that writes the hook (`0555`) and merges `.claude/settings.json` without touching other keys. - Wired into `internal/dispatcher/dispatcher.go` `launchAgent` (fail-closed) and `internal/switcher/account_switcher.go` `relaunchDedicatedSessions` (fail-open — initial deploy remains in place). - Blocks: `rm -rf` on root/home, `dd of=/dev/*`, `mkfs*`, `git push --force` (allows `--force-with-lease`), `git reset --hard` on main/master/production, `sudo` outside allowlist, `curl|sh`, fork bomb, `chmod 777` on system paths, writes to `.claude/settings*.json`, `.claude/hooks/`, `~/.ssh/authorized_keys`, shell rc, `/etc/sudoers*`, `/etc/systemd/*`. - Warns (logged, not blocked): `kubectl delete`, `helm uninstall`, `terraform destroy`, destructive SQL. **Scope note:** This is **Option A only**. Option C (two separate pools `ccl-auto-*` + `ccl-safe-*`) will be handled in a separate task. **Cross-repo coordination:** FNDG-04 input sanitization in `secuaas-mcp` is the first line of defence; this PR is the second line. The bash dispatcher `lib-common.sh` (in `dev-management/agent-orchestrator/`) also needs to source this hook — that wiring is tracked as a follow-up task. ## Test plan - [x] `go build ./...` clean - [x] `go vet ./...` clean - [x] `go test ./internal/safety/...` — 8 tests pass (file layout, idempotence, merge preserving existing `security-gate.sh`, matcher refresh, empty-dir rejection, 9 destructive inputs blocked, 7 benign inputs allowed) - [x] `go test ./internal/dispatcher/...` — new `TestDispatchProjectDeploysSafetyHook` verifies the hook is written before any `claude` invocation - [x] `go test ./...` full suite pass - [ ] Manual: launch a test session, send `rm -rf ~` via `sessions.send`, verify blocked + logged in `~/.claude/safety-hook.log` (to be done post-merge on staging) - [ ] Update secuaas-mcp audit report `docs/audit-mcp-stdio-2026-04-18.md` to mark FNDG-04b as "CORRIGÉ partiellement (Option A) commit `$(git rev-parse HEAD)` — Option C en cours" (follow-up commit in secuaas-mcp) ## References - Task: `.agent-queue/inbox/20260418-211102-fndg-04b-dangerously-skip-permissions-sessions-headless.md` - FNDG-04 audit: `secuaas-mcp` branch `audit/mcp-stdio-2026-04-18` - Docs: `docs/security/claude-safety-hook.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
olivier added 1 commit 2026-04-19 17:49:27 +00:00
Adds internal/safety/ — the in-repo source of truth for the PreToolUse hook
deployed into every project before a Claude Code agent is launched. The hook
blocks destructive Bash/Edit/Write patterns on sessions running with
--dangerously-skip-permissions, closing the exploitation path where a prompt
injection via MCP sessions.send could otherwise trigger arbitrary destruction
without interactive confirmation.

Wire-up:
- internal/dispatcher/dispatcher.go launchAgent: deploys hook before claude
  launch; fail-closed if deployment fails.
- internal/switcher/account_switcher.go relaunchDedicatedSessions: redeploys
  hook before --resume after account failover; fail-open (log + continue)
  since the initial deployment is still in place.

Blocks (exit 2, stderr shown to model):
- rm -rf targeting /, ~, $HOME, /etc, /var, /usr, /boot
- dd of=/dev/{sd,nvme,disk,hd,mmcblk}*, mkfs*
- git push --force (but allows --force-with-lease)
- git reset --hard on main|master|production
- sudo outside short allowlist (systemctl, journalctl, cp, install, apt*)
- curl|sh, bash <(curl ...), eval "$(curl ...)", fork bomb, crontab -e
- chmod 777 on system paths / home
- Writes to .claude/settings*.json, .claude/hooks/, ~/.ssh/authorized_keys,
  shell rc files, /etc/sudoers*, /etc/systemd/*

Warn-only (logged, not blocked):
- kubectl delete, helm uninstall, terraform destroy
- DROP TABLE, TRUNCATE TABLE, DELETE FROM ... WHERE 1=1

Hook script is embedded via //go:embed so a single binary release carries
the authoritative copy. Every launch rewrites the deployed file with mode
0555 (anti-tamper); the hook itself also blocks writes to .claude/hooks/
for defense in depth.

Decision: Olivier, 2026-04-19 — Option A now, Option C (two pools) tracked
separately. Complements FNDG-04 input sanitization in secuaas-mcp.

Tests: 8 unit/integration tests in internal/safety/, plus a dispatcher-level
test verifying the hook is written before launch. go vet clean, go test ./...
all pass.

Refs: FNDG-04 audit (secuaas-mcp branch audit/mcp-stdio-2026-04-18)
Task:  .agent-queue/inbox/20260418-211102-fndg-04b-*.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
olivier added 1 commit 2026-04-19 17:50:41 +00:00
- hook.sh bash 5.2 regex fix (re_curlpipe variable)
- TestDispatchProjectDeploysSafetyHook (dispatcher_test.go)
- deploy_safety_hook() in lib-common.sh + launch-agent.sh (dev-management)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin security/fndg-04b-dangerously-skip-permissions:security/fndg-04b-dangerously-skip-permissions
git checkout security/fndg-04b-dangerously-skip-permissions

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git checkout main
git merge --no-ff security/fndg-04b-dangerously-skip-permissions
git checkout security/fndg-04b-dangerously-skip-permissions
git rebase main
git checkout main
git merge --ff-only security/fndg-04b-dangerously-skip-permissions
git checkout security/fndg-04b-dangerously-skip-permissions
git rebase main
git checkout main
git merge --no-ff security/fndg-04b-dangerously-skip-permissions
git checkout main
git merge --squash security/fndg-04b-dangerously-skip-permissions
git checkout main
git merge --ff-only security/fndg-04b-dangerously-skip-permissions
git checkout main
git merge security/fndg-04b-dangerously-skip-permissions
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: olivier/claude-failover#3
No description provided.