Advanced Guardrails: - prompt-injection-detector.sh (PreToolUse) - output-validator.sh (PostToolUse heuristics) - claudemd-scanner.sh (SessionStart injection detection) - output-secrets-scanner.sh (PostToolUse secrets leak prevention) Observability & Monitoring: - session-logger.sh (JSONL activity logging) - session-stats.sh (cost tracking & analysis) - guide/observability.md (full documentation) LLM-as-a-Judge Evaluation: - output-evaluator.md agent (Haiku) - /validate-changes command - pre-commit-evaluator.sh (opt-in git hook) Google Agent Whitepaper Integration: - Context Triage Guide (Section 2.2.4) - CLAUDE.md Injection Warning (Section 3.1.3) - Agent Validation Checklist (Section 4.2.4) - MCP Security: Tool Shadowing & Confused Deputy (Section 8.6) - Session vs Memory patterns (Section 3.3.3) Stats: 10 new files, 8 modified, 5 new guide sections Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
102 lines
2.8 KiB
Bash
Executable file
102 lines
2.8 KiB
Bash
Executable file
#!/bin/bash
|
|
# Hook: PostToolUse - Log all Claude Code operations for monitoring
|
|
# Exit 0 = allow (always)
|
|
#
|
|
# This hook logs all tool operations to JSONL files for later analysis.
|
|
# Use session-stats.sh to analyze the logs.
|
|
#
|
|
# Logs are stored in: ~/.claude/logs/activity-YYYY-MM-DD.jsonl
|
|
#
|
|
# Environment variables:
|
|
# CLAUDE_LOG_DIR - Override log directory (default: ~/.claude/logs)
|
|
# CLAUDE_LOG_TOKENS - Enable token estimation (default: true)
|
|
# CLAUDE_SESSION_ID - Session identifier (auto-generated if not set)
|
|
#
|
|
# Place in: .claude/hooks/session-logger.sh
|
|
# Register in: .claude/settings.json under PostToolUse event
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
LOG_DIR="${CLAUDE_LOG_DIR:-$HOME/.claude/logs}"
|
|
ENABLE_TOKENS="${CLAUDE_LOG_TOKENS:-true}"
|
|
SESSION_ID="${CLAUDE_SESSION_ID:-$(date +%s)-$$}"
|
|
|
|
# Ensure log directory exists
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
# Log file for today
|
|
LOG_FILE="$LOG_DIR/activity-$(date +%Y-%m-%d).jsonl"
|
|
|
|
# Read JSON from stdin
|
|
INPUT=$(cat)
|
|
|
|
# Extract tool information
|
|
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
|
|
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // ""')
|
|
|
|
# Get timestamp
|
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
# Extract relevant details based on tool type
|
|
FILE_PATH=""
|
|
COMMAND=""
|
|
|
|
case "$TOOL_NAME" in
|
|
Read|Write|Edit)
|
|
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // .path // ""')
|
|
;;
|
|
Bash)
|
|
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // ""' | head -c 200)
|
|
;;
|
|
Grep|Glob)
|
|
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.path // .pattern // ""')
|
|
;;
|
|
esac
|
|
|
|
# Estimate tokens (rough heuristic: ~4 chars per token)
|
|
TOKENS_INPUT=0
|
|
TOKENS_OUTPUT=0
|
|
|
|
if [[ "$ENABLE_TOKENS" == "true" ]]; then
|
|
INPUT_LEN=${#TOOL_INPUT}
|
|
OUTPUT_LEN=${#TOOL_OUTPUT}
|
|
TOKENS_INPUT=$((INPUT_LEN / 4))
|
|
TOKENS_OUTPUT=$((OUTPUT_LEN / 4))
|
|
fi
|
|
|
|
# Get project directory (if available)
|
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
PROJECT_NAME=$(basename "$PROJECT_DIR")
|
|
|
|
# Build log entry
|
|
LOG_ENTRY=$(jq -n \
|
|
--arg timestamp "$TIMESTAMP" \
|
|
--arg session_id "$SESSION_ID" \
|
|
--arg tool "$TOOL_NAME" \
|
|
--arg file "$FILE_PATH" \
|
|
--arg command "$COMMAND" \
|
|
--arg project "$PROJECT_NAME" \
|
|
--argjson tokens_in "$TOKENS_INPUT" \
|
|
--argjson tokens_out "$TOKENS_OUTPUT" \
|
|
'{
|
|
timestamp: $timestamp,
|
|
session_id: $session_id,
|
|
tool: $tool,
|
|
file: (if $file != "" then $file else null end),
|
|
command: (if $command != "" then $command else null end),
|
|
project: $project,
|
|
tokens: {
|
|
input: $tokens_in,
|
|
output: $tokens_out,
|
|
total: ($tokens_in + $tokens_out)
|
|
}
|
|
} | with_entries(select(.value != null))'
|
|
)
|
|
|
|
# Append to log file
|
|
echo "$LOG_ENTRY" >> "$LOG_FILE"
|
|
|
|
# Always allow
|
|
exit 0
|