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), ejecutableComportamiento
- Corre
scan.sh . --historyen 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) | bloquea | bloquea |
tus identidades (~/nef/pii-markers.txt) | bloquea | bloquea |
| correos genéricos de terceros | avisa | bloquea |
| punteros a credenciales (rc del shell, netrc, credenciales de AWS) | bloquea | bloquea |
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 0Bugs que salieron al PROBAR (no al escribir)
- El patrón empezaba con
-(de la cabecera de private key):greplo leía como opción y fallaba en silencio → el leak pasaba. Se arregla con-e "$PATRON". - 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. - 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
| caso | resultado |
|---|---|
| repo con un marcador tuyo | push abortado ✅ |
| repo genéricamente sucio (correo de tercero) | avisa, no bloquea ✅ |
| repo limpio de verdad | push pasa ✅ |
clinica-chat (real, árbol + historial) | limpio ✅ |
Límites honestos
- Un repo con
core.hooksPathlocal (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-verifysiempre 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).