Hook global de pre-push sanitize (2026-09-27)

Complemento de la skill sanitize. La skill ayuda si alguien la invoca; el hook corre solo, sin depender de la memoria.

Instalación

git config --global core.hooksPath ~/.config/git/hooks
# + el archivo ~/.config/git/hooks/pre-push (abajo), ejecutable

Comportamiento

  • Corre scan.sh . --history en el repo que se empuja.
  • Si hay blockers → aborta el push con explicación.
  • Bypass puntual: git push --no-verify.
  • Encadena al pre-push propio del repo (.git/hooks/pre-push) si existe, para no romper otros hooks.
  • Si falta la skill, no bloquea (falla abierto).

Criterio de precisión (importante, y costó una iteración)

Un hook global no puede bloquear por cualquier correo o sería inservible. Entonces:

quéhook normal--strict
tokens/llaves (prefijos conocidos: hf, ghp, sk, AKIA, cfat, xox; private keys)bloqueabloquea
tus identidades (~/nef/pii-markers.txt)bloqueabloquea
correos genéricos de tercerosavisabloquea
punteros a credenciales (rc del shell, netrc, credenciales de AWS)bloqueabloquea

Los correos seguros (noreply de GitHub, dominios de ejemplo, noreply@) se excluyen.

El hook (verbatim, recuperable)

#!/usr/bin/env bash
# Hook global de pre-push: corre el escaner de PII/credenciales (skill sanitize).
# Bypass puntual:  git push --no-verify
gd="$(git rev-parse --git-dir 2>/dev/null || true)"
if [ -n "$gd" ] && [ -x "$gd/hooks/pre-push" ]; then
  "$gd/hooks/pre-push" "$@" || exit $?
fi
SCAN="$HOME/.claude/skills/sanitize/scan.sh"
[ -x "$SCAN" ] || exit 0
if ! bash "$SCAN" . --history; then
  echo
  echo "PUSH ABORTADO por sanitize: hay PII o credenciales (ver arriba)."
  echo "Si es un falso positivo justificado:  git push --no-verify"
  exit 1
fi
exit 0

Bugs que salieron al PROBAR (no al escribir)

  1. El patrón empezaba con - (de la cabecera de private key): grep lo leía como opción y fallaba en silencio → el leak pasaba. Se arregla con -e "$PATRON".
  2. Los marcadores se buscaban en un directorio que no era donde vive el archivo real (este es ~/nef/, sin punto). Sin marcadores, el escáner no veía nada.
  3. Falsos positivos: un nombre de variable sin valor y el propio noreply atrapado por el marcador del local-part. Se quitó el primero y se excluyen los correos seguros.

Verificado

casoresultado
repo con un marcador tuyopush abortado ✅
repo genéricamente sucio (correo de tercero)avisa, no bloquea ✅
repo limpio de verdadpush pasa ✅
clinica-chat (real, árbol + historial)limpio ✅

Límites honestos

  • Un repo con core.hooksPath local (p. ej. husky) anula el global en ese repo. El encadenado cubre los pre-push en .git/hooks, no el caso husky.
  • git push --no-verify siempre puede saltárselo. Es una red, no una jaula.
  • Meta-efecto: un doc que documenta los patrones del escáner los dispara. Se escribe sin los literales (p. ej. “rc del shell” en vez del nombre del archivo).