Parte: 0 — Fundamentos y prerrequisitos · Fuente: Chacon & Straub, Pro Git ⏱️ Duración estimada: 100 min · Nivel: Fundamentos
Usar Git con soltura para versionar herramientas, notas y hallazgos, colaborar en equipo y, sobre todo, evitar filtrar secretos en el historial, uno de los errores de seguridad más comunes y más caros del mundo real. Al terminar manejarás el flujo básico, las ramas, la resolución de conflictos y las prácticas de higiene de secretos, y entenderás por qué el modelo de datos de Git hace que un secreto empujado no se borre con solo eliminarlo en un commit posterior.
Al finalizar, el alumno podrá:
.gitignore y buenas prácticas de higiene.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Modelo de Git | Guarda snapshots completos, no diffs |
| 2 | Área de staging | El paso intermedio que muchos ignoran |
| 3 | Flujo básico | add / commit / log / diff |
| 4 | Ramas y merge | Trabajo paralelo aislado |
| 5 | Conflictos | Resolverlos sin romper nada |
| 6 | Remotos | Colaboración, respaldo y su riesgo |
| 7 | .gitignore |
Primera línea contra subir secretos |
| 8 | Secretos en el historial | El error clásico y su remediación |
La intuición equivocada más extendida es pensar que Git guarda las diferencias entre versiones. No: Git guarda snapshots completos del árbol de ficheros en cada commit, referenciados por un hash SHA-1 (o SHA-256 en repos modernos) que resume el contenido. Cada commit apunta a su árbol de contenido y a su commit o commits padre, formando un grafo dirigido acíclico donde cada nodo es inmutable e identificado por su hash. Esta arquitectura tiene una consecuencia de seguridad enorme y contraintuitiva: como cada commit es un objeto inmutable direccionado por contenido, un secreto que entró en un commit vive en ese commit para siempre, aunque lo borres en un commit posterior. El commit nuevo solo añade un snapshot sin el secreto; el commit viejo, con el secreto, sigue ahí y sigue siendo accesible con git log -p o git show. El siguiente diagrama muestra la cadena de commits y por qué eliminar en C4 no toca C2.
Git organiza tu trabajo en tres zonas, y entenderlas evita la mayor parte de la confusión de un principiante. El directorio de trabajo son tus ficheros tal como los ves y editas. El área de staging (o índice) es una zona intermedia donde preparas exactamente qué cambios entrarán en el próximo commit: git add mueve cambios del directorio de trabajo al staging. El repositorio es donde git commit graba de forma permanente lo que había en staging. Este paso intermedio es una virtud, no un estorbo: te permite componer commits atómicos y coherentes (por ejemplo, no mezclar el arreglo de un bug con el fichero .env que se te coló). En seguridad, mirar qué hay en staging con git diff --cached antes de cada commit es un hábito que evita fugas.
Una rama en Git no es una copia de los ficheros sino un simple puntero móvil a un commit; crear una rama es tan barato como escribir 41 bytes en un fichero. Eso hace que ramificar sea la operación natural para aislar trabajo: pruebas un exploit peligroso o una refactorización arriesgada en su propia rama, sin ensuciar main, y si sale mal la descartas. Fusionar (merge) integra el trabajo de una rama en otra. Cuando las dos ramas modificaron líneas distintas, Git fusiona automáticamente; cuando ambas tocaron la misma línea, se produce un conflicto que Git no puede resolver por ti: marca la zona con <<<<<<<, ======= y >>>>>>>, y tú decides qué versión queda, editas el fichero, haces git add y confirmas. Resolver conflictos con calma, entendiendo ambas versiones en lugar de borrar a ciegas, es una destreza que se practica.
Un remoto es una copia del repositorio en otra máquina (GitHub, GitLab, un servidor propio) con la que sincronizas mediante push y pull. Git es distribuido: cada clone es una copia completa del historial, no un enlace a un servidor central. Aquí está el núcleo del problema de seguridad: cuando empujas un secreto a un remoto público, no solo queda en el servidor, sino que cualquiera que clone el repositorio se lleva una copia íntegra del historial, secreto incluido, y hay bots que escanean GitHub en continuo y encuentran claves nuevas en segundos. Por eso la única respuesta segura ante un secreto expuesto no es borrarlo, sino rotarlo: invalidar la credencial filtrada y emitir una nueva.
La prevención empieza con .gitignore, un fichero que lista patrones de rutas que Git no debe rastrear (.env, *.pem, *.key, venv/). Es la primera línea de defensa, aunque tiene un matiz: .gitignore solo ignora ficheros que Git aún no rastrea; si un fichero ya fue añadido, hay que sacarlo del seguimiento con git rm --cached. La segunda línea son los escáneres de secretos como gitleaks o trufflehog, que recorren todo el historial buscando patrones de claves, tokens y contraseñas, y que se integran idealmente como pre-commit hook para bloquear la fuga antes de que ocurra. Cuando la prevención falla y el secreto ya está en el historial, la remediación tiene dos pasos innegociables, y este es el orden mental que debes memorizar:
Fíjate en que rotar va primero: reescribir el historial es lento y no garantiza nada si alguien ya clonó el repo, mientras que rotar la clave la vuelve inútil de inmediato aunque el atacante la tenga.
git add la llena y git diff --cached te deja revisarla antes de confirmar, un hábito clave para no filtrar secretos.main, lo que permite experimentar con exploits o cambios arriesgados de forma segura..gitignore: fichero con patrones de rutas que Git no debe rastrear. Es la primera defensa contra subir secretos y artefactos, pero solo afecta a ficheros aún no rastreados.| Término | Definición concisa |
|---|---|
| Commit | Snapshot inmutable del proyecto con hash único |
| Hash SHA | Identificador de contenido de un objeto Git |
| Staging (índice) | Zona intermedia de cambios preparados |
git add |
Mueve cambios al área de staging |
| Rama | Puntero móvil a un commit |
main |
Rama principal por convención |
| Merge | Integra una rama en otra |
| Conflicto | Choque de ediciones en la misma línea |
| Remoto | Copia del repo en otra máquina |
push / pull |
Sincronizan con el remoto |
.gitignore |
Patrones de ficheros no rastreados |
| Historial | Cadena inmutable de commits |
| gitleaks | Escáner de secretos en el historial |
| Rotar credencial | Invalidar y reemitir una clave expuesta |
git rm --cached |
Deja de rastrear un fichero sin borrarlo |
Instala Git y configúralo con tu identidad:
sudo apt install git
git config --global user.name "Tu Nombre"
git config --global user.email "tu@correo"
Para el escaneo de secretos, instala gitleaks (https://github.com/gitleaks/gitleaks). Opcionalmente, crea una cuenta en GitHub o GitLab para practicar los remotos. Importante: no ejecutes los ejercicios de esta clase dentro del repositorio del curso; crea un repositorio de práctica aparte para poder plantar y borrar secretos sin riesgo.
bash
mkdir ~/practica-git && cd ~/practica-git && git init
bash
echo "# Notas" > README.md
git add README.md
git diff --cached # revisa qué vas a confirmar
git commit -m "Primer commit"
git log --oneline
.gitignore con patrones típicos de secretos y artefactos:gitignore
.env
*.pem
*.key
venv/
__pycache__/
secreto.env con una clave falsa, añádelo por error y haz commit. Comprueba que aparece en git log -p.bash
gitleaks detect --source . -v
Observa cómo detecta el secreto plantado en el historial.
7. Aprende la lección: borra secreto.env en un commit nuevo y vuelve a ejecutar gitleaks. Verás que sigue detectándolo, porque permanece en el commit anterior. Esa es la prueba de que hay que reescribir el historial y, sobre todo, rotar el secreto.
git log --graph --oneline..gitignore completo para un proyecto Python de seguridad (entornos, claves, artefactos, cachés).git log, git diff y git blame para investigar quién y cuándo cambió una línea concreta.git filter-repo o BFG, y argumenta por qué además hay que rotar la credencial.Crea un repositorio de práctica, simula la fuga de un secreto (clave ficticia) en un commit, detéctalo con gitleaks, y documenta el procedimiento correcto de remediación: rotación de la credencial y eliminación del historial. Añade un .gitignore que habría prevenido la fuga.
Criterio de aceptación: gitleaks detecta el secreto plantado en el historial; tu .gitignore incluye el patrón que lo habría bloqueado; y tu documento explica por qué "borrar el fichero en un commit posterior" es insuficiente, por qué la rotación es lo primero y qué herramienta se usa para reescribir el historial. Todo reproducible en un repo limpio.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| Secreto ya empujado a un remoto | Rota la credencial de inmediato; reescribir el historial no basta si alguien ya lo clonó. |
.gitignore no ignora un fichero |
Ya estaba rastreado. Ejecuta git rm --cached fichero y vuelve a confirmar. |
| Conflicto de merge sin resolver | Edita los marcadores <<<<<<<, =======, >>>>>>>, haz git add y confirma. |
detached HEAD |
Hiciste checkout a un commit, no a una rama. Crea una rama desde ahí con git switch -c. |
| Push rechazado (non-fast-forward) | El remoto avanzó. Haz git pull --rebase, resuelve y vuelve a empujar. |
gitleaks sigue detectando tras borrar |
El secreto persiste en commits anteriores. Reescribe el historial y rota la clave. |
❓ ¿Por qué es tan grave subir un secreto a Git? Porque el historial es distribuido y persistente: cualquiera que clone el repo se lleva el secreto, y hay bots que escanean GitHub en segundos. La única respuesta segura es rotar el secreto, no confiar en borrarlo.
❓ ¿git rm borra un secreto del historial? No: git rm lo quita del árbol actual, pero sigue presente en los commits anteriores. Hay que reescribir el historial (git filter-repo o BFG) y, antes que nada, rotar la clave.
❓ ¿Necesito ramas si trabajo solo? No es obligatorio, pero las ramas te dejan experimentar con algo peligroso (un exploit, una refactorización) sin ensuciar main. Es buena higiene y prácticamente gratis.
❓ ¿Debo pasar gitleaks en cada commit? Sí: integrarlo como pre-commit hook evita las fugas antes de que ocurran. La prevención es mucho más barata que la remediación, que implica rotar credenciales y reescribir historial.
Clase 017 — Python para seguridad: manipulación de paquetes con Scapy
Clase 019 — Expresiones regulares para análisis de logs y datos