mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
* fix(careful): check PowerShell tool commands and nested pwsh/cmd (#3067) /careful and /guard matched only the Bash tool, so on Windows, where Claude Code's PowerShell tool is the primary shell, the guard never ran. check-careful.sh also had no PowerShell or cmd patterns. - careful/guard register a PowerShell matcher beside Bash. - hook-extract.sh gains gstack_hook_extract_tool: one parser call reads tool_name with the field, so dispatch adds no fork. - check-careful.sh runs one PowerShell/cmd pattern table on PowerShell tool commands and on the tail of any Bash command that launches pwsh/powershell/cmd: Remove-Item and aliases with -Recurse/-Force prefixes, rd/rmdir/del/erase /s, Format-Volume, Clear-Disk, Clear-Content, [IO.Directory]/[IO.File]::Delete, and asks with an explanation for -EncodedCommand, iex, Start-Process of a shell and & $cmd. Command-position anchors keep Bash negative controls allowed. - /careful documents the coverage as best-effort and points to Claude Code permission deny rules. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(freeze): NotebookEdit respects the freeze boundary (#3067) NotebookEdit was in no freeze/guard/investigate matcher, and check-freeze.sh read only file_path, so notebook edits ignored /freeze on every platform. NotebookEdit carries tool_input.notebook_path. - freeze, guard and investigate register a NotebookEdit matcher beside Edit and Write. - check-freeze.sh reads file_path, then notebook_path, through the shared extractor; denials name the tool, the field, the boundary and /unfreeze. A helper without the combined reader denies as out of date. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: run hook-scripts on the Windows lane (#3067) The careful/freeze hooks are Windows coverage, but test/hook-scripts.test.ts was excluded from windows-free-tests by the raw /tmp/ pattern; its /tmp/ literals are payload strings that are never opened. Admit it by name and gate the three cases Windows runners cannot host: a symlink (no Developer Mode), a newline in a directory name, and a POSIX ':' PATH prefix. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(evals): check-careful.sh is free-only for PR selection No paid fixture copies or runs careful/bin/check-careful.sh (the investigate boundary fixture copies hook-extract.sh and check-freeze.sh only); test/hook-scripts.test.ts covers it. As an unknown dependency it restored the full paid gate on every /careful hook change. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(careful): pin the #3067 Invoke-Sqlcmd DROP TABLE example on the PowerShell tool Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(evals): record the check-careful.sh selection change as non-behavioral Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: /careful covers PowerShell (best-effort), /freeze covers NotebookEdit (#3067) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: release v1.91.37.0 (CHANGELOG, TODOS for deferred hook follow-ups) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: re-slot release to v1.91.39.0 (v1.91.37.0 and v1.91.38.0 claimed by #3075, #3076) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: re-slot release to v1.91.41.0 (merge-queue reservation) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(careful): hook payload reader survives Windows text-mode Python and backslash source paths windows-free-tests (run 37739845723) showed every PowerShell-tool case falling through: Python's text-mode stdout on Windows wrote tool_name as "PowerShell\r", so dispatch never matched, and freeze denials carried stray CRs. gstack_hook_extract_tool now reads and writes bytes. hook-extract.sh also located gstack-state-root.sh with ${BASH_SOURCE%/*}, which strips nothing when the file is sourced through a C:\... path, so gstack_hook_state_root vanished (state-root parity case on the same run). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(evals): PR lane selects office-hours-auto-mode by its real test name The PR paid lane filters audited files with -t built from CASE_TEST_NAMES[id] ?? id. office-hours-auto-mode's Bun test is named 'AskUserQuestion surfaces when --disallowedTools AskUserQuestion is set', so the filter matched 0 tests and the shard failed (#3077 eval-slices (6), run 37741434771). Map the label, and check every audited PR-profile case is addressable by name. Recorded as non-behavioral for HARNESS_VERSION. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: CHANGELOG notes for the Windows CRLF and PR-lane name fixes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: re-slot release to v1.91.47.0 (merge-queue assignment) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
108 lines
4.8 KiB
Cheetah
108 lines
4.8 KiB
Cheetah
---
|
|
name: careful
|
|
version: 0.1.0
|
|
description: |
|
|
Safety guardrails for destructive commands. Warns before rm -rf, DROP TABLE,
|
|
force-push, git reset --hard, kubectl delete, and similar destructive operations.
|
|
User can override each warning. Use when touching prod, debugging live systems,
|
|
or working in a shared environment. Use when asked to "be careful", "safety mode",
|
|
"prod mode", or "careful mode". (gstack)
|
|
triggers:
|
|
- be careful
|
|
- warn before destructive
|
|
- safety mode
|
|
allowed-tools:
|
|
- Bash
|
|
- Read
|
|
hooks:
|
|
PreToolUse:
|
|
- matcher: "Bash"
|
|
hooks:
|
|
- type: command
|
|
command: 'bash -c "exec bash \"$HOME/.claude/skills/gstack/careful/bin/check-careful.sh\""'
|
|
statusMessage: "Checking for destructive commands..."
|
|
- matcher: "PowerShell"
|
|
hooks:
|
|
- type: command
|
|
command: 'bash -c "exec bash \"$HOME/.claude/skills/gstack/careful/bin/check-careful.sh\""'
|
|
statusMessage: "Checking for destructive commands..."
|
|
sensitive: true
|
|
---
|
|
|
|
# /careful — Destructive Command Guardrails
|
|
|
|
Safety mode is now **active**. Every Bash and PowerShell command will be checked
|
|
for destructive patterns before running. If a destructive command is detected, you'll be warned
|
|
and can choose to proceed or cancel.
|
|
|
|
```bash
|
|
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
|
|
mkdir -p "$GSTACK_STATE_ROOT"/analytics
|
|
echo '{"skill":"careful","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> "$GSTACK_STATE_ROOT"/analytics/skill-usage.jsonl 2>/dev/null || true
|
|
```
|
|
|
|
## What's protected
|
|
|
|
| Pattern | Example | Risk |
|
|
|---------|---------|------|
|
|
| `rm -rf` / `rm -r` / `rm --recursive` | `rm -rf /var/data` | Recursive delete |
|
|
| `DROP TABLE` / `DROP DATABASE` | `DROP TABLE users;` | Data loss |
|
|
| `TRUNCATE` | `TRUNCATE orders;` | Data loss |
|
|
| `git push --force` / `-f` | `git push -f origin main` | History rewrite |
|
|
| `git reset --hard` | `git reset --hard HEAD~3` | Uncommitted work loss |
|
|
| `git checkout .` / `git restore .` | `git checkout .` | Uncommitted work loss |
|
|
| `kubectl delete` | `kubectl delete pod` | Production impact |
|
|
| `docker rm -f` / `docker system prune` | `docker system prune -a` | Container/image loss |
|
|
|
|
## PowerShell and cmd (Windows)
|
|
|
|
The hook also checks the PowerShell tool (Claude Code's main Windows shell)
|
|
and any `pwsh`/`powershell`/`cmd` launched from Bash. Matching ignores case,
|
|
covers aliases in command position, accepts parameter prefixes (`-r`, `-fo`)
|
|
and strips cmd `^` escapes. All rows above apply there too.
|
|
|
|
| Pattern | Example |
|
|
|---------|---------|
|
|
| `Remove-Item`/`rm`/`ri`/`del`/`erase`/`rd`/`rmdir` + `-Recurse` or `-Force` | `gci \| ri -r -fo` |
|
|
| cmd `rd /s`, `rmdir /s`, `del /s`, `erase /s` | `cmd /c rd /s /q C:\proj` |
|
|
| `Format-Volume`, `Clear-Disk`, `Clear-Content`, `[IO.Directory]::Delete`, `[IO.File]::Delete` | `Clear-Disk -Number 1` |
|
|
| `-EncodedCommand`, `iex`, `Start-Process` of a shell, `& $cmd` (can't be inspected) | `irm $url \| iex` |
|
|
|
|
**Best-effort on PowerShell.** String matching can't see a command built at
|
|
runtime. For a hard stop, add Claude Code permission deny rules, which parse
|
|
PowerShell and its aliases: `"deny": ["PowerShell(Remove-Item *)"]` in
|
|
`.claude/settings.json`. The hook runs through `bash`, so Windows needs Git Bash.
|
|
|
|
## Safe exceptions
|
|
|
|
These patterns are allowed without warning:
|
|
- `rm -rf node_modules` / `.next` / `dist` / `__pycache__` / `.cache` / `build` / `.turbo` / `coverage`
|
|
|
|
## How it works
|
|
|
|
The hook reads `tool_name` and the command from the tool input JSON, checks it against the
|
|
patterns above, and returns a `hookSpecificOutput` payload with
|
|
`permissionDecision: "ask"` and a warning reason if a match is found (the
|
|
decision must be nested under `hookSpecificOutput` — Claude Code ignores a
|
|
top-level `permissionDecision`). You can always override a MEDIUM warning and
|
|
proceed.
|
|
|
|
## HIGH tier (hard deny)
|
|
|
|
Two catastrophic shapes are **denied**, not asked: `rm -r`/`-R` of exactly
|
|
`/`, `~`, or `$HOME`, and force-push to the repo's **default branch**. SIMPLE
|
|
commands only (no `;`, `&&`, `||`, `|`, newline) — compound shapes fall
|
|
through to the MEDIUM ask; `--force-with-lease` is never HIGH. A best-effort
|
|
advisory hard-stop, not a policy boundary: the escape hatch is ending the
|
|
opt-in, session-scoped /careful session.
|
|
|
|
## Project patterns (additive only)
|
|
|
|
Add warn rules — one POSIX ERE per line, `#` comments OK — in
|
|
`~/.gstack/careful-patterns.txt` (global) or
|
|
`~/.gstack/projects/<slug>/careful-patterns.txt` (per-project). Consulted
|
|
after the built-in families, so config can only ADD rules, never suppress a
|
|
baseline warning. Invalid regex lines are skipped.
|
|
|
|
To deactivate, end the conversation or start a new one. Hooks are session-scoped.
|