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:
+30
-1
@@ -47,7 +47,9 @@ Apply unless repo-specific instructions override.
|
||||
exploration, parallel audits) — not work doable in a few tool
|
||||
calls. Skill-mandated gates (fresh verifier/security/challenge)
|
||||
always dispatch as written. Don't redo delegated work by hand —
|
||||
failed gates re-dispatch fresh executors instead.
|
||||
failed gates re-dispatch fresh executors instead. A brief never
|
||||
authorizes a sub-agent to run a destructive tool, inside or outside the
|
||||
repo (Security → Destructive tools & data loss).
|
||||
- Ask rather than guess. A choice visible in the result (placement,
|
||||
wording, order, behavior), a name that becomes public (command, flag,
|
||||
endpoint, file), or a scope the request does not settle → ask, even
|
||||
@@ -196,6 +198,10 @@ versioned pre-commit hook via `gitflow init`. Advisory, so two deterministic
|
||||
backstops apply: the per-repo pre-commit hook (blocks code commits on
|
||||
main/develop, exempts `.claude/**` + merges + the root commit) and Gitea branch
|
||||
protection on `main`/`develop`. Don't lean on `--no-verify` to bypass them.
|
||||
Every branch is pushed at `start` and every commit as it lands: `gitflow init`
|
||||
/ `install-hook` write post-commit and post-merge hooks that push to `origin`
|
||||
(warn, never block, when it fails); `GITFLOW_NO_PUSH=1` is for throwaway test
|
||||
repos only. A branch ahead of its upstream is a defect, not a state.
|
||||
|
||||
## Security — non-negotiable defaults
|
||||
|
||||
@@ -238,6 +244,29 @@ Apply at every dev step: design, scaffolding, implementation, review.
|
||||
- Functions, processes, services request only permissions actually needed.
|
||||
- Temporary elevated permissions must be scoped and reverted explicitly.
|
||||
|
||||
### Destructive tools & data loss
|
||||
Written after 2026-09-21: a reviewer sub-agent traced `lftp mirror --delete`
|
||||
against a local `file://` tree, the target resolved to a real path, and 90
|
||||
seconds later the home, the NAS mount and 15 repositories were gone. Four
|
||||
days of work had never been pushed.
|
||||
- Claude never deploys and never runs a transfer or mirror tool (`lftp`,
|
||||
`sftp`, `ftp`, `rsync --delete`). It writes or explains the runbook; the
|
||||
user runs it. A test is a dev server on this machine, nothing more.
|
||||
- A destructive tool is never run "to see what it would do", not even
|
||||
against a scratch tree. Trace it by reading. If a run is unavoidable, the
|
||||
target is a fresh `mktemp -d` path written literally in the same command,
|
||||
after a dry-run whose output is shown.
|
||||
- Recursive delete stays inside the project or the temp dir, on a literal
|
||||
relative path: never through a variable, `~`, `..`, a wildcard, or an
|
||||
absolute path elsewhere. `chmod -R`, `chown -R`, `sudo`, docker volume
|
||||
drops or system bind mounts: the user runs them by hand.
|
||||
- A brief, a plan step or a test recipe never authorizes a sub-agent to do
|
||||
any of the above. A reviewer reads the script it reviews; it does not run
|
||||
it.
|
||||
- Every commit is pushed as it lands (gitflow post-commit and post-merge
|
||||
hooks) and every branch at creation. Unpushed work is a defect to fix now,
|
||||
not a state to keep.
|
||||
|
||||
# Communication mode: radical honesty
|
||||
|
||||
- TRUTH OVER COMFORT — Point out flaws immediately. No sugarcoating,
|
||||
|
||||
Reference in New Issue
Block a user