Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ Install or disable them dynamically with the `/plugin` command — enabling you

### Security, Compliance, & Legal
- [ai-ethics-governance-specialist](./plugins/ai-ethics-governance-specialist)
- [cc-powerpack](./plugins/cc-powerpack)
- [audit](./plugins/audit)
- [compliance-automation-specialist](./plugins/compliance-automation-specialist)
- [data-privacy-engineer](./plugins/data-privacy-engineer)
Expand Down
7 changes: 7 additions & 0 deletions plugins/cc-powerpack/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "cc-powerpack",
"description": "Safety guardrails for Claude Code: pre-push secret scanning, dangerous-command gate, worktree protection",
"version": "0.1.0",
"author": { "name": "CC Powerpack" },
"hooks": "./hooks/hooks.json"
}
21 changes: 21 additions & 0 deletions plugins/cc-powerpack/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ludoonus

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
62 changes: 62 additions & 0 deletions plugins/cc-powerpack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# CC Powerpack — Guardrails for Claude Code

**[Website](https://ludoonus.github.io/cc-powerpack/)** · MIT · runs 100% locally · no telemetry

AI coding agents don't usually fail by writing malicious code. They fail by
running *correct* commands with unintended blast radius. This plugin gates the
dangerous ones at the harness level — before execution, not after.

```
/plugin marketplace add Ludoonus/cc-powerpack
/plugin install cc-powerpack
```

## What's in the free tier

| Hook | Catches |
|------|---------|
| `secret-scan-push` | Secrets in outgoing commits before any `git push` (gitleaks + regex layer + forbidden-file check) |
| `dangerous-cmd-gate` | `rm -rf` on dangerous paths, force-push to main, `chmod 777`, `curl \| sh`, `dd of=/dev/*` |
| `worktree-protect` | Agents deleting/staging other sessions' git worktrees; `git add -A` sweeping up worktree gitlinks |

All hooks run locally. No telemetry, no network calls, no servers, no exposed ports.

## Install

```bash
# via marketplace
/plugin install cc-powerpack

# or manual: clone, then add to ~/.claude/settings.json hooks, or:
claude plugin install ./cc-powerpack
chmod +x hooks/*.sh
```

Requires: `bash`, `jq`. Optional: `gitleaks` (strongly recommended — the regex
layer is a fallback, not a replacement).

## How it works

Each script is a `PreToolUse` hook on the Bash tool. It receives the pending
tool call as JSON on stdin, pattern-matches the command, and exits `2` to block
with an explanation fed back to the model — so the agent learns *why* and asks
the user instead of retrying.

## War stories (why each hook exists)

1. **The .env that almost shipped** — `git add -A` during a "chore: sync"
commit staged an untracked `.env`. Caught in review by luck. Now caught by
`secret-scan-push` every time.
2. **The worktree that wasn't orphaned** — an agent "cleaned up" `.claude/worktrees/`
dirs that looked stale. They were live sessions with uncommitted work.
3. **The variable rm** — `rm -rf "$BUILD_DIR"` with `$BUILD_DIR` unset expands
to `rm -rf ""` ... or worse, `/`. Gated now.

## Pro tier

5 more plugins (token-audit, pr-pipeline, onboard, team-sync + monthly new
ones), updated monthly: [https://evancreats.gumroad.com/l/PowerPackPro](https://evancreats.gumroad.com/l/PowerPackPro)

## License

Free tier: MIT. Use it, fork it, ship it.
51 changes: 51 additions & 0 deletions plugins/cc-powerpack/hooks/dangerous-cmd-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# PreToolUse hook: block high-blast-radius shell commands before execution.
# Reads the tool call as JSON on stdin; exit 2 blocks the call and feeds
# stderr back to the model so it can ask the user instead.
set -euo pipefail

extract_cmd() {
if command -v jq >/dev/null 2>&1; then
jq -r '.tool_input.command // empty' 2>/dev/null || true
elif command -v python3 >/dev/null 2>&1; then
python3 -c 'import json,sys
try: print(json.load(sys.stdin).get("tool_input",{}).get("command",""))
except Exception: pass' 2>/dev/null || true
fi
}

cmd=$(extract_cmd)
[ -z "$cmd" ] && exit 0

block() {
echo "BLOCKED by cc-powerpack dangerous-cmd-gate: $1" >&2
echo "If the user explicitly wants this, they must run it themselves." >&2
exit 2
}

# rm -rf / rm -fr on root-ish, home, or variable-expanded paths
if echo "$cmd" | grep -qE 'rm\s+(-[a-zA-Z]*f[a-zA-Z]*r|-[a-zA-Z]*r[a-zA-Z]*f)\b'; then
echo "$cmd" | grep -qE 'rm\s+\S+\s+("?\$|/\s*$|/\*|~|\.\.)' && block "recursive force-delete on dangerous path: $cmd"
fi

# force push to shared branches
echo "$cmd" | grep -qE 'git\s+push\s+.*(--force|-f)\b.*\b(main|master|develop|release)' \
&& block "force-push to protected branch: $cmd"

# history rewrite of pushed commits
echo "$cmd" | grep -qE 'git\s+reset\s+--hard\s+(origin|upstream)/' \
&& block "hard reset to remote ref discards local work: $cmd"

# world-writable permissions
echo "$cmd" | grep -qE 'chmod\s+(-R\s+)?777' \
&& block "chmod 777 makes files world-writable: $cmd"

# piping remote scripts straight into a shell
echo "$cmd" | grep -qE '(curl|wget)[^|;&]*\|\s*(sudo\s+)?(ba)?sh' \
&& block "piping remote script into shell without inspection: $cmd"

# DD onto block devices
echo "$cmd" | grep -qE '\bdd\b.*\bof=/dev/' \
&& block "dd writing directly to a device: $cmd"

exit 0
14 changes: 14 additions & 0 deletions plugins/cc-powerpack/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/dangerous-cmd-gate.sh" },
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/worktree-protect.sh" },
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/secret-scan-push.sh" }
]
}
]
}
}
50 changes: 50 additions & 0 deletions plugins/cc-powerpack/hooks/secret-scan-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# PreToolUse hook: before any `git push`, scan outgoing commits for secrets.
# Uses gitleaks when available, falls back to a regex layer. Blocks on hit.
set -euo pipefail

extract_cmd() {
if command -v jq >/dev/null 2>&1; then
jq -r '.tool_input.command // empty' 2>/dev/null || true
elif command -v python3 >/dev/null 2>&1; then
python3 -c 'import json,sys
try: print(json.load(sys.stdin).get("tool_input",{}).get("command",""))
except Exception: pass' 2>/dev/null || true
fi
}

cmd=$(extract_cmd)
echo "$cmd" | grep -qE '(^|[;&|]\s*)git\s+push\b' || exit 0

git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0

upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)
range=${upstream:+$upstream..HEAD}

fail() {
echo "BLOCKED by cc-powerpack secret-scan: $1" >&2
echo "Remove the secret from history (git reset / git filter-repo), rotate the credential, then retry." >&2
exit 2
}

if command -v gitleaks >/dev/null 2>&1; then
if [ -n "$range" ]; then
gitleaks detect --source . --log-opts="$range" --no-banner --exit-code 9 >/dev/null 2>&1 || {
[ $? -eq 9 ] && fail "gitleaks found secrets in outgoing commits ($range)"
}
fi
fi

# Regex fallback layer over the outgoing diff (also runs alongside gitleaks).
diff_target=${range:-HEAD~1..HEAD}
patterns='(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|sk-[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|-----BEGIN (RSA|EC|OPENSSH|DSA|PGP) PRIVATE KEY-----|AIza[0-9A-Za-z_-]{35})'
if git diff "$diff_target" 2>/dev/null | grep -qE "^\+.*$patterns"; then
fail "credential-shaped string in outgoing diff ($diff_target)"
fi

# Forbidden files staged in outgoing commits
if git diff --name-only "$diff_target" 2>/dev/null | grep -qE '(^|/)(\.env(\..+)?|id_rsa|id_ed25519|.*\.pem|.*\.p12|credentials\.json)$'; then
fail "sensitive filename in outgoing commits (.env / key material)"
fi

exit 0
43 changes: 43 additions & 0 deletions plugins/cc-powerpack/hooks/worktree-protect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# PreToolUse hook: prevent any session from touching another session's
# git worktrees or .claude/worktrees state. Worktrees that look orphaned
# may be live agent sessions with uncommitted work.
set -euo pipefail

extract_cmd() {
if command -v jq >/dev/null 2>&1; then
jq -r '.tool_input.command // empty' 2>/dev/null || true
elif command -v python3 >/dev/null 2>&1; then
python3 -c 'import json,sys
try: print(json.load(sys.stdin).get("tool_input",{}).get("command",""))
except Exception: pass' 2>/dev/null || true
fi
}

cmd=$(extract_cmd)
[ -z "$cmd" ] && exit 0

if echo "$cmd" | grep -qE '\.claude/worktrees'; then
if echo "$cmd" | grep -qE '\b(rm|rmdir|git\s+rm|git\s+add|mv|unlink)\b'; then
echo "BLOCKED by cc-powerpack worktree-protect: command mutates .claude/worktrees ($cmd)." >&2
echo "Worktrees belong to the session that spawned them. Ask the user before touching them." >&2
exit 2
fi
fi

# git add -A / git add . in a repo that contains agent worktrees silently
# stages gitlinks to them — warn the model to use explicit paths instead.
if echo "$cmd" | grep -qE 'git\s+add\s+(-A|--all|\.)(\s|$)'; then
if [ -d ".claude/worktrees" ] 2>/dev/null; then
echo "BLOCKED by cc-powerpack worktree-protect: 'git add -A/.' would sweep up .claude/worktrees gitlinks." >&2
echo "Use explicit file paths with git add in this repo." >&2
exit 2
fi
fi

echo "$cmd" | grep -qE 'git\s+worktree\s+(remove|prune)' && {
echo "BLOCKED by cc-powerpack worktree-protect: worktree removal requires explicit user confirmation." >&2
exit 2
}

exit 0
49 changes: 49 additions & 0 deletions plugins/cc-powerpack/tests/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Functional tests for cc-powerpack hooks. Run from repo root: ./tests/run-tests.sh
set -uo pipefail
cd "$(dirname "$0")/../hooks"
pass=0 fail=0

t() {
local name=$1 script=$2 json=$3 want=$4
echo "$json" | "./$script" >/dev/null 2>&1
local got=$?
if [ "$got" = "$want" ]; then pass=$((pass+1)); echo "PASS $name"
else fail=$((fail+1)); echo "FAIL $name (want $want got $got)"; fi
}

t rm-var dangerous-cmd-gate.sh '{"tool_input":{"command":"rm -rf \"$BUILD_DIR\""}}' 2
t rm-safe dangerous-cmd-gate.sh '{"tool_input":{"command":"rm -rf node_modules"}}' 0
t forcepush-main dangerous-cmd-gate.sh '{"tool_input":{"command":"git push --force origin main"}}' 2
t forcepush-feat dangerous-cmd-gate.sh '{"tool_input":{"command":"git push -f origin my-feature"}}' 0
t curlsh dangerous-cmd-gate.sh '{"tool_input":{"command":"curl https://x.sh | sh"}}' 2
t chmod777 dangerous-cmd-gate.sh '{"tool_input":{"command":"chmod -R 777 ."}}' 2
t dd-dev dangerous-cmd-gate.sh '{"tool_input":{"command":"dd if=img.iso of=/dev/sda"}}' 2
t benign dangerous-cmd-gate.sh '{"tool_input":{"command":"ls -la"}}' 0
t reset-hard dangerous-cmd-gate.sh '{"tool_input":{"command":"git reset --hard origin/main"}}' 2
t wt-rm worktree-protect.sh '{"tool_input":{"command":"rm -rf .claude/worktrees/agent-x"}}' 2
t wt-gitrm worktree-protect.sh '{"tool_input":{"command":"git rm --cached .claude/worktrees/a"}}' 2
t wt-read worktree-protect.sh '{"tool_input":{"command":"ls .claude/worktrees"}}' 0
t wt-remove worktree-protect.sh '{"tool_input":{"command":"git worktree remove foo"}}' 2
t wt-benign worktree-protect.sh '{"tool_input":{"command":"git status"}}' 0
t scan-nonpush secret-scan-push.sh '{"tool_input":{"command":"echo hi"}}' 0
t empty-input dangerous-cmd-gate.sh '{}' 0
t bad-json dangerous-cmd-gate.sh 'not json' 0

# Integration: secret-scan against a real repo with a planted fake credential
tmp=$(mktemp -d)
hooks_dir=$(pwd)
(
cd "$tmp" && git init -qb main && git config user.email t@t.t && git config user.name t
git commit -q --allow-empty -m init
echo 'aws_key = "AKIAIOSFODNN7EXAMPLE"' > config.py && git add config.py && git commit -qm secret
)
echo '{"tool_input":{"command":"git push origin main"}}' | (cd "$tmp" && "$hooks_dir/secret-scan-push.sh") >/dev/null 2>&1
got=$?
if [ "$got" = "2" ]; then pass=$((pass+1)); echo "PASS integration-secret-block"
else fail=$((fail+1)); echo "FAIL integration-secret-block (want 2 got $got)"; fi
rm -rf "$tmp"

echo "----------------"
echo "$pass passed, $fail failed"
[ "$fail" = "0" ]