sparrow-cli 0.5.0

A local-first Rust agent cockpit β€” route, run, replay, rewind
Documentation
#!/usr/bin/env bash
# Sparrow Pre-Commit Hook
# Scans staged files for secrets, tokens, and sensitive patterns.
# Installed by: sparrow hook install
# Docs: https://github.com/ucav/Sparrow

set -euo pipefail

RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

echo -e "${YELLOW}πŸ”’ Sparrow pre-commit hook β€” scanning staged files...${NC}"

# ─── 1. Check for sensitive filenames ────────────────────────────────
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true)

if [ -z "$STAGED_FILES" ]; then
    echo -e "${GREEN}βœ“ No staged files to scan.${NC}"
    exit 0
fi

SENSITIVE_NAMES=(
    ".env" ".env.local" ".env.production" ".env.development"
    "credentials.json" "service-account.json" "secrets.yaml" "secrets.yml"
    ".netrc" ".npmrc" ".pypirc"
    "id_rsa" "id_ed25519" "id_ecdsa"
)

ISSUES=0

for file in $STAGED_FILES; do
    filename=$(basename "$file")
    for sensitive in "${SENSITIVE_NAMES[@]}"; do
        if [ "$filename" = "$sensitive" ]; then
            echo -e "${RED}βœ— BLOCKED: Sensitive file staged: $file${NC}"
            echo -e "  β†’ Remove it: git rm --cached $file"
            ISSUES=$((ISSUES + 1))
        fi
    done

    # Check for .pem, .key, .p12, .pfx extensions
    case "$filename" in
        *.pem|*.key|*.p12|*.pfx)
            echo -e "${RED}βœ— BLOCKED: Private key file staged: $file${NC}"
            echo -e "  β†’ Remove it: git rm --cached $file"
            ISSUES=$((ISSUES + 1))
            ;;
    esac

    # Check for agent config directories
    case "$file" in
        .sparrow/*|.codex/*|.agent/*)
            echo -e "${YELLOW}⚠ WARNING: Agent config directory staged: $file${NC}"
            echo -e "  β†’ Consider adding to .gitignore"
            ;;
    esac

    # ─── 2. Scan content of staged files ─────────────────────────────
    if [ -f "$file" ]; then
        # Get the staged content (not working tree)
        STAGED_CONTENT=$(git show ":$file" 2>/dev/null || true)

        if [ -n "$STAGED_CONTENT" ]; then
            # Check for common API key patterns
            # GitHub tokens
            if echo "$STAGED_CONTENT" | grep -qE 'ghp_[0-9a-zA-Z]{36}|github_pat_[0-9a-zA-Z_]{36,}'; then
                echo -e "${RED}βœ— BLOCKED: GitHub token detected in $file${NC}"
                ISSUES=$((ISSUES + 1))
            fi

            # OpenAI keys
            if echo "$STAGED_CONTENT" | grep -qE 'sk-[0-9a-zA-Z]{32,}|sk-proj-[0-9a-zA-Z]{32,}'; then
                echo -e "${RED}βœ— BLOCKED: OpenAI API key detected in $file${NC}"
                ISSUES=$((ISSUES + 1))
            fi

            # Anthropic keys
            if echo "$STAGED_CONTENT" | grep -qE 'sk-ant-[0-9a-zA-Z]{32,}'; then
                echo -e "${RED}βœ— BLOCKED: Anthropic API key detected in $file${NC}"
                ISSUES=$((ISSUES + 1))
            fi

            # AWS keys
            if echo "$STAGED_CONTENT" | grep -qE 'AKIA[0-9A-Z]{16}'; then
                echo -e "${RED}βœ— BLOCKED: AWS Access Key detected in $file${NC}"
                ISSUES=$((ISSUES + 1))
            fi

            # Generic API key patterns
            if echo "$STAGED_CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret|secret[_-]?key)\s*[:=]\s*['"'"'"]?\w{20,}'; then
                echo -e "${YELLOW}⚠ WARNING: Possible API key in $file${NC}"
                echo -e "  β†’ Review and use environment variables instead"
            fi

            # Private keys
            if echo "$STAGED_CONTENT" | grep -qE '-----BEGIN (RSA|DSA|EC|OPENSSH|PGP) PRIVATE KEY-----'; then
                echo -e "${RED}βœ— BLOCKED: Private key detected in $file${NC}"
                ISSUES=$((ISSUES + 1))
            fi

            # Password/token assignment
            if echo "$STAGED_CONTENT" | grep -qiE '(password|passwd|pwd|token|auth[_-]?token)\s*[:=]\s*['"'"'"]?\S{4,}'; then
                echo -e "${YELLOW}⚠ WARNING: Possible hardcoded password/token in $file${NC}"
            fi
        fi
    fi
done

# ─── 3. Result ────────────────────────────────────────────────────────────────
if [ $ISSUES -gt 0 ]; then
    echo ""
    echo -e "${RED}══════════════════════════════════════════════════${NC}"
    echo -e "${RED}  COMMIT BLOQUÉ — $ISSUES problème(s) de sécurité${NC}"
    echo -e "${RED}══════════════════════════════════════════════════${NC}"
    echo ""
    echo "Pour ignorer (dΓ©conseillΓ©) : git commit --no-verify"
    echo "Pour enlever un fichier du stage : git rm --cached <fichier>"
    echo "Pour dΓ©sinstaller ce hook : rm .git/hooks/pre-commit"
    exit 1
else
    echo -e "${GREEN}βœ“ Aucun problΓ¨me dΓ©tectΓ© β€” commit autorisΓ©.${NC}"
    exit 0
fi