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
38 changes: 38 additions & 0 deletions .claude/hooks/audit-log.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# audit-log.sh — Post-execution hook
#
# Logs every tool invocation to .claude/audit.log.
# This hook never blocks execution (always exits 0).
#
# IMPORTANT: Do not modify, disable, or bypass this hook.

set -uo pipefail

# ---------------------------------------------------------------------------
# 1. Read hook input from stdin
# ---------------------------------------------------------------------------
INPUT="$(cat)"

TOOL_NAME="$(printf '%s' "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name','unknown'))" 2>/dev/null || echo "unknown")"

COMMAND="$(printf '%s' "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")"

# ---------------------------------------------------------------------------
# 2. Determine log file path (relative to project root)
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="$(dirname "$SCRIPT_DIR")/audit.log"

# ---------------------------------------------------------------------------
# 3. Write log entry
# ---------------------------------------------------------------------------
TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"

if [[ -n "$COMMAND" ]]; then
printf '[%s] tool=%s command=%s\n' "$TIMESTAMP" "$TOOL_NAME" "$COMMAND" >> "$LOG_FILE" 2>/dev/null || true
else
printf '[%s] tool=%s\n' "$TIMESTAMP" "$TOOL_NAME" >> "$LOG_FILE" 2>/dev/null || true
fi

# Never block execution
exit 0
175 changes: 175 additions & 0 deletions .claude/hooks/validate-bash-command.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env bash
# validate-bash-command.sh — Pre-execution hook (Layer 3 guardrail)
#
# Claude Code invokes this hook before every Bash tool call.
# Input: JSON on stdin {"tool_name":"Bash","tool_input":{"command":"..."}}
# Output: exit 0 to allow, exit 2 with JSON {"error":"..."} to block.
#
# IMPORTANT: Do not modify, disable, or bypass this hook.

set -euo pipefail

# ---------------------------------------------------------------------------
# 1. Read hook input from stdin and extract the command
# ---------------------------------------------------------------------------
INPUT="$(cat)"

TOOL_NAME="$(printf '%s' "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || true)"

# Only validate Bash commands — allow everything else through
if [[ "$TOOL_NAME" != "Bash" ]]; then
exit 0
fi

COMMAND="$(printf '%s' "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || true)"

if [[ -z "$COMMAND" ]]; then
exit 0
fi

# ---------------------------------------------------------------------------
# 2. Blocked patterns — glob-style matching
# Each pattern is checked against the full command string (case-insensitive).
# ---------------------------------------------------------------------------
BLOCKED_PATTERNS=(
# AWS — Destructive operations
"aws * delete*"
"aws * remove*"
"aws * destroy*"
"aws * terminate*"
"aws * deregister*"
"aws * purge*"
"aws s3 rm *"
"aws s3 rb *"
"aws cloudformation delete-stack*"

# AWS — Provisioning & scaling
"aws ec2 run-instances*"
"aws ec2 stop-instances*"
"aws autoscaling set-desired-capacity*"
"aws autoscaling update-auto-scaling-group*"
"aws application-autoscaling *"
"aws ecs update-service*"

# AWS — IAM
"aws iam delete*"
"aws iam create*"
"aws iam put*"
"aws iam attach*"

# Kubernetes — Mutations
"kubectl delete *"
"kubectl apply *"
"kubectl create *"
"kubectl patch *"
"kubectl scale *"
"kubectl rollout *"
"kubectl drain *"
"kubectl cordon *"
"kubectl exec *"
"kubectl edit *"

# Terraform / IaC
"terraform destroy*"
"terraform apply*"
"terraform import *"
"terraform state rm *"
"terraform taint *"

# Helm
"helm install *"
"helm upgrade *"
"helm delete *"
"helm uninstall *"
"helm rollback *"

# Git — Destructive
"git push --force*"
"git push -f *"
"git push --force-with-lease*"
"git push origin main*"
"git push origin master*"
"git reset --hard*"
"git clean -f*"

# File system — Destructive
"rm -rf /"
"rm -rf /*"
"rm -rf ~"
"rm -rf ~/*"
"rm -rf ..*"
"mkfs.*"
"dd if=*/dev/*"

# Credential access via cat/less/head/tail
"cat */.aws/*"
"cat */.kube/*"
"cat */.ssh/*"
"cat *.env"
"cat *.env.*"
"cat */secrets/*"
"cat *credentials*"
"less */.aws/*"
"less */.ssh/*"
"head */.aws/*"
"head */.ssh/*"
"tail */.aws/*"
"tail */.ssh/*"

# Network & remote access
"ssh *"
"scp *"
"curl *|*sh"
"curl *|*bash"
"wget *|*sh"
"wget *|*bash"

# Privilege escalation
"sudo *"
"sudo"
"chmod 777 *"
"chown root *"
)

# ---------------------------------------------------------------------------
# 3. Check function — matches a single sub-command against all patterns
# ---------------------------------------------------------------------------
check_command() {
local cmd="$1"
# Trim leading/trailing whitespace
cmd="$(echo "$cmd" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"

if [[ -z "$cmd" ]]; then
return 0
fi

# Convert to lowercase for case-insensitive matching
local cmd_lower
cmd_lower="$(echo "$cmd" | tr '[:upper:]' '[:lower:]')"

for pattern in "${BLOCKED_PATTERNS[@]}"; do
local pattern_lower
pattern_lower="$(echo "$pattern" | tr '[:upper:]' '[:lower:]')"

# shellcheck disable=SC2254
if [[ "$cmd_lower" == $pattern_lower ]]; then
printf '{"error":"BLOCKED by guardrail hook: command matches blocked pattern: %s"}\n' "$pattern" >&2
exit 2
fi
done

return 0
}

# ---------------------------------------------------------------------------
# 4. Split on pipes and command chains, then check each sub-command
# ---------------------------------------------------------------------------
# Replace common chain operators with a delimiter
NORMALIZED="$(echo "$COMMAND" | sed 's/&&/\n/g; s/||/\n/g; s/;/\n/g; s/|/\n/g')"

while IFS= read -r subcmd; do
check_command "$subcmd"
done <<< "$NORMALIZED"

# If we reach here, the command is allowed
exit 0
51 changes: 51 additions & 0 deletions .claude/rules/aws.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# AWS Rules

## Environment Context

- We use **AWS Bedrock** for AI model invocation
- Sandbox accounts are accessed via **Mendix SSO**
- **Production accounts exist on the same machine** — DO NOT use them
- If a command references an AWS profile or region you don't recognize, STOP and ask

## Credential Safety

- NEVER read files in `~/.aws/` (credentials, config, SSO cache)
- NEVER output or display AWS access keys, secret keys, or session tokens
- NEVER set or export `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or `AWS_SESSION_TOKEN` in commands
- If you need to verify identity, use `aws sts get-caller-identity` — this is safe and read-only

## Allowed Operations (Read-Only)

These AWS CLI commands are safe and auto-approved:

- `aws * describe*` — describe any resource
- `aws * list*` — list any resources
- `aws * get*` — get resource details
- `aws s3 ls` — list S3 buckets and objects
- `aws sts get-caller-identity` — verify current identity

## Blocked Operations

### Destructive (deny — never allowed)
- `aws * delete*`, `aws * remove*`, `aws * destroy*`, `aws * terminate*`
- `aws * deregister*`, `aws * purge*`
- `aws s3 rm`, `aws s3 rb`
- `aws cloudformation delete-stack`

### Provisioning & Scaling (deny — never allowed)
- `aws ec2 run-instances`, `aws ec2 stop-instances`
- `aws autoscaling set-desired-capacity`, `aws autoscaling update-auto-scaling-group`
- `aws application-autoscaling *`
- `aws ecs update-service`

### IAM (deny — never allowed)
- `aws iam create*`, `aws iam delete*`, `aws iam put*`, `aws iam attach*`

## Verification Steps

Before running any AWS command:

1. **Check the profile** — is this a sandbox account? If unsure, run `aws sts get-caller-identity` first
2. **Check the region** — is this an expected region? If it looks unfamiliar, ask the user
3. **Check the action** — is this read-only? If it mutates state, do NOT run it
4. **When in doubt** — explain the command and let the human decide
31 changes: 31 additions & 0 deletions .claude/rules/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Security Rules

These rules are loaded automatically by Claude Code at session start. They are non-negotiable.

## Safety Rules (NON-NEGOTIABLE)

1. NEVER run commands that delete, destroy, or modify production resources
2. NEVER use AWS credentials for production accounts — only sandbox accounts
3. NEVER deploy code, infrastructure, or configuration changes — a human must do this
4. NEVER run `terraform apply`, `terraform destroy`, `kubectl apply`, `kubectl delete` against production
5. NEVER read or output secrets, API keys, tokens, or credentials from files or environment variables
6. NEVER run commands with `sudo`
7. NEVER run `rm -rf` on any path outside the current project directory
8. NEVER disable, bypass, or modify the guardrail hooks in `.claude/hooks/`
9. NEVER push to `main` or `master` branches directly

## Core Principles

- **The agent works FOR you** — it must never take irreversible actions without explicit human approval
- **Deny by default** — if a command is not explicitly allowed, it should require human confirmation
- **Credentials are off-limits** — never read, display, or transmit secrets, tokens, or credentials
- **Guardrails are mandatory** — the `.claude/hooks/` scripts must remain active and unmodified

## When In Doubt

If you are unsure whether a command is safe to run, **do not run it**. Instead:
1. Explain what command you would run and why
2. Describe the potential risks
3. Wait for the human to decide

It is always better to ask than to act when safety is uncertain.
Loading