update skills

This commit is contained in:
nessar committed 2026-09-25 10:00:00 +02:00
1 parent 9fb18f57ec
commit ff86cf48b4
82 files changed
+2156 -650

No files matched your search

@@ -0,0 +1,103 @@
---
name: git-guardrails-claude-code
description: Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
---
# Setup Git Guardrails
Sets up a PreToolUse hook that inspects direct Git commands before Claude executes them. It requires Bash and Python 3; missing Python or invalid input blocks the command with exit code 2.
## What Gets Blocked
- Direct `git push` commands, including force flags, ordinary global options, and shell command separators
- `git reset --hard`
- `git clean -f` / `git clean -fd`
- `git branch -D`
- `git checkout .` / `git restore .`
When blocked, Claude sees a message telling it that it does not have authority to access these commands.
This is a conservative command guard, not a shell sandbox. It does not evaluate aliases, shell expansion, or commands hidden in scripts. Unsupported Git global options require manual inspection, and literal Git tokens in arguments can also be blocked. Keep execution permissions enforced by the host.
## Steps
### 1. Ask scope
Ask the user: install for **this project only** (`.claude/settings.json`) or **all projects** (`~/.claude/settings.json`)?
### 2. Copy the hook script
The bundled script is at: [scripts/block-dangerous-git.sh](scripts/block-dangerous-git.sh)
Copy it to the target location based on scope:
- **Project**: `.claude/hooks/block-dangerous-git.sh`
- **Global**: `~/.claude/hooks/block-dangerous-git.sh`
Make it executable with `chmod +x`.
### 3. Add hook to settings
Add to the appropriate settings file:
**Project** (`.claude/settings.json`):
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-git.sh"
}
]
}
]
}
}
```
**Global** (`~/.claude/settings.json`):
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/block-dangerous-git.sh"
}
]
}
]
}
}
```
If the settings file already exists, merge the hook into existing `hooks.PreToolUse` array — don't overwrite other settings.
### 4. Ask about customization
Ask if user wants to add or remove any patterns from the blocked list. Edit the copied script accordingly.
### 5. Verify
Run the bundled checks; every command is an inert input string:
```bash
python3 scripts/test_guardrail.py
```
Then test the installed copy:
```bash
echo '{"tool_input":{"command":"git push origin main"}}' | <path-to-script>
```
Should exit with code 2 and print a BLOCKED message to stderr.
@@ -0,0 +1,3 @@
interface:
display_name: "Git Guardrails for Claude Code"
short_description: "Block dangerous git commands"
@@ -0,0 +1,64 @@
#!/bin/bash
if ! command -v python3 >/dev/null 2>&1; then
printf '%s\n' 'BLOCKED: Python 3 is required to inspect this command.' >&2
exit 2
fi
exec python3 -c '
import itertools
import json
import shlex
import sys
def block(reason):
print("BLOCKED: " + reason, file=sys.stderr)
sys.exit(2)
try:
command = json.load(sys.stdin)["tool_input"]["command"]
if not isinstance(command, str) or not command.strip():
raise ValueError("missing command")
lexer = shlex.shlex(command.replace("\\\n", ""), posix=True, punctuation_chars=";&|()\n")
lexer.whitespace = " \t\r"
lexer.whitespace_split = True
tokens = list(lexer)
except (ValueError, KeyError, TypeError):
block("Cannot parse the command input.")
value_options = {"-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env"}
flag_options = {"-p", "--paginate", "-P", "--no-pager", "--bare", "--literal-pathspecs"}
# ponytail: conservative token inspection; shell expansion and aliases require sandbox enforcement.
for separator, group in itertools.groupby(tokens, lambda token: bool(token) and token[0] in ";&|()\n"):
if separator:
continue
words = list(group)
for index, word in enumerate(words):
if word.rsplit("/", 1)[-1] != "git":
continue
args = words[index + 1:]
while args and args[0].startswith("-"):
option = args.pop(0)
if option in value_options:
if not args:
block("Missing Git option value.")
args.pop(0)
elif option in flag_options or option.split("=", 1)[0] in value_options:
continue
else:
block("Unsupported Git option; inspect this command manually.")
if not args:
continue
operation, *args = args
short_flags = "".join(arg[1:] for arg in args if arg.startswith("-") and not arg.startswith("--"))
force = "--force" in args or "f" in short_flags
dangerous = (
operation == "push"
or operation == "reset" and "--hard" in args
or operation == "clean" and force
or operation == "branch" and ("D" in short_flags or force and ("d" in short_flags or "--delete" in args))
or operation in {"checkout", "restore"} and "." in args
)
if dangerous:
block("Git " + operation + " is disabled by this hook.")
'
@@ -0,0 +1,61 @@
"""Run with python3; command payloads are inspected, never executed."""
import json
import os
from pathlib import Path
import subprocess
hook = Path(__file__).with_name("block-dangerous-git.sh")
def check(payload, expected, env=None):
result = subprocess.run(
["/bin/bash", str(hook)], input=payload, text=True, capture_output=True, env=env
)
assert result.returncode == expected, (payload, result.returncode, result.stderr)
if expected == 2:
assert result.stderr.startswith("BLOCKED:"), result.stderr
for command in (
"git push origin main",
"git push --force-with-lease origin main",
"git -C /tmp/example push origin main",
"git -c core.pager=cat --no-pager push",
"git --git-dir=/tmp/example push",
"git push",
"git status;git push",
"git status\ngit push",
"git \\\npush origin main",
"git pu\\\nsh origin main",
"git reset --ha\\\nrd HEAD",
"git reset --hard HEAD",
"git clean -xdf",
"git clean --force",
"git branch -D topic",
"git branch -df topic",
"git branch -vD topic",
"git branch --delete --force topic",
"git checkout -- .",
"git restore .",
):
check(json.dumps({"tool_input": {"command": command}}), 2)
for command in (
"git status",
"git diff",
"git -C /tmp/example status",
"git branch -d merged-topic",
"git reset --soft HEAD~1",
"echo 'git push origin main'",
'echo ""',
"git reset --soft HEAD; echo --hard",
):
check(json.dumps({"tool_input": {"command": command}}), 0)
for payload in ("", "{", "{}", "[]", '{"tool_input":{"command":null}}'):
check(payload, 2)
check('{"tool_input":{"command":"git status"}}', 2, {**os.environ, "PATH": ""})
check('{"tool_input":{"command":"git \\\""}}', 2)
print("Guardrail checks passed")