guardrails: enforce live-host and secrets policy with hooks #82

Merged
admin_emi merged 2 commits from guardrails/enforcement-hooks into dev 2026-07-25 07:09:14 +00:00
2 changed files with 25 additions and 3 deletions
Showing only changes of commit c184fd55ea - Show all commits

View file

@ -54,12 +54,31 @@ fails toward the prompt rather than toward silent execution.
## lint-after-edit needs shellcheck on PATH ## lint-after-edit needs shellcheck on PATH
It exits quietly when shellcheck is absent, so it is **inert until installed** — a It exits quietly when shellcheck is absent — a missing local tool must not break
missing local tool must not break editing, and `shell-lint` CI is still the backstop. editing, and `shell-lint` CI is still the backstop. v0.11.0 (the version CI pins) is
Install the same pinned version CI uses: installed at `~/.local/bin/shellcheck`; if you move machines, reinstall it:
```bash ```bash
VER=v0.11.0 VER=v0.11.0
curl -fsSL "https://github.com/koalaman/shellcheck/releases/download/${VER}/shellcheck-${VER}.linux.x86_64.tar.xz" \ curl -fsSL "https://github.com/koalaman/shellcheck/releases/download/${VER}/shellcheck-${VER}.linux.x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C ~/.local/bin "shellcheck-${VER}/shellcheck" | tar -xJ --strip-components=1 -C ~/.local/bin "shellcheck-${VER}/shellcheck"
``` ```
Keep it matched to `shell-lint.yml`'s pin. A drifted shellcheck grows *new* warnings
and fails CI on an unrelated push, which is why that workflow pins the version and
its sha256 rather than using `apt-get install`.
## The global copy
`~/.claude/settings.json` runs `prod-guard.sh` from `~/.claude/hooks/` as well, so a
session started **outside** this repo is covered too — otherwise the guard would be
trivially bypassed by working from `~/Code`. That copy is a mirror; this one is
canonical. If you change the classifier here, copy it over:
```bash
cp .claude/hooks/prod-guard.sh ~/.claude/hooks/prod-guard.sh
```
The blanket `Bash(ssh prod:*)` / `Bash(ssh beta:*)` allow rules were removed from
global settings at the same time, so the hook is the only thing granting live-host
access. If it is missing or broken, nothing allows the command and you get a prompt.

View file

@ -55,6 +55,9 @@ is_readonly() {
case "$stripped" in *'>'*) return 1 ;; esac case "$stripped" in *'>'*) return 1 ;; esac
# Command substitution can hide anything; refuse to reason about it. # Command substitution can hide anything; refuse to reason about it.
# shellcheck disable=SC2016 # single quotes are the point: these are literal
# `$(` and backtick characters being searched for inside the command text, not
# expansions to perform. Expanding them here would defeat the check entirely.
case "$cmd" in *'$('*|*'`'*) return 1 ;; esac case "$cmd" in *'$('*|*'`'*) return 1 ;; esac
while IFS= read -r seg; do while IFS= read -r seg; do