feat(guardrails): push every commit, static deny for destructive tools, brief carries no user authority

Layer C of the plan written after the 2026-09-21 wipe (BDR-095): a reviewer
sub-agent traced `lftp mirror --delete` against a local file:// tree, the
prose tiers named neither lftp nor a local trace, the brief had authorized
it, and four days of commits had never left the machine.

- gitflow: `start` pushes the branch with its upstream, merge targets are
  pushed after each merge, and `init`/`install-hook` write post-commit and
  post-merge hooks that push every commit as it lands (warn, never block;
  GITFLOW_NO_PUSH=1 for throwaway repos). T18 + T19 (installed == emitted).
- hooks/unpushed-guard.sh on SessionStart and Stop: branch ahead of its
  upstream, no upstream, or no origin. Non-blocking systemMessage.
- settings.json: static deny for transfer and mirror tools, rsync --delete,
  xargs rm, pipe-to-shell, chmod/chown -R, sudo/doas/pkexec, disk tools,
  chattr, docker volume drops/prune/--privileged/socket/-v /:, git history
  destruction, --no-verify and core.hooksPath; new hard_deny "destructive
  tool against a local path, brief carries no user authority"; soft_deny
  reworded + discarding uncommitted work; environment records the incident.
- CLAUDE.global.md "Destructive tools & data loss"; the four report-only
  agents trace by reading, never by running, whatever the brief says.
- lib/tests/guard-bash.test.sh: executable spec of the PreToolUse guard
  (214 cases). The hook itself is not shipped (BLK-022); the spec skips.
This commit is contained in:
bastien
2026-09-22 07:43:12 +02:00
parent 475200bc83
commit 9da5d8d52c
16 changed files with 699 additions and 18 deletions
+40
View File
@@ -50,6 +50,27 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
and never runs it itself (it interviews the user). Skipped for single
component reviews and non-UI work.
- **Every commit is pushed as it lands.** `gitflow start` pushes the new
branch with its upstream, `gitflow finish` pushes each merge target, and
`gitflow init` / `install-hook` now write `post-commit` and `post-merge`
hooks next to `pre-commit` that push the current branch after every
commit and merge (`--follow-tags`, 30 s timeout, `GITFLOW_NO_PUSH=1` to
opt out in throwaway repos). A failed push warns loudly and never blocks
the commit. Existing projects get the hooks by re-running
`bash ~/.claude/lib/gitflow.sh install-hook`. Covered by `gitflow-test.sh`
T18 (bare origin: start, commit, opt-out, unreachable origin, finish) and
T19 (installed hooks equal the emitted ones, LRN-114 drift gate).
- `hooks/unpushed-guard.sh` on `SessionStart` and `Stop`: a warning when the
branch is ahead of its upstream, has no upstream, or has no `origin`; at
session start also the count of uncommitted changes. Non-blocking.
- `lib/tests/guard-bash.test.sh`: the executable spec of a PreToolUse Bash
guard (transfer tools, sync deletes, recursive `rm` outside the project,
bulk permissions, privilege escalation, disk tools, docker privileges and
system mounts, git history destruction, writes into system zones,
guardrail tampering, pipe-to-shell, nested forms, scripts the command
runs). The hook itself is not shipped (BLK-022); the spec skips cleanly
until it lands.
### Changed
- **Design gate: `magic` → the `21st` CLI in the required-manual slot.**
`design.profile`'s `GATE-BLOCK` now lists `21st` (CLI channel) and
@@ -150,6 +171,25 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
`Bash(grep * .env*)` family: a `cat .env | sed` pipe still slips past,
which is what the `hard_deny` exfiltration rule is there to catch.
- **Data-loss guardrails after the 2026-09-21 wipe** (BDR-095). Static
`permissions.deny` now refuses transfer and mirror tools (`lftp`, `sftp`,
`ftp`, `curl -T`), `rsync --delete`, `xargs rm`, pipe-to-shell,
`chmod`/`chown -R`, `sudo`/`doas`/`pkexec`, disk tools, `chattr`, docker
volume drops, `system prune`, `compose down -v`, `--privileged`, the
docker socket and `-v /:`, and git history destruction (`push --delete`,
`--mirror`, `:ref`, `--force-with-lease`, `branch -D`, `filter-branch`,
`reflog expire`, `stash clear`/`drop`, `clean -f`, `--no-verify`,
`core.hooksPath`). The pipe-to-shell and stash entries left `ask`, which is
unreliable under auto mode. New `autoMode.hard_deny`: a destructive tool
aimed at a path built from a variable, `~`, `..`, a wildcard, or outside
the project and the temp dir, including as a trace or a rehearsal that a
brief allows; a sub-agent brief carries no user authority. `soft_deny`
reworded for the promoted docker items and gains "discarding uncommitted
work". `environment` records the incident, the push discipline, and that
Claude never runs a deploy. `CLAUDE.global.md` gains "Destructive tools &
data loss"; the four report-only agents state that a destructive tool is
traced by reading, never by running, whatever the brief says.
### Removed
- **`magic` MCP (`@21st-dev/magic`) and `MAGIC_API_KEY`**, with the two risks
attached to them: the unauthenticated `127.0.0.1` callback server