English · Español
Lab 02 — Añadir un hook de pre-commit personalizado¶
Pre-requisito: lee
../theory/02-engineering-hygiene.md. Objetivo: escribir un hook de pre-commit local (no desde un repo público) que fuerce una regla específica del proyecto. No miressolutions/02-precommit-ref.mdhasta haberlo hecho funcionar.
§1 Contexto¶
.pre-commit-config.yaml declara los hooks. La mayoría de hooks viven en repos públicos (pre-commit-hooks, ruff-pre-commit, etc.) y están versionados por tag. Pero para reglas específicas del proyecto — cosas que solo importan a este currículo — escribes un hook local: un script en este repo que pre-commit invoca.
§2 Tu tarea¶
Escribe un hook local de pre-commit llamado forbid-pickle-in-checkpoint-load que:
- Escanee archivos
.pystaged buscando las cadenaspickle.load(opickle.loads(. - Permita matches en:
- Archivos cuya ruta empiece con
tests/. - Líneas que incluyan el comentario al final
# nosec safe-source: <reason>. - Rechace cualquier otro match con un mensaje de error claro que mencione
safetensorscomo alternativa.
Esto es una defensa real de security/THREATS.md: pickle.load sobre un checkpoint que vino de fuente no confiable ejecuta código arbitrario al cargar. Fase 16+ usa exclusivamente safetensors.
§3 Restricciones¶
- Python puro, sin deps extra. Usa solo la librería estándar.
- El script vive en
scripts/precommit/forbid_pickle_loads.py. - Debe ser ejecutable directamente (
uv run python scripts/precommit/forbid_pickle_loads.py file1.py file2.py) y salir con código distinto de cero si encuentra algo. - Debe estar conectado en
.pre-commit-config.yamlcomo hookrepo: localconlanguage: python. - La salida debe ser
path:line:col: <message>en líneas ofensivas — el mismo formato que usaruff, así los LSP de editor pueden navegar.
§4 Tests¶
Añade tests/test_forbid_pickle_loads.py cubriendo:
- Un archivo que contenga
pickle.load(...)→ hook sale con 1, imprime una detección. - Un archivo en
tests/que contengapickle.load(...)→ hook sale con 0. - Un archivo con
pickle.load(...) # nosec safe-source: round-trip test→ hook sale con 0. - Un archivo con
pickle.loads(b"...")→ hook sale con 1. - Un archivo con solo
import pickle→ hook sale con 0.
No leas input solo desde fixtures de filesystem — capsys de pytest + pasar rutas de archivos temporales vía tmp_path es más limpio.
§5 Condiciones de parada¶
-
uv run python scripts/precommit/forbid_pickle_loads.py <file>produce los códigos de salida esperados. - El hook se dispara en un
git commit -am 'test'real donde añadistepickle.load(...)en sitio no whitelisteado. -
pytest tests/test_forbid_pickle_loads.pypasa. -
just lintestá en verde. - Commit:
lab: phase-00 add forbid-pickle-loads pre-commit hook.
§6 Qué habrás aprendido¶
- Cómo pre-commit invoca hooks locales (lista de archivos como argv, código de salida = pasa/falla).
- La diferencia entre una puerta de estilo (ruff) y una puerta de política (este — sobre seguridad).
- Por qué
# noseccon una razón es mejor que supresión general (auditable, acotado, removible). - El argumento de
safetensorscomo ejemplo concreto.
§7 Pistas (úsalas con moderación)¶
pre-commitpasa los archivos staged como args posicionales al hook.sys.argv[1:]es la lista.- Usa
tokenizeo un regex simple sobre líneas — parsing AST completo es excesivo para una política de búsqueda de cadena. - La config
repo: localen pre-commit: - El hook también debería funcionar cuando se ejecuta con cero archivos (no-op, exit 0).
Si recurres a
solutions/02-precommit-ref.mdantes de completar esto, marcadod.lab_attempted_before_solutions: false. La honestidad importa aquí.