sparrow-cli 0.5.4

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