Add Claude Code hooks and new-credential skill
Hooks: - PreToolUse: block direct edits to credential files (99-claude, etc.) - PostToolUse: auto-run shellcheck after editing bash.d scripts Skill: - /new-credential: scaffolds a credential file pair (.example template + real file), adds to .gitignore, sets permissions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2b747b6945
commit
ed82cebd16
4 changed files with 130 additions and 0 deletions
23
.claude/hooks/block-credential-edit.sh
Executable file
23
.claude/hooks/block-credential-edit.sh
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# PreToolUse hook: block direct edits to credential files.
|
||||||
|
# Only .example templates should be modified — real secrets stay untouched.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
input=$(cat)
|
||||||
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
||||||
|
|
||||||
|
# No file path in input (e.g. Bash tool) — allow
|
||||||
|
[[ -z "$file_path" ]] && exit 0
|
||||||
|
|
||||||
|
basename=$(basename "$file_path")
|
||||||
|
|
||||||
|
# Block known credential files (but allow .example templates)
|
||||||
|
case "$basename" in
|
||||||
|
99-claude|99-gemini|99-google|99-huggingface|99-replicate)
|
||||||
|
echo "Blocked: do not edit credential files directly — edit the .example template instead" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
31
.claude/hooks/shellcheck-on-edit.sh
Executable file
31
.claude/hooks/shellcheck-on-edit.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# PostToolUse hook: run shellcheck after editing a bash.d script.
|
||||||
|
# Skips non-script files (markdown, .gitignore, etc).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
input=$(cat)
|
||||||
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
|
||||||
|
|
||||||
|
# No file path — nothing to check
|
||||||
|
[[ -z "$file_path" ]] && exit 0
|
||||||
|
|
||||||
|
basename=$(basename "$file_path")
|
||||||
|
|
||||||
|
# Only check files that match the numbered script naming convention
|
||||||
|
# or .example templates (which are also valid shell scripts)
|
||||||
|
case "$basename" in
|
||||||
|
[0-9][0-9]-*|[0-9][0-9]-*.example) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# File must still exist (Write could have been to a new path)
|
||||||
|
[[ -f "$file_path" ]] || exit 0
|
||||||
|
|
||||||
|
# Run shellcheck — exit 2 feeds stderr back to Claude
|
||||||
|
if ! shellcheck "$file_path" 2>&1; then
|
||||||
|
echo "shellcheck failed on $basename — please fix the issues above" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
26
.claude/settings.json
Normal file
26
.claude/settings.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-credential-edit.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/shellcheck-on-edit.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
50
.claude/skills/new-credential/SKILL.md
Normal file
50
.claude/skills/new-credential/SKILL.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
name: new-credential
|
||||||
|
description: Create a new bash.d credential file with .example template, .gitignore entry, and correct permissions
|
||||||
|
user-invocable: true
|
||||||
|
disable-model-invocation: true
|
||||||
|
arguments:
|
||||||
|
- name: name
|
||||||
|
description: "Short name for the credential (e.g. 'openai', 'stripe')"
|
||||||
|
required: true
|
||||||
|
- name: var
|
||||||
|
description: "Environment variable name to export (e.g. 'OPENAI_API_KEY')"
|
||||||
|
required: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a new credential file pair in ~/.bash.d/ following the project conventions.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Create the `.example` template** at `99-$name.example` (mode 644):
|
||||||
|
```bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# <Description of what this credential is for>
|
||||||
|
# Copy to 99-$name and fill in your token, then: chmod 700 99-$name
|
||||||
|
require_private "${BASH_SOURCE[0]}"
|
||||||
|
export $var=your-token-here
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create the real credential file** at `99-$name` (mode 700):
|
||||||
|
```bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# NOTE: Contains credentials - ensure file permissions remain 600/700
|
||||||
|
require_private "${BASH_SOURCE[0]}"
|
||||||
|
export $var=your-token-here
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add `99-$name` to `.gitignore`** (append to the existing credential list)
|
||||||
|
|
||||||
|
4. **Set permissions**: `chmod 700 99-$name`
|
||||||
|
|
||||||
|
5. **Validate both files**: `shellcheck 99-$name.example 99-$name`
|
||||||
|
|
||||||
|
6. **Remind the user** to edit `99-$name` and fill in the real secret value
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- The `.example` template must NOT contain real secrets — use `your-token-here` as placeholder
|
||||||
|
- The real credential file must have mode `700`
|
||||||
|
- Both files must start with `# shellcheck shell=bash`
|
||||||
|
- Both files must call `require_private "${BASH_SOURCE[0]}"` as the first functional line
|
||||||
|
- Only the `.example` file should be staged in git — verify `99-$name` is gitignored
|
||||||
Loading…
Add table
Add a link
Reference in a new issue