feat(docs): add LLM Handbook + Google Whitepaper integration v3.3.0
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>
This commit is contained in:
parent
19110eba22
commit
8a4d116e2e
17 changed files with 2188 additions and 3 deletions
|
|
@ -46,6 +46,7 @@ Ready-to-use templates for Claude Code configuration.
|
|||
| [test-writer.md](./agents/test-writer.md) | TDD/BDD test generation | Sonnet |
|
||||
| [security-auditor.md](./agents/security-auditor.md) | Security vulnerability detection | Sonnet |
|
||||
| [refactoring-specialist.md](./agents/refactoring-specialist.md) | Clean code refactoring | Sonnet |
|
||||
| [output-evaluator.md](./agents/output-evaluator.md) | LLM-as-a-Judge quality gate | Haiku |
|
||||
|
||||
### Skills
|
||||
| File | Purpose |
|
||||
|
|
@ -64,6 +65,7 @@ Ready-to-use templates for Claude Code configuration.
|
|||
| [generate-tests.md](./commands/generate-tests.md) | `/generate-tests` | Test generation |
|
||||
| [git-worktree.md](./commands/git-worktree.md) | `/git-worktree` | Isolated git worktree setup |
|
||||
| [diagnose.md](./commands/diagnose.md) | `/diagnose` | Interactive troubleshooting assistant (FR/EN) |
|
||||
| [validate-changes.md](./commands/validate-changes.md) | `/validate-changes` | LLM-as-a-Judge pre-commit validation |
|
||||
|
||||
### Hooks
|
||||
| File | Event | Purpose |
|
||||
|
|
@ -72,6 +74,10 @@ Ready-to-use templates for Claude Code configuration.
|
|||
| [security-check.*](./hooks/) | PreToolUse | Block secrets in commands |
|
||||
| [auto-format.*](./hooks/) | PostToolUse | Auto-format after edits |
|
||||
| [notification.sh](./hooks/bash/notification.sh) | Notification | Contextual macOS sound alerts |
|
||||
| [prompt-injection-detector.sh](./hooks/bash/prompt-injection-detector.sh) | PreToolUse | Detect prompt injection attempts |
|
||||
| [output-validator.sh](./hooks/bash/output-validator.sh) | PostToolUse | Heuristic output validation |
|
||||
| [session-logger.sh](./hooks/bash/session-logger.sh) | PostToolUse | Log operations for monitoring |
|
||||
| [pre-commit-evaluator.sh](./hooks/bash/pre-commit-evaluator.sh) | Git hook | LLM-as-a-Judge pre-commit |
|
||||
|
||||
> **See [hooks/README.md](./hooks/README.md) for complete documentation and examples**
|
||||
|
||||
|
|
@ -96,6 +102,7 @@ Ready-to-use templates for Claude Code configuration.
|
|||
| [check-claude.ps1](./scripts/check-claude.ps1) | Health check diagnostics (Windows) | Human |
|
||||
| [clean-reinstall-claude.sh](./scripts/clean-reinstall-claude.sh) | Clean reinstall procedure (macOS/Linux) | Human |
|
||||
| [clean-reinstall-claude.ps1](./scripts/clean-reinstall-claude.ps1) | Clean reinstall procedure (Windows) | Human |
|
||||
| [session-stats.sh](./scripts/session-stats.sh) | Analyze session logs & costs | JSON / Human |
|
||||
|
||||
> **Usage**: `./audit-scan.sh` for human output, `./audit-scan.sh --json` for JSON output
|
||||
|
||||
|
|
|
|||
143
examples/agents/output-evaluator.md
Normal file
143
examples/agents/output-evaluator.md
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
name: output-evaluator
|
||||
description: Evaluate Claude Code outputs for quality before commit/action (LLM-as-a-Judge pattern)
|
||||
model: haiku
|
||||
tools: Read, Grep, Glob
|
||||
---
|
||||
|
||||
# Output Evaluator Agent
|
||||
|
||||
You evaluate code changes proposed by Claude for quality, correctness, and safety before they are committed or applied.
|
||||
|
||||
## Purpose
|
||||
|
||||
This agent implements the **LLM-as-a-Judge** pattern: using a language model to evaluate outputs from another LLM (or the same model in a different context). This provides an automated quality gate before irreversible actions like commits.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Before committing staged changes
|
||||
- After significant code generation
|
||||
- Before applying bulk edits
|
||||
- When reviewing unfamiliar code modifications
|
||||
|
||||
## Evaluation Criteria
|
||||
|
||||
Score each criterion from 0-10:
|
||||
|
||||
### Correctness (0-10)
|
||||
|
||||
- [ ] Code compiles/parses without errors
|
||||
- [ ] Logic is sound and handles expected cases
|
||||
- [ ] No obvious bugs or regressions introduced
|
||||
- [ ] Type safety maintained (if applicable)
|
||||
- [ ] No undefined variables or missing imports
|
||||
|
||||
### Completeness (0-10)
|
||||
|
||||
- [ ] All TODOs are resolved (not left as placeholders)
|
||||
- [ ] Error handling is present where needed
|
||||
- [ ] Edge cases are considered
|
||||
- [ ] No stub implementations or mock data
|
||||
- [ ] Tests included if appropriate for the change
|
||||
|
||||
### Safety (0-10)
|
||||
|
||||
- [ ] No hardcoded secrets or credentials
|
||||
- [ ] No destructive operations without safeguards
|
||||
- [ ] No SQL injection, XSS, or command injection vectors
|
||||
- [ ] No overly permissive file/network access
|
||||
- [ ] Sensitive data not logged or exposed
|
||||
|
||||
## Evaluation Process
|
||||
|
||||
1. **Read the changes**: Examine all modified files
|
||||
2. **Check context**: Understand what the changes are trying to accomplish
|
||||
3. **Score each criterion**: Apply the checklist above
|
||||
4. **Identify issues**: List specific problems found
|
||||
5. **Render verdict**: Based on scores and severity
|
||||
|
||||
## Output Format
|
||||
|
||||
Always respond with this JSON structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"verdict": "APPROVE|NEEDS_REVIEW|REJECT",
|
||||
"scores": {
|
||||
"correctness": 8,
|
||||
"completeness": 7,
|
||||
"safety": 9
|
||||
},
|
||||
"overall_score": 8.0,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "high|medium|low",
|
||||
"file": "path/to/file.ts",
|
||||
"line": 42,
|
||||
"description": "Description of the issue"
|
||||
}
|
||||
],
|
||||
"summary": "Brief 1-2 sentence assessment",
|
||||
"suggestion": "What to do next (if not APPROVE)"
|
||||
}
|
||||
```
|
||||
|
||||
## Verdict Rules
|
||||
|
||||
| Verdict | Condition |
|
||||
|---------|-----------|
|
||||
| **APPROVE** | All scores >= 7, no high-severity issues |
|
||||
| **NEEDS_REVIEW** | Any score 5-6, or medium-severity issues present |
|
||||
| **REJECT** | Any score < 5, or any high-severity security issue |
|
||||
|
||||
## Issue Severity Guide
|
||||
|
||||
- **High**: Security vulnerabilities, data loss risk, breaking changes, secrets exposure
|
||||
- **Medium**: Missing error handling, incomplete implementation, poor patterns
|
||||
- **Low**: Style issues, naming, minor optimizations, documentation gaps
|
||||
|
||||
## Example Evaluation
|
||||
|
||||
Given a diff that adds a new API endpoint:
|
||||
|
||||
```json
|
||||
{
|
||||
"verdict": "NEEDS_REVIEW",
|
||||
"scores": {
|
||||
"correctness": 8,
|
||||
"completeness": 6,
|
||||
"safety": 7
|
||||
},
|
||||
"overall_score": 7.0,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "medium",
|
||||
"file": "src/api/users.ts",
|
||||
"line": 45,
|
||||
"description": "Missing error handling for database connection failures"
|
||||
},
|
||||
{
|
||||
"severity": "low",
|
||||
"file": "src/api/users.ts",
|
||||
"line": 52,
|
||||
"description": "Consider adding rate limiting for this endpoint"
|
||||
}
|
||||
],
|
||||
"summary": "Endpoint implementation is correct but lacks error handling for edge cases.",
|
||||
"suggestion": "Add try-catch around database operations and handle connection errors gracefully."
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Not a replacement for human review**: This is a first-pass automated check
|
||||
- **No runtime testing**: Evaluation is static analysis only
|
||||
- **Model limitations**: May miss subtle bugs or domain-specific issues
|
||||
- **Cost**: Each evaluation uses API tokens (~$0.01-0.05 with Haiku)
|
||||
|
||||
## Integration
|
||||
|
||||
Use with:
|
||||
- `/validate-changes` command - Invoke before commits
|
||||
- `pre-commit-evaluator.sh` hook - Automatic git integration
|
||||
- Manual invocation for significant changes
|
||||
115
examples/commands/validate-changes.md
Normal file
115
examples/commands/validate-changes.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
name: validate-changes
|
||||
description: Evaluate staged changes using LLM-as-a-Judge before committing
|
||||
allowed-tools: Bash, Read, Grep, Glob, Task
|
||||
---
|
||||
|
||||
# Validate Changes Before Commit
|
||||
|
||||
Evaluate staged git changes using the output-evaluator agent to catch issues before committing.
|
||||
|
||||
## Process
|
||||
|
||||
### Step 1: Check for Staged Changes
|
||||
|
||||
Run `git diff --cached --stat` to see what's staged. If nothing is staged, inform the user and exit.
|
||||
|
||||
### Step 2: Get the Full Diff
|
||||
|
||||
Run `git diff --cached` to get the complete diff of all staged changes.
|
||||
|
||||
### Step 3: Invoke the Evaluator
|
||||
|
||||
Use the Task tool to launch the `output-evaluator` agent with the diff:
|
||||
|
||||
```
|
||||
Evaluate these staged changes for correctness, completeness, and safety.
|
||||
Return a JSON verdict with scores and issues.
|
||||
|
||||
Changes:
|
||||
[paste the git diff here]
|
||||
```
|
||||
|
||||
### Step 4: Parse and Act on Verdict
|
||||
|
||||
Based on the evaluation result:
|
||||
|
||||
**If APPROVE:**
|
||||
- Tell the user the changes passed evaluation
|
||||
- Show the summary and scores
|
||||
- Ask if they want to proceed with commit
|
||||
|
||||
**If NEEDS_REVIEW:**
|
||||
- Show all issues found (grouped by severity)
|
||||
- Show the suggestion from the evaluator
|
||||
- Ask the user how to proceed:
|
||||
- Fix issues and re-evaluate
|
||||
- Commit anyway (acknowledge risks)
|
||||
- Abort
|
||||
|
||||
**If REJECT:**
|
||||
- Clearly state the changes were rejected
|
||||
- Show critical issues that caused rejection
|
||||
- Do NOT offer to commit anyway
|
||||
- Suggest specific fixes
|
||||
|
||||
### Step 5: Commit (if approved)
|
||||
|
||||
If user confirms, create the commit using the standard commit flow.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```
|
||||
/validate-changes
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Evaluating 3 staged files...
|
||||
|
||||
VERDICT: NEEDS_REVIEW
|
||||
|
||||
Scores:
|
||||
Correctness: 8/10
|
||||
Completeness: 6/10
|
||||
Safety: 9/10
|
||||
|
||||
Issues Found:
|
||||
[MEDIUM] src/api/handler.ts:45
|
||||
Missing error handling for network failures
|
||||
|
||||
[LOW] src/utils/format.ts:12
|
||||
Consider adding input validation
|
||||
|
||||
Suggestion: Add try-catch around the fetch call in handler.ts
|
||||
|
||||
How would you like to proceed?
|
||||
1. Fix issues and re-evaluate
|
||||
2. Commit anyway (1 medium issue)
|
||||
3. Abort
|
||||
```
|
||||
|
||||
## Cost Awareness
|
||||
|
||||
This command invokes an LLM evaluation, which uses API tokens:
|
||||
- **Typical cost**: $0.01-0.05 per evaluation (using Haiku)
|
||||
- **Larger diffs**: May cost more due to increased token usage
|
||||
|
||||
## When to Use
|
||||
|
||||
- After significant code changes before committing
|
||||
- When working on unfamiliar parts of the codebase
|
||||
- For changes that affect security-sensitive code
|
||||
- Before pushing to shared branches
|
||||
|
||||
## When to Skip
|
||||
|
||||
- Trivial changes (typos, formatting)
|
||||
- Documentation-only changes
|
||||
- When you've already manually reviewed thoroughly
|
||||
- When iterating quickly on a feature branch
|
||||
|
||||
## Integration with Git Hooks
|
||||
|
||||
For automatic evaluation on every commit, see `pre-commit-evaluator.sh` hook.
|
||||
This command is the manual alternative when you want control over when evaluation runs.
|
||||
|
|
@ -8,6 +8,8 @@ Hooks are scripts that execute automatically on Claude Code events. They enable
|
|||
|------|-------|---------|----------|
|
||||
| [dangerous-actions-blocker.sh](./bash/dangerous-actions-blocker.sh) | PreToolUse | Block dangerous commands/edits | Bash |
|
||||
| [security-check.sh](./bash/security-check.sh) | PreToolUse | Block secrets in commands | Bash |
|
||||
| [claudemd-scanner.sh](./bash/claudemd-scanner.sh) | SessionStart | Detect CLAUDE.md injection attacks | Bash |
|
||||
| [output-secrets-scanner.sh](./bash/output-secrets-scanner.sh) | PostToolUse | Detect secrets in tool outputs | Bash |
|
||||
| [auto-format.sh](./bash/auto-format.sh) | PostToolUse | Auto-format after edits | Bash |
|
||||
| [notification.sh](./bash/notification.sh) | Notification | Contextual macOS sound alerts | Bash (macOS) |
|
||||
| [security-check.ps1](./powershell/security-check.ps1) | PreToolUse | Block secrets in commands | PowerShell |
|
||||
|
|
@ -25,6 +27,99 @@ Hooks are scripts that execute automatically on Claude Code events. They enable
|
|||
| `SessionEnd` | At session end | Cleanup, session summary |
|
||||
| `Stop` | User interrupts operation | State saving, graceful shutdown |
|
||||
|
||||
## Advanced Guardrails (NEW in v3.3.0)
|
||||
|
||||
Advanced protection patterns inspired by production LLM systems.
|
||||
|
||||
### prompt-injection-detector.sh
|
||||
|
||||
**Event**: `PreToolUse`
|
||||
|
||||
Detects and blocks prompt injection attempts before they reach Claude:
|
||||
|
||||
**Detected Patterns**:
|
||||
- Role override: "ignore previous instructions", "you are now", "pretend to be"
|
||||
- Jailbreak attempts: "DAN mode", "developer mode", "no restrictions"
|
||||
- Delimiter injection: `</system>`, `[INST]`, `<<SYS>>`
|
||||
- Authority impersonation: "anthropic employee", "authorized to bypass"
|
||||
- Base64-encoded payloads (decoded and scanned)
|
||||
- Context manipulation: false claims about previous messages
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [{
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/prompt-injection-detector.sh",
|
||||
"timeout": 5000
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### output-validator.sh
|
||||
|
||||
**Event**: `PostToolUse`
|
||||
|
||||
Heuristic validation of Claude's outputs (no LLM call, pure bash):
|
||||
|
||||
**Validation Checks**:
|
||||
- Placeholder paths: `/path/to/`, `/your/project/`
|
||||
- Placeholder content: `TODO:`, `your-api-key`, `example.com`
|
||||
- Potential secrets in output (regex patterns)
|
||||
- Uncertainty indicators (multiple "I'm not sure", "probably")
|
||||
- Incomplete implementations: `NotImplementedError`, `throw new Error`
|
||||
- Unverified reference claims
|
||||
|
||||
**Behavior**: Warns via `systemMessage`, does not block. For deeper validation, use the `output-evaluator` agent.
|
||||
|
||||
### session-logger.sh
|
||||
|
||||
**Event**: `PostToolUse`
|
||||
|
||||
Logs all Claude operations to JSONL files for monitoring and cost tracking:
|
||||
|
||||
**Log Location**: `~/.claude/logs/activity-YYYY-MM-DD.jsonl`
|
||||
|
||||
**Logged Data**:
|
||||
- Timestamp, session ID, tool name
|
||||
- File paths and commands (truncated)
|
||||
- Project name
|
||||
- Token estimates (input/output)
|
||||
|
||||
**Analysis**: Use `session-stats.sh` script to analyze logs.
|
||||
|
||||
**Environment Variables**:
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CLAUDE_LOG_DIR` | `~/.claude/logs` | Log directory |
|
||||
| `CLAUDE_LOG_TOKENS` | `true` | Enable token estimation |
|
||||
| `CLAUDE_SESSION_ID` | auto | Custom session ID |
|
||||
|
||||
See [Observability Guide](../../guide/observability.md) for full documentation.
|
||||
|
||||
### pre-commit-evaluator.sh
|
||||
|
||||
**Type**: Git pre-commit hook (not Claude hook)
|
||||
|
||||
LLM-as-a-Judge evaluation before every commit. **Opt-in only** due to API costs.
|
||||
|
||||
**Installation**:
|
||||
```bash
|
||||
cp pre-commit-evaluator.sh .git/hooks/pre-commit
|
||||
chmod +x .git/hooks/pre-commit
|
||||
export CLAUDE_PRECOMMIT_EVAL=1 # Enable evaluation
|
||||
```
|
||||
|
||||
**Cost**: ~$0.01-0.05 per commit (Haiku model)
|
||||
|
||||
**Bypass**: `git commit --no-verify` or `CLAUDE_SKIP_EVAL=1 git commit`
|
||||
|
||||
---
|
||||
|
||||
## Security Hooks
|
||||
|
||||
### dangerous-actions-blocker.sh
|
||||
|
|
@ -77,6 +172,72 @@ Focused on detecting secrets in commands:
|
|||
- Private keys
|
||||
- Hardcoded tokens
|
||||
|
||||
### claudemd-scanner.sh
|
||||
|
||||
**Event**: `SessionStart`
|
||||
|
||||
Scans CLAUDE.md files at session start for potential prompt injection attacks:
|
||||
|
||||
**Detected Patterns**:
|
||||
- "ignore previous instructions" variants
|
||||
- Shell injection: `curl | bash`, `wget | sh`, `eval(`
|
||||
- Base64 encoded content (potential obfuscation)
|
||||
- Hidden instructions in HTML comments
|
||||
- Suspicious long lines (>500 chars)
|
||||
- Non-ASCII characters near sensitive keywords (homoglyph attacks)
|
||||
|
||||
**Files Scanned**:
|
||||
- `CLAUDE.md` (project root)
|
||||
- `.claude/CLAUDE.md` (local override)
|
||||
- Any `.md` files in `.claude/` directory
|
||||
|
||||
**Why This Matters**: When you clone an unfamiliar repository, a malicious CLAUDE.md could inject instructions that compromise your system. This hook warns you before Claude processes potentially dangerous instructions.
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [{
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/claudemd-scanner.sh",
|
||||
"timeout": 5000
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### output-secrets-scanner.sh
|
||||
|
||||
**Event**: `PostToolUse`
|
||||
|
||||
Complements `security-check.sh` by scanning tool **outputs** (not inputs) for leaked secrets.
|
||||
|
||||
**Detected Patterns**:
|
||||
- API Keys: OpenAI, Anthropic, AWS, GCP, Azure, Stripe, Twilio, SendGrid
|
||||
- Tokens: GitHub, GitLab, NPM, PyPI, JWT
|
||||
- Private Keys: RSA, EC, DSA, OpenSSH, PGP
|
||||
- Database URLs with embedded passwords
|
||||
- Generic `api_key=`, `secret=`, `password=` patterns
|
||||
|
||||
**Why This Matters**: Claude might read a `.env` file and include credentials in its response or a commit. This hook catches secrets before they leak.
|
||||
|
||||
**Configuration**:
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [{
|
||||
"hooks": [{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/output-secrets-scanner.sh",
|
||||
"timeout": 5000
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Productivity Hooks
|
||||
|
||||
### auto-format.sh / auto-format.ps1
|
||||
|
|
|
|||
98
examples/hooks/bash/claudemd-scanner.sh
Normal file
98
examples/hooks/bash/claudemd-scanner.sh
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# CLAUDE.md Injection Scanner Hook
|
||||
# =============================================================================
|
||||
# Event: SessionStart (runs when Claude Code session begins)
|
||||
# Purpose: Detect potential prompt injection attacks in CLAUDE.md files
|
||||
#
|
||||
# Installation:
|
||||
# Add to .claude/settings.json:
|
||||
# {
|
||||
# "hooks": {
|
||||
# "SessionStart": [{
|
||||
# "matcher": "",
|
||||
# "hooks": ["bash examples/hooks/bash/claudemd-scanner.sh"]
|
||||
# }]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# What it detects:
|
||||
# - "ignore previous instructions" patterns (common injection technique)
|
||||
# - Shell command execution attempts (curl|bash, wget|sh, eval)
|
||||
# - Base64 encoded content (potential obfuscation)
|
||||
# - Suspicious HTML comments that might hide instructions
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Define suspicious patterns (case-insensitive)
|
||||
SUSPICIOUS_PATTERNS=(
|
||||
"ignore.*previous.*instruction"
|
||||
"ignore.*all.*instruction"
|
||||
"disregard.*instruction"
|
||||
"forget.*instruction"
|
||||
"new.*instruction.*follow"
|
||||
"curl.*\|.*bash"
|
||||
"curl.*\|.*sh"
|
||||
"wget.*\|.*bash"
|
||||
"wget.*\|.*sh"
|
||||
"eval\s*\("
|
||||
"base64.*decode"
|
||||
"\$\(.*curl"
|
||||
"\$\(.*wget"
|
||||
"<!--.*ignore"
|
||||
"<!--.*instruction"
|
||||
)
|
||||
|
||||
WARNINGS=()
|
||||
|
||||
# Function to scan a file for suspicious patterns
|
||||
scan_file() {
|
||||
local file="$1"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
for pattern in "${SUSPICIOUS_PATTERNS[@]}"; do
|
||||
if grep -qiE "$pattern" "$file" 2>/dev/null; then
|
||||
WARNINGS+=("Suspicious pattern in $file: matches '$pattern'")
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for very long single lines (potential obfuscation)
|
||||
if awk 'length > 500' "$file" | grep -q .; then
|
||||
WARNINGS+=("Warning: $file contains very long lines (potential obfuscation)")
|
||||
fi
|
||||
|
||||
# Check for uncommon Unicode characters (potential homoglyph attack)
|
||||
if grep -P '[^\x00-\x7F]' "$file" 2>/dev/null | grep -qiE "instruction|ignore|run|execute"; then
|
||||
WARNINGS+=("Warning: $file contains non-ASCII characters near sensitive keywords")
|
||||
fi
|
||||
}
|
||||
|
||||
# Scan all potential CLAUDE.md locations
|
||||
scan_file "CLAUDE.md"
|
||||
scan_file ".claude/CLAUDE.md"
|
||||
|
||||
# Also scan any .md files in .claude/ directory that might be loaded
|
||||
if [[ -d ".claude" ]]; then
|
||||
for md_file in .claude/*.md; do
|
||||
[[ -f "$md_file" ]] && scan_file "$md_file"
|
||||
done
|
||||
fi
|
||||
|
||||
# Output warnings if any found
|
||||
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||
# Construct JSON response with system message
|
||||
WARNING_TEXT="SECURITY WARNING - Suspicious content detected:\\n"
|
||||
for warning in "${WARNINGS[@]}"; do
|
||||
WARNING_TEXT+="- $warning\\n"
|
||||
done
|
||||
WARNING_TEXT+="\\nReview these files before proceeding. See: https://github.com/FlorianBruniaux/claude-code-ultimate-guide/guide/ultimate-guide.md#security-warning-claudemd-injection"
|
||||
|
||||
echo "{\"systemMessage\": \"$WARNING_TEXT\"}"
|
||||
fi
|
||||
|
||||
# Always exit 0 to not block session (just warn)
|
||||
exit 0
|
||||
97
examples/hooks/bash/output-secrets-scanner.sh
Normal file
97
examples/hooks/bash/output-secrets-scanner.sh
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Output Secrets Scanner Hook
|
||||
# =============================================================================
|
||||
# Event: PostToolUse (runs after each tool execution)
|
||||
# Purpose: Detect secrets that might leak in tool outputs
|
||||
#
|
||||
# This complements security-check.sh (which scans inputs). This hook scans
|
||||
# outputs to catch secrets that Claude might inadvertently expose.
|
||||
#
|
||||
# Installation:
|
||||
# Add to .claude/settings.json:
|
||||
# {
|
||||
# "hooks": {
|
||||
# "PostToolUse": [{
|
||||
# "matcher": "",
|
||||
# "hooks": ["bash examples/hooks/bash/output-secrets-scanner.sh"]
|
||||
# }]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# What it detects:
|
||||
# - API keys (OpenAI, Anthropic, AWS, GCP, Azure, Stripe, etc.)
|
||||
# - Private keys and certificates
|
||||
# - Database connection strings with passwords
|
||||
# - GitHub/GitLab tokens
|
||||
# - JWT tokens
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Read the hook input from stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
# Extract tool output from JSON (handle both formats)
|
||||
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // .output // ""' 2>/dev/null || echo "")
|
||||
|
||||
# If no output or empty, exit cleanly
|
||||
if [[ -z "$TOOL_OUTPUT" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Secret patterns to detect
|
||||
declare -A SECRET_PATTERNS=(
|
||||
# API Keys
|
||||
["OpenAI API Key"]="sk-[a-zA-Z0-9]{20,}"
|
||||
["Anthropic API Key"]="sk-ant-[a-zA-Z0-9]{20,}"
|
||||
["AWS Access Key"]="AKIA[0-9A-Z]{16}"
|
||||
["AWS Secret Key"]="[0-9a-zA-Z/+]{40}"
|
||||
["GCP API Key"]="AIza[0-9A-Za-z_-]{35}"
|
||||
["Azure Key"]="[a-zA-Z0-9]{32,}"
|
||||
["Stripe Key"]="(sk|pk)_(live|test)_[0-9a-zA-Z]{24,}"
|
||||
["Twilio Key"]="SK[a-f0-9]{32}"
|
||||
["SendGrid Key"]="SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}"
|
||||
|
||||
# Tokens
|
||||
["GitHub Token"]="(ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}"
|
||||
["GitLab Token"]="glpat-[a-zA-Z0-9_-]{20,}"
|
||||
["NPM Token"]="npm_[a-zA-Z0-9]{36}"
|
||||
["PyPI Token"]="pypi-[a-zA-Z0-9_-]{50,}"
|
||||
["JWT Token"]="eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*"
|
||||
|
||||
# Private Keys
|
||||
["Private Key"]="-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
|
||||
["PGP Private Key"]="-----BEGIN PGP PRIVATE KEY BLOCK-----"
|
||||
|
||||
# Database
|
||||
["Database URL with Password"]="(postgres|mysql|mongodb)://[^:]+:[^@]+@"
|
||||
["Redis URL with Password"]="redis://:[^@]+@"
|
||||
|
||||
# Generic
|
||||
["Generic API Key"]="(api[_-]?key|apikey|api[_-]?secret)['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_-]{20,}"
|
||||
["Generic Secret"]="(secret|password|passwd|pwd)['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}"
|
||||
)
|
||||
|
||||
DETECTED_SECRETS=()
|
||||
|
||||
# Check each pattern
|
||||
for secret_type in "${!SECRET_PATTERNS[@]}"; do
|
||||
pattern="${SECRET_PATTERNS[$secret_type]}"
|
||||
if echo "$TOOL_OUTPUT" | grep -qiE "$pattern" 2>/dev/null; then
|
||||
DETECTED_SECRETS+=("$secret_type")
|
||||
fi
|
||||
done
|
||||
|
||||
# If secrets detected, warn via systemMessage
|
||||
if [[ ${#DETECTED_SECRETS[@]} -gt 0 ]]; then
|
||||
SECRETS_LIST=$(printf ", %s" "${DETECTED_SECRETS[@]}")
|
||||
SECRETS_LIST=${SECRETS_LIST:2} # Remove leading ", "
|
||||
|
||||
WARNING_MSG="SECRET LEAK WARNING: Potential secrets detected in output: $SECRETS_LIST. Do NOT commit or share this output. Consider using environment variables or a secrets manager."
|
||||
|
||||
echo "{\"systemMessage\": \"$WARNING_MSG\"}"
|
||||
fi
|
||||
|
||||
# Always exit 0 (warn, don't block)
|
||||
exit 0
|
||||
184
examples/hooks/bash/output-validator.sh
Executable file
184
examples/hooks/bash/output-validator.sh
Executable file
|
|
@ -0,0 +1,184 @@
|
|||
#!/bin/bash
|
||||
# Hook: PostToolUse - Validate Claude's outputs for quality issues
|
||||
# Exit 0 = allow (always), but emit systemMessage warnings
|
||||
#
|
||||
# This hook performs heuristic validation of Claude's outputs to detect:
|
||||
# - Potential hallucinations (fabricated paths, functions)
|
||||
# - Sensitive data leakage in outputs
|
||||
# - High uncertainty indicators
|
||||
#
|
||||
# This is a lightweight heuristic check, not a full LLM evaluation.
|
||||
# For deeper validation, use the output-evaluator agent.
|
||||
#
|
||||
# Place in: .claude/hooks/output-validator.sh
|
||||
# Register in: .claude/settings.json under PostToolUse event
|
||||
|
||||
set -e
|
||||
|
||||
# Read JSON from stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // empty')
|
||||
|
||||
# Only validate tools that produce code/content outputs
|
||||
case "$TOOL_NAME" in
|
||||
Edit|Write|Bash)
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
WARNINGS=()
|
||||
|
||||
# === FABRICATED FILE PATHS ===
|
||||
# Detect paths that look suspicious (common hallucination patterns)
|
||||
SUSPICIOUS_PATHS=(
|
||||
"/path/to/"
|
||||
"/your/project/"
|
||||
"/example/"
|
||||
"/foo/bar/"
|
||||
"/my/app/"
|
||||
"/user/project/"
|
||||
"C:\\Users\\User\\"
|
||||
"C:\\path\\to\\"
|
||||
)
|
||||
|
||||
for pattern in "${SUSPICIOUS_PATHS[@]}"; do
|
||||
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
|
||||
WARNINGS+=("Suspicious placeholder path detected: '$pattern'")
|
||||
fi
|
||||
done
|
||||
|
||||
# === PLACEHOLDER CONTENT ===
|
||||
# Detect common placeholder patterns that shouldn't be in production code
|
||||
PLACEHOLDER_PATTERNS=(
|
||||
"TODO:"
|
||||
"FIXME:"
|
||||
"XXX:"
|
||||
"HACK:"
|
||||
"your-api-key"
|
||||
"your_api_key"
|
||||
"YOUR_API_KEY"
|
||||
"sk-..."
|
||||
"pk_test_"
|
||||
"pk_live_"
|
||||
"api_key_here"
|
||||
"replace_with"
|
||||
"insert_your"
|
||||
"placeholder"
|
||||
"example.com"
|
||||
"foo@bar.com"
|
||||
"test@test.com"
|
||||
)
|
||||
|
||||
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
|
||||
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
|
||||
WARNINGS+=("Placeholder content detected: '$pattern'")
|
||||
fi
|
||||
done
|
||||
|
||||
# === SENSITIVE DATA LEAKAGE ===
|
||||
# Detect potential secrets in output (could indicate data exposure)
|
||||
SECRET_PATTERNS=(
|
||||
# AWS
|
||||
'AKIA[0-9A-Z]{16}'
|
||||
# Generic API keys (long hex strings)
|
||||
'[a-f0-9]{32,}'
|
||||
# Private keys
|
||||
'-----BEGIN.*PRIVATE KEY-----'
|
||||
'-----BEGIN RSA'
|
||||
'-----BEGIN EC'
|
||||
# JWT tokens
|
||||
'eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.'
|
||||
# Password patterns
|
||||
'password["\x27]?\s*[=:]\s*["\x27][^"\x27]{8,}'
|
||||
)
|
||||
|
||||
for pattern in "${SECRET_PATTERNS[@]}"; do
|
||||
if echo "$TOOL_OUTPUT" | grep -qE "$pattern" 2>/dev/null; then
|
||||
WARNINGS+=("Potential sensitive data in output (pattern: ${pattern:0:20}...)")
|
||||
fi
|
||||
done
|
||||
|
||||
# === UNCERTAINTY INDICATORS ===
|
||||
# Detect high uncertainty language that might indicate guessing
|
||||
UNCERTAINTY_PATTERNS=(
|
||||
"I'm not sure"
|
||||
"I think it might"
|
||||
"probably"
|
||||
"possibly"
|
||||
"might be"
|
||||
"could be"
|
||||
"I believe"
|
||||
"I assume"
|
||||
"I guess"
|
||||
"if I recall"
|
||||
"from memory"
|
||||
"I don't have access"
|
||||
"I cannot verify"
|
||||
)
|
||||
|
||||
UNCERTAINTY_COUNT=0
|
||||
TOOL_OUTPUT_LOWER=$(echo "$TOOL_OUTPUT" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
for pattern in "${UNCERTAINTY_PATTERNS[@]}"; do
|
||||
pattern_lower=$(echo "$pattern" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "$TOOL_OUTPUT_LOWER" == *"$pattern_lower"* ]]; then
|
||||
((UNCERTAINTY_COUNT++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $UNCERTAINTY_COUNT -ge 3 ]]; then
|
||||
WARNINGS+=("High uncertainty detected ($UNCERTAINTY_COUNT indicators) - verify output accuracy")
|
||||
fi
|
||||
|
||||
# === INCOMPLETE IMPLEMENTATIONS ===
|
||||
# Detect code that looks incomplete
|
||||
INCOMPLETE_PATTERNS=(
|
||||
"not implemented"
|
||||
"NotImplementedError"
|
||||
"throw new Error.*implement"
|
||||
"// TODO"
|
||||
"# TODO"
|
||||
"pass # "
|
||||
"raise NotImplemented"
|
||||
"undefined"
|
||||
)
|
||||
|
||||
for pattern in "${INCOMPLETE_PATTERNS[@]}"; do
|
||||
if echo "$TOOL_OUTPUT" | grep -qiE "$pattern" 2>/dev/null; then
|
||||
WARNINGS+=("Incomplete implementation detected: '$pattern'")
|
||||
fi
|
||||
done
|
||||
|
||||
# === HALLUCINATION INDICATORS ===
|
||||
# Detect patterns that often indicate hallucinated content
|
||||
HALLUCINATION_PATTERNS=(
|
||||
"According to the documentation"
|
||||
"As stated in"
|
||||
"The official guide says"
|
||||
"Based on the API reference"
|
||||
)
|
||||
|
||||
for pattern in "${HALLUCINATION_PATTERNS[@]}"; do
|
||||
if [[ "$TOOL_OUTPUT" == *"$pattern"* ]]; then
|
||||
WARNINGS+=("Unverified reference claim: '$pattern' - verify source")
|
||||
fi
|
||||
done
|
||||
|
||||
# === OUTPUT WARNINGS ===
|
||||
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||
WARNING_MSG="Output validation warnings:\\n"
|
||||
for warn in "${WARNINGS[@]}"; do
|
||||
WARNING_MSG+=" - $warn\\n"
|
||||
done
|
||||
WARNING_MSG+="\\nReview output carefully before accepting."
|
||||
|
||||
# Emit as systemMessage (warning, not blocking)
|
||||
echo "{\"systemMessage\": \"$WARNING_MSG\"}"
|
||||
fi
|
||||
|
||||
# Always allow (this hook warns, doesn't block)
|
||||
exit 0
|
||||
207
examples/hooks/bash/pre-commit-evaluator.sh
Executable file
207
examples/hooks/bash/pre-commit-evaluator.sh
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
#!/bin/bash
|
||||
# Git pre-commit hook: LLM-as-a-Judge evaluation before commit
|
||||
#
|
||||
# This hook uses Claude to evaluate staged changes before allowing a commit.
|
||||
# It's an OPT-IN feature due to API costs and latency.
|
||||
#
|
||||
# COST WARNING: Each commit evaluation costs ~$0.01-0.05 (Haiku model)
|
||||
#
|
||||
# Installation:
|
||||
# 1. Copy to your repo: cp pre-commit-evaluator.sh .git/hooks/pre-commit
|
||||
# 2. Make executable: chmod +x .git/hooks/pre-commit
|
||||
# 3. Set required env var: export CLAUDE_PRECOMMIT_EVAL=1
|
||||
#
|
||||
# Environment Variables:
|
||||
# CLAUDE_PRECOMMIT_EVAL - Set to "1" to enable (default: disabled)
|
||||
# CLAUDE_EVAL_MODEL - Model to use (default: haiku)
|
||||
# CLAUDE_EVAL_THRESHOLD - Minimum score to pass (default: 7)
|
||||
# CLAUDE_EVAL_SKIP_PATHS - Colon-separated paths to skip (e.g., "docs:*.md")
|
||||
#
|
||||
# Bypass for single commit:
|
||||
# CLAUDE_SKIP_EVAL=1 git commit -m "message"
|
||||
# or
|
||||
# git commit --no-verify -m "message"
|
||||
|
||||
set -e
|
||||
|
||||
# Check if evaluation is enabled
|
||||
if [[ "${CLAUDE_PRECOMMIT_EVAL:-0}" != "1" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for bypass
|
||||
if [[ "${CLAUDE_SKIP_EVAL:-0}" == "1" ]]; then
|
||||
echo "Skipping LLM evaluation (CLAUDE_SKIP_EVAL=1)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
MODEL="${CLAUDE_EVAL_MODEL:-haiku}"
|
||||
THRESHOLD="${CLAUDE_EVAL_THRESHOLD:-7}"
|
||||
SKIP_PATHS="${CLAUDE_EVAL_SKIP_PATHS:-}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check for staged changes
|
||||
STAGED_FILES=$(git diff --cached --name-only)
|
||||
if [[ -z "$STAGED_FILES" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Filter out skipped paths
|
||||
if [[ -n "$SKIP_PATHS" ]]; then
|
||||
IFS=':' read -ra SKIP_ARRAY <<< "$SKIP_PATHS"
|
||||
FILTERED_FILES=""
|
||||
for file in $STAGED_FILES; do
|
||||
skip=false
|
||||
for pattern in "${SKIP_ARRAY[@]}"; do
|
||||
if [[ "$file" == $pattern ]]; then
|
||||
skip=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$skip" == "false" ]]; then
|
||||
FILTERED_FILES="$FILTERED_FILES $file"
|
||||
fi
|
||||
done
|
||||
STAGED_FILES=$(echo "$FILTERED_FILES" | xargs)
|
||||
fi
|
||||
|
||||
# Exit if all files were filtered
|
||||
if [[ -z "$STAGED_FILES" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Count files
|
||||
FILE_COUNT=$(echo "$STAGED_FILES" | wc -w | tr -d ' ')
|
||||
|
||||
echo -e "${CYAN}Evaluating $FILE_COUNT staged file(s) with Claude ($MODEL)...${NC}"
|
||||
echo -e "${YELLOW}Cost: ~\$0.01-0.05 per evaluation${NC}"
|
||||
echo ""
|
||||
|
||||
# Get the diff
|
||||
DIFF=$(git diff --cached)
|
||||
|
||||
# Truncate diff if too large (to control costs)
|
||||
MAX_CHARS=50000
|
||||
if [[ ${#DIFF} -gt $MAX_CHARS ]]; then
|
||||
echo -e "${YELLOW}Warning: Diff truncated to ${MAX_CHARS} chars for cost control${NC}"
|
||||
DIFF="${DIFF:0:$MAX_CHARS}
|
||||
|
||||
[TRUNCATED - diff exceeded ${MAX_CHARS} characters]"
|
||||
fi
|
||||
|
||||
# Prepare the prompt
|
||||
PROMPT="You are a code quality evaluator. Analyze this git diff and provide a JSON evaluation.
|
||||
|
||||
Score each criterion from 0-10:
|
||||
- correctness: Does the code work correctly?
|
||||
- completeness: Is the implementation complete (no TODOs, stubs)?
|
||||
- safety: No secrets, no security issues?
|
||||
|
||||
Respond ONLY with valid JSON in this format:
|
||||
{
|
||||
\"verdict\": \"APPROVE\" or \"NEEDS_REVIEW\" or \"REJECT\",
|
||||
\"scores\": {\"correctness\": N, \"completeness\": N, \"safety\": N},
|
||||
\"issues\": [{\"severity\": \"high/medium/low\", \"description\": \"...\"}],
|
||||
\"summary\": \"One sentence summary\"
|
||||
}
|
||||
|
||||
Rules:
|
||||
- APPROVE if all scores >= $THRESHOLD and no high-severity issues
|
||||
- NEEDS_REVIEW if any score is 5-$((THRESHOLD-1)) or medium issues exist
|
||||
- REJECT if any score < 5 or high-severity security issues
|
||||
|
||||
Git diff to evaluate:
|
||||
|
||||
$DIFF"
|
||||
|
||||
# Call Claude (requires claude CLI to be installed and authenticated)
|
||||
if ! command -v claude &> /dev/null; then
|
||||
echo -e "${RED}Error: 'claude' CLI not found. Install Claude Code first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run evaluation
|
||||
RESULT=$(echo "$PROMPT" | claude --model "$MODEL" --print 2>/dev/null) || {
|
||||
echo -e "${RED}Error: Claude evaluation failed${NC}"
|
||||
echo "You can bypass with: CLAUDE_SKIP_EVAL=1 git commit"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Extract JSON from response (handle potential markdown wrapping)
|
||||
JSON_RESULT=$(echo "$RESULT" | grep -o '{.*}' | head -1)
|
||||
|
||||
if [[ -z "$JSON_RESULT" ]]; then
|
||||
echo -e "${YELLOW}Warning: Could not parse evaluation result${NC}"
|
||||
echo "Raw response: $RESULT"
|
||||
echo ""
|
||||
echo "Proceeding with commit (evaluation inconclusive)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse result
|
||||
VERDICT=$(echo "$JSON_RESULT" | jq -r '.verdict // "UNKNOWN"')
|
||||
CORRECTNESS=$(echo "$JSON_RESULT" | jq -r '.scores.correctness // 0')
|
||||
COMPLETENESS=$(echo "$JSON_RESULT" | jq -r '.scores.completeness // 0')
|
||||
SAFETY=$(echo "$JSON_RESULT" | jq -r '.scores.safety // 0')
|
||||
SUMMARY=$(echo "$JSON_RESULT" | jq -r '.summary // "No summary"')
|
||||
ISSUES=$(echo "$JSON_RESULT" | jq -r '.issues // []')
|
||||
|
||||
# Display results
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Evaluation Results${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo " Correctness: $CORRECTNESS/10"
|
||||
echo " Completeness: $COMPLETENESS/10"
|
||||
echo " Safety: $SAFETY/10"
|
||||
echo ""
|
||||
echo " Summary: $SUMMARY"
|
||||
echo ""
|
||||
|
||||
# Show issues if any
|
||||
ISSUE_COUNT=$(echo "$ISSUES" | jq 'length')
|
||||
if [[ "$ISSUE_COUNT" -gt 0 ]]; then
|
||||
echo " Issues found:"
|
||||
echo "$ISSUES" | jq -r '.[] | " [\(.severity | ascii_upcase)] \(.description)"'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Handle verdict
|
||||
case "$VERDICT" in
|
||||
APPROVE)
|
||||
echo -e "${GREEN}✓ APPROVED - Proceeding with commit${NC}"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
NEEDS_REVIEW)
|
||||
echo -e "${YELLOW}⚠ NEEDS_REVIEW - Issues detected${NC}"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " 1. Fix issues and try again"
|
||||
echo " 2. Bypass: CLAUDE_SKIP_EVAL=1 git commit"
|
||||
echo " 3. Skip hook: git commit --no-verify"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
REJECT)
|
||||
echo -e "${RED}✗ REJECTED - Critical issues found${NC}"
|
||||
echo ""
|
||||
echo "Please fix the issues before committing."
|
||||
echo "To force commit anyway: git commit --no-verify"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo -e "${YELLOW}? Unknown verdict: $VERDICT${NC}"
|
||||
echo "Proceeding with commit (evaluation inconclusive)"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
182
examples/hooks/bash/prompt-injection-detector.sh
Executable file
182
examples/hooks/bash/prompt-injection-detector.sh
Executable file
|
|
@ -0,0 +1,182 @@
|
|||
#!/bin/bash
|
||||
# Hook: PreToolUse - Detect prompt injection attempts
|
||||
# Exit 0 = allow, Exit 2 = block (stderr message shown to Claude)
|
||||
#
|
||||
# This hook detects common prompt injection patterns that attempt to
|
||||
# manipulate Claude's behavior through malicious instructions.
|
||||
#
|
||||
# Place in: .claude/hooks/prompt-injection-detector.sh
|
||||
# Register in: .claude/settings.json under PreToolUse event
|
||||
|
||||
set -e
|
||||
|
||||
# Read JSON from stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty')
|
||||
|
||||
# Only check tools that handle user-provided text content
|
||||
case "$TOOL_NAME" in
|
||||
Bash|Write|Edit|WebFetch)
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Extract content to analyze based on tool type
|
||||
CONTENT=""
|
||||
case "$TOOL_NAME" in
|
||||
Bash)
|
||||
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
|
||||
;;
|
||||
Write|Edit)
|
||||
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.content // .new_string // empty')
|
||||
;;
|
||||
WebFetch)
|
||||
CONTENT=$(echo "$TOOL_INPUT" | jq -r '.url // empty')
|
||||
;;
|
||||
esac
|
||||
|
||||
# Skip if no content to analyze
|
||||
[[ -z "$CONTENT" ]] && exit 0
|
||||
|
||||
# Convert to lowercase for case-insensitive matching
|
||||
CONTENT_LOWER=$(echo "$CONTENT" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# === ROLE OVERRIDE PATTERNS ===
|
||||
# Attempts to override Claude's instructions or identity
|
||||
ROLE_OVERRIDE_PATTERNS=(
|
||||
"ignore previous instructions"
|
||||
"ignore all previous"
|
||||
"ignore your instructions"
|
||||
"disregard previous"
|
||||
"disregard your instructions"
|
||||
"forget your instructions"
|
||||
"forget everything"
|
||||
"you are now"
|
||||
"act as if"
|
||||
"pretend you are"
|
||||
"pretend to be"
|
||||
"from now on you"
|
||||
"new instructions:"
|
||||
"override:"
|
||||
"system prompt:"
|
||||
)
|
||||
|
||||
for pattern in "${ROLE_OVERRIDE_PATTERNS[@]}"; do
|
||||
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
|
||||
echo "BLOCKED: Prompt injection detected - role override attempt: '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
# === JAILBREAK PATTERNS ===
|
||||
# Known jailbreak techniques and bypass attempts
|
||||
JAILBREAK_PATTERNS=(
|
||||
"dan mode"
|
||||
"developer mode"
|
||||
"jailbreak"
|
||||
"do anything now"
|
||||
"bypass"
|
||||
"no restrictions"
|
||||
"without restrictions"
|
||||
"unrestricted mode"
|
||||
"god mode"
|
||||
"sudo mode"
|
||||
"admin mode"
|
||||
"debug mode"
|
||||
"maintenance mode"
|
||||
)
|
||||
|
||||
for pattern in "${JAILBREAK_PATTERNS[@]}"; do
|
||||
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
|
||||
echo "BLOCKED: Prompt injection detected - jailbreak attempt: '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
# === DELIMITER INJECTION ===
|
||||
# Attempts to inject fake system messages or close prompt sections
|
||||
DELIMITER_PATTERNS=(
|
||||
"</system>"
|
||||
"<|endoftext|>"
|
||||
"<|im_end|>"
|
||||
"[/INST]"
|
||||
"[INST]"
|
||||
"<<SYS>>"
|
||||
"<</SYS>>"
|
||||
"### System:"
|
||||
"### Human:"
|
||||
"### Assistant:"
|
||||
"```system"
|
||||
"SYSTEM:"
|
||||
)
|
||||
|
||||
for pattern in "${DELIMITER_PATTERNS[@]}"; do
|
||||
if [[ "$CONTENT" == *"$pattern"* ]]; then
|
||||
echo "BLOCKED: Prompt injection detected - delimiter injection: '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
# === AUTHORITY IMPERSONATION ===
|
||||
# Claims of special authority or permissions
|
||||
AUTHORITY_PATTERNS=(
|
||||
"anthropic employee"
|
||||
"anthropic staff"
|
||||
"i am your creator"
|
||||
"i am your developer"
|
||||
"i have admin access"
|
||||
"authorized to bypass"
|
||||
"emergency override"
|
||||
"security exception"
|
||||
"this is a test"
|
||||
"testing mode"
|
||||
)
|
||||
|
||||
for pattern in "${AUTHORITY_PATTERNS[@]}"; do
|
||||
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
|
||||
echo "BLOCKED: Prompt injection detected - authority impersonation: '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
# === BASE64 ENCODED INSTRUCTIONS ===
|
||||
# Detect potential base64-encoded payloads (heuristic)
|
||||
# Look for long base64-like strings that might contain instructions
|
||||
if echo "$CONTENT" | grep -qE '[A-Za-z0-9+/]{50,}={0,2}'; then
|
||||
# Try to decode and check for injection patterns
|
||||
DECODED=$(echo "$CONTENT" | grep -oE '[A-Za-z0-9+/]{50,}={0,2}' | head -1 | base64 -d 2>/dev/null || true)
|
||||
DECODED_LOWER=$(echo "$DECODED" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
for pattern in "ignore" "override" "system" "jailbreak" "dan mode"; do
|
||||
if [[ "$DECODED_LOWER" == *"$pattern"* ]]; then
|
||||
echo "BLOCKED: Prompt injection detected - encoded payload containing: '$pattern'" >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# === CONTEXT MANIPULATION ===
|
||||
# Attempts to manipulate the conversation context
|
||||
CONTEXT_PATTERNS=(
|
||||
"in the previous message"
|
||||
"as i mentioned earlier"
|
||||
"you agreed to"
|
||||
"you already said"
|
||||
"you promised"
|
||||
"remember when you"
|
||||
"our agreement was"
|
||||
)
|
||||
|
||||
for pattern in "${CONTEXT_PATTERNS[@]}"; do
|
||||
if [[ "$CONTENT_LOWER" == *"$pattern"* ]]; then
|
||||
# Warning only - these could be legitimate
|
||||
echo '{"systemMessage": "Warning: Detected potential context manipulation pattern. Verify legitimacy."}'
|
||||
fi
|
||||
done
|
||||
|
||||
# Allow by default
|
||||
exit 0
|
||||
102
examples/hooks/bash/session-logger.sh
Executable file
102
examples/hooks/bash/session-logger.sh
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/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
|
||||
235
examples/scripts/session-stats.sh
Executable file
235
examples/scripts/session-stats.sh
Executable file
|
|
@ -0,0 +1,235 @@
|
|||
#!/bin/bash
|
||||
# session-stats.sh - Analyze Claude Code session logs
|
||||
#
|
||||
# Analyzes logs created by session-logger.sh hook and outputs statistics
|
||||
# about tool usage, estimated costs, and session patterns.
|
||||
#
|
||||
# Usage:
|
||||
# ./session-stats.sh # Today's summary
|
||||
# ./session-stats.sh --range week # Last 7 days
|
||||
# ./session-stats.sh --range month # Last 30 days
|
||||
# ./session-stats.sh --date 2026-01-14 # Specific date
|
||||
# ./session-stats.sh --json # Machine-readable output
|
||||
# ./session-stats.sh --project myapp # Filter by project
|
||||
#
|
||||
# Environment:
|
||||
# CLAUDE_LOG_DIR - Log directory (default: ~/.claude/logs)
|
||||
#
|
||||
# Cost rates (per 1K tokens, configurable):
|
||||
# CLAUDE_RATE_INPUT - Input token rate (default: 0.003 for Sonnet)
|
||||
# CLAUDE_RATE_OUTPUT - Output token rate (default: 0.015 for Sonnet)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Configuration
|
||||
LOG_DIR="${CLAUDE_LOG_DIR:-$HOME/.claude/logs}"
|
||||
RATE_INPUT="${CLAUDE_RATE_INPUT:-0.003}"
|
||||
RATE_OUTPUT="${CLAUDE_RATE_OUTPUT:-0.015}"
|
||||
|
||||
# Defaults
|
||||
OUTPUT_MODE="human"
|
||||
DATE_RANGE="today"
|
||||
SPECIFIC_DATE=""
|
||||
PROJECT_FILTER=""
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--json)
|
||||
OUTPUT_MODE="json"
|
||||
shift
|
||||
;;
|
||||
--range)
|
||||
DATE_RANGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--date)
|
||||
SPECIFIC_DATE="$2"
|
||||
DATE_RANGE="specific"
|
||||
shift 2
|
||||
;;
|
||||
--project)
|
||||
PROJECT_FILTER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
grep '^#' "$0" | sed 's/^# \?//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if log directory exists
|
||||
if [[ ! -d "$LOG_DIR" ]]; then
|
||||
if [[ "$OUTPUT_MODE" == "json" ]]; then
|
||||
echo '{"error": "No logs found", "log_dir": "'"$LOG_DIR"'"}'
|
||||
else
|
||||
echo -e "${RED}No logs found in $LOG_DIR${NC}"
|
||||
echo "Enable session-logger.sh hook to start collecting logs."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine which log files to analyze
|
||||
LOG_FILES=()
|
||||
|
||||
case "$DATE_RANGE" in
|
||||
today)
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
[[ -f "$LOG_DIR/activity-$TODAY.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$TODAY.jsonl")
|
||||
;;
|
||||
week)
|
||||
for i in {0..6}; do
|
||||
DATE=$(date -v-${i}d +%Y-%m-%d 2>/dev/null || date -d "-$i days" +%Y-%m-%d)
|
||||
[[ -f "$LOG_DIR/activity-$DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$DATE.jsonl")
|
||||
done
|
||||
;;
|
||||
month)
|
||||
for i in {0..29}; do
|
||||
DATE=$(date -v-${i}d +%Y-%m-%d 2>/dev/null || date -d "-$i days" +%Y-%m-%d)
|
||||
[[ -f "$LOG_DIR/activity-$DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$DATE.jsonl")
|
||||
done
|
||||
;;
|
||||
specific)
|
||||
[[ -f "$LOG_DIR/activity-$SPECIFIC_DATE.jsonl" ]] && LOG_FILES+=("$LOG_DIR/activity-$SPECIFIC_DATE.jsonl")
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ${#LOG_FILES[@]} -eq 0 ]]; then
|
||||
if [[ "$OUTPUT_MODE" == "json" ]]; then
|
||||
echo '{"error": "No logs found for specified range", "range": "'"$DATE_RANGE"'"}'
|
||||
else
|
||||
echo -e "${YELLOW}No logs found for range: $DATE_RANGE${NC}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Combine and filter logs
|
||||
COMBINED_LOGS=$(cat "${LOG_FILES[@]}" 2>/dev/null || true)
|
||||
|
||||
if [[ -n "$PROJECT_FILTER" ]]; then
|
||||
COMBINED_LOGS=$(echo "$COMBINED_LOGS" | jq -c "select(.project == \"$PROJECT_FILTER\")" 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
if [[ -z "$COMBINED_LOGS" ]]; then
|
||||
if [[ "$OUTPUT_MODE" == "json" ]]; then
|
||||
echo '{"error": "No matching logs found"}'
|
||||
else
|
||||
echo -e "${YELLOW}No matching logs found${NC}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Calculate statistics
|
||||
TOTAL_OPS=$(echo "$COMBINED_LOGS" | wc -l | tr -d ' ')
|
||||
TOTAL_TOKENS_IN=$(echo "$COMBINED_LOGS" | jq -s '[.[].tokens.input // 0] | add')
|
||||
TOTAL_TOKENS_OUT=$(echo "$COMBINED_LOGS" | jq -s '[.[].tokens.output // 0] | add')
|
||||
TOTAL_TOKENS=$((TOTAL_TOKENS_IN + TOTAL_TOKENS_OUT))
|
||||
|
||||
# Cost calculation
|
||||
COST_INPUT=$(echo "scale=4; $TOTAL_TOKENS_IN * $RATE_INPUT / 1000" | bc)
|
||||
COST_OUTPUT=$(echo "scale=4; $TOTAL_TOKENS_OUT * $RATE_OUTPUT / 1000" | bc)
|
||||
COST_TOTAL=$(echo "scale=4; $COST_INPUT + $COST_OUTPUT" | bc)
|
||||
|
||||
# Tool breakdown
|
||||
TOOL_STATS=$(echo "$COMBINED_LOGS" | jq -s 'group_by(.tool) | map({tool: .[0].tool, count: length}) | sort_by(-.count)')
|
||||
|
||||
# Session count
|
||||
SESSION_COUNT=$(echo "$COMBINED_LOGS" | jq -s '[.[].session_id] | unique | length')
|
||||
|
||||
# Project breakdown
|
||||
PROJECT_STATS=$(echo "$COMBINED_LOGS" | jq -s 'group_by(.project) | map({project: .[0].project, count: length}) | sort_by(-.count)')
|
||||
|
||||
# Most edited files
|
||||
FILE_STATS=$(echo "$COMBINED_LOGS" | jq -s '[.[] | select(.file != null)] | group_by(.file) | map({file: .[0].file, count: length}) | sort_by(-.count) | .[0:10]')
|
||||
|
||||
# Output
|
||||
if [[ "$OUTPUT_MODE" == "json" ]]; then
|
||||
jq -n \
|
||||
--arg range "$DATE_RANGE" \
|
||||
--argjson total_ops "$TOTAL_OPS" \
|
||||
--argjson sessions "$SESSION_COUNT" \
|
||||
--argjson tokens_in "$TOTAL_TOKENS_IN" \
|
||||
--argjson tokens_out "$TOTAL_TOKENS_OUT" \
|
||||
--argjson tokens_total "$TOTAL_TOKENS" \
|
||||
--arg cost_in "$COST_INPUT" \
|
||||
--arg cost_out "$COST_OUTPUT" \
|
||||
--arg cost_total "$COST_TOTAL" \
|
||||
--argjson tools "$TOOL_STATS" \
|
||||
--argjson projects "$PROJECT_STATS" \
|
||||
--argjson files "$FILE_STATS" \
|
||||
'{
|
||||
range: $range,
|
||||
summary: {
|
||||
total_operations: $total_ops,
|
||||
sessions: $sessions,
|
||||
tokens: {
|
||||
input: $tokens_in,
|
||||
output: $tokens_out,
|
||||
total: $tokens_total
|
||||
},
|
||||
estimated_cost: {
|
||||
input: ($cost_in | tonumber),
|
||||
output: ($cost_out | tonumber),
|
||||
total: ($cost_total | tonumber),
|
||||
currency: "USD"
|
||||
}
|
||||
},
|
||||
tools: $tools,
|
||||
projects: $projects,
|
||||
top_files: $files
|
||||
}'
|
||||
else
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Claude Code Session Statistics - $DATE_RANGE${NC}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Summary${NC}"
|
||||
echo " Total operations: $TOTAL_OPS"
|
||||
echo " Sessions: $SESSION_COUNT"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Token Usage${NC}"
|
||||
printf " Input tokens: %'d\n" "$TOTAL_TOKENS_IN"
|
||||
printf " Output tokens: %'d\n" "$TOTAL_TOKENS_OUT"
|
||||
printf " Total tokens: %'d\n" "$TOTAL_TOKENS"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Estimated Cost (Sonnet rates)${NC}"
|
||||
printf " Input: \$%.4f\n" "$COST_INPUT"
|
||||
printf " Output: \$%.4f\n" "$COST_OUTPUT"
|
||||
printf " ${GREEN}Total: \$%.4f${NC}\n" "$COST_TOTAL"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Tools Used${NC}"
|
||||
echo "$TOOL_STATS" | jq -r '.[] | " \(.tool): \(.count)"'
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Projects${NC}"
|
||||
echo "$PROJECT_STATS" | jq -r '.[] | " \(.project): \(.count)"'
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Most Edited Files${NC}"
|
||||
echo "$FILE_STATS" | jq -r '.[] | " \(.file | split("/")[-1]): \(.count)"' | head -5
|
||||
echo ""
|
||||
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "Logs: $LOG_DIR"
|
||||
echo -e "Rate config: \$${RATE_INPUT}/1K in, \$${RATE_OUTPUT}/1K out"
|
||||
echo ""
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue