¿Qué es Gobernancia y por qué lo necesitamos?
Cuando hablamos de governance en un pipeline de CI/CD, nos referimos al conjunto de puntos de control que verifican que todo cumpla con las reglas y políticas establecidas antes de que el código llegue a producción. No es un stage aislado que aparece de golpe al final; es un ciclo de vida que atraviesa todo el pipeline, desde el primer commit del desarrollador hasta el registro del deploy en producción.
La razón de ser de la gobernanza es simple: la automatización nos da velocidad, pero la gobernanza nos da garantías. Sin ella, un pipeline puede ser rápido pero inseguro, o eficiente pero no auditable. Necesitamos ambas cosas: velocidad y control.
La pregunta que governance responde en cada etapa es:
"¿Estamos seguros de que este cambio cumple nuestras políticas antes de que llegue a los usuarios?"
El commit local la primera línea de defensa
Todo empieza en la máquina del desarrollador. Cuando ejecutamos git commit -S, estamos haciendo dos cosas simultáneamente: firmando el commit con su clave SSH/ED25519, para verificar que realmente fue él quien lo creó, y ejecutando commitlint para validar que el mensaje cumple con el formato de Conventional Commits (feat(scope): descripción). Este último control lo podemos implementar mediante Git Hooks con Husky, utilizando el hook commit-msg para ejecutarlo durante el proceso de creación del commit.
¿Por qué hacemos esto aquí?
Porque es una de las verificaciones más baratas que podemos ejecutar. Un hook local se ejecuta en milisegundos, directamente en la máquina del desarrollador, sin consumir recursos del CI. Si el mensaje del commit está mal formateado o falta la firma, el commit se aborta inmediatamente y el desarrollador puede corregirlo antes de gastar ni un segundo de tiempo de pipeline.
Esto forma parte de una práctica conocida como Shift Left: mover las validaciones lo más cerca posible del momento en que se introduce un defecto, reduciendo así el costo y el tiempo necesarios para detectarlo y corregirlo.
Cada validación corre en la capa más barata que pueda detectar su fallo.
También podemos ejecutar dentro del pre-commit hook con Husky:
lint-staged (Prettier + ESLint sobre los archivos staged)
Static Application Security Testing SAST con reglas OWASP
Gitleaks u otra herramienta para detección de secretos antes de que entren al historial de Git
Todo esto corre en paralelo antes de que el commit se confirme.
Pero hay un detalle crítico: los hooks locales son bypassable con --no-verify.
Por eso nunca confiamos exclusivamente en ellos el CI re-verifica todo. Los hooks son el primer filtro rápido; el CI es la garantía real.
Nota
Este tipo de validaciones, como parte de una estrategia de Shift Left, suele ejecutarse antes de que el código llegue al repositorio remoto, como GitHub. Muchas de las validaciones que implementamos bajo esta estrategia pueden apoyarse en Git Hooks, permitiendo analizar directamente los cambios que se encuentran en el Staging Area (staged) antes de crear el commit.
La idea es detectar los problemas lo más cerca posible de su origen. Por ejemplo, podemos ejecutar validaciones de formato, linting, análisis estático o convenciones sobre los archivos modificados, evitando analizar innecesariamente todo el repositorio.
Si todas estas validaciones estuvieran únicamente en CI (GitHub), incluso un fallo trivial de formato podría costar varios minutos de feedback loop y bloquear el proceso de integración hasta que el desarrollador corrija el problema. Con Shift Left, ese mismo error puede detectarse localmente en segundos, antes de consumir recursos del pipeline y antes de afectar al resto del equipo.
El PR Gate las verificaciones de gobierno
Cuando el desarrollador hace push y abre un PR, GitHub Actions dispara nuestro workflow de CI. Aquí es donde governance se materializa como un conjunto coordinado de checks que validan la integridad del cambio:
Commit Lint (commitlint) (Job in CI workflow)
Re-valida que todos los commits del PR sigan Conventional Commits. Es defense-in-depth: ya lo validamos en local, pero aquí lo verificamos de nuevo porque no confiamos ciegamente en los hooks locales.
Por qué existe / qué riesgo mitiga
Historial legible y machine-readable:
git log --onelinedeja de ser ruido y pasa a ser un changelog: cada commit dice qué cambió y (con scope) dónde.Release automático: Changesets y semantic-release derivan versiones (major/minor/patch) de los tipos sin Conventional Commits, el bump es manual y propenso a error.
Navegación y blame: Un
git bisectsobrefix(...)vsfeat(...)es directo; un mensaje libre ("update stuff") no permite filtrar.Gate de calidad cultural: Fuerza a cada persona a pensar qué cambia antes de commitear.
¿Por qué importa?
Los mensajes de commit conforman el changelog automático, alimentan herramientas de release (Changesets, semantic-release) y son la primera línea de documentación en el historial de Git. Un commit que dice "fix things" es inútil para cualquier persona (o herramienta) que necesite entender qué cambió y por qué.
Es el check más barato de satisfacer y el que más frecuencia de violación tiene (la gente escribe "cambios varios"). Por eso corre dos veces: una en local con git hooks (feedback en segundos, en el mismo editor) y otra en Github actions (red de seguridad obligatoria). El mensaje es claro: El historial de main es un artefacto de software, no un registro de intenciones.
Commit Signature Verification (Job in CI workflow)
GitHub firma commits con claves SSH/PGP/GPG y expone el resultado de verificación en su REST API bajo .commit.verification.verified. Nosotros firmamos localmente con una clave SSH ed25519 dedicada (git commit -S) por dos razones: es la cadena de confianza de nuestra infra (misma infraestructura que git push), y ed25519 es el algoritmo moderno recomendado (curva elíptica de 256 bits, firma y verificación rápidas, sin los problemas de tamaño de claves RSA).
El job verify-signatures del CI no verifica que el commit esté firmado** (eso ya lo garantiza el ruleset con required_signatures); verifica que GitHub haya verificado la firma que la clave es legítima y el commit no fue alterado después de firmar.
Qué valida exactamente
Para cada commit nuevo del PR (no los históricos de main), consulta la REST API y exige: .commit.verification.verified == true
Por qué existe / qué riesgo mitiga
Cadena de suministro: Un commit firmado por la clave correcta prueba que el autor tenía acceso a la clave privada; un commit no verificado puede ser inyectado por un tercero, un token comprometido o un actor automático sin identidad.
Auditabilidad: Si main debe ser reproducible y auditable, cada commit en su historial debe tener autoría verificable. Sin este gate, el merge de un commit con firma rota o sin firma por accidente era posible (un squash imperfecto, un coauthor mal configurado, un bot).
Guardian del historial: Combinado con required_linear_history, impide que el historial lineal de main se contamine con commits sin identidad verificable.
¿Por qué importa?
La firma criptográfica de commits previene impersonación. Sin ella, cualquiera podría hacer git commit --author="Senior Dev <senior@company.com>" y el pipeline lo aceptaría. Con la firma, el commit lleva una firma digital que solo el titular de la clave privada (Developer) puede generar. Si GitHub no verifica la firma, el commit no pasa.
PR Title Lint (Job in CI workflow)
Valida que el título del PR siga Conventional Commits, porque cuando hacemos squash merge hacia una rama, el título del PR se convierte en el mensaje del commit. Si el título es "update stuff", el changelog automático genera basura. Usamos amannn/action-semantic-pull-request@v6.
Nota
El tipo ops lo añadimos para cambios de operaciones/plataforma (secrets, rotación de credenciales, provisionamiento de infraestructura) que no son ci ni chore. Es el mismo patrón que usa Kubernetes.
Por qué existe / qué riesgo mitiga
El squash merge titula con
PR_TITLE(squash_merge_commit_title=PR_TITLE). Si el título no es un Conventional Commit válido, cada merge generaría un commit que fallaría el check de commit-lint del siguiente análisis. Validar el título es validar el commit que el squash va a crear.Discusión y navegación: Un PR con título semántico (
feat(client): ...) se filtra y se discute mejor que uno genérico (Update files).Consistencia con el release: El release automation lee el título del commit squash para el changelog.
¿Por qué importa?
Es el único de los 4 checks que mira un artefacto que aún no existe como commit (el título del PR). Su fallo cuesta segundos de arreglar y ahorra contaminar el historial es el caso canónico de shifting-left: validar antes de que el dato se materialice.
DCO (Developer Certificate of Origin) (Job in CI workflow)
El DCO es un certificado legal ligero (creado por el kernel de Linux) que declara: "el autor certifica que escribió este código o tiene derecho a enviarlo bajo la licencia del proyecto". Se materializa como un trailer en el mensaje del commit:
Por qué existe / qué riesgo mitiga
Proveniencia legal: sin él, cualquier commit podría alegar desconocimiento sobre el origen del código; con él, cada commit lleva una declaración explícita de que quien lo envió tenía derecho a enviarlo.
Auditoría de autoría: El trailer permite rastrear quién certificó cada línea, independientemente del autor del commit.
Cada merge conserva la cadena: Con squash merge que usa COMMIT_MESSAGES, los trailers de los commits del PR se conservan en el commit final.
¿Por qué importa?
En proyectos open source o con contribuciones externas, el DCO es evidencia legal de que el contribuyente tiene derecho a licenciar ese código bajo la licencia del proyecto. En contextos empresariales, previene disputas sobre propiedad intelectual.
Early-Abort Gate (SAST de diff) (Job in CI workflow)
Una verificación SAST ultrarrápida que analiza solo el diff del PR (no el repo completo), buscando vulnerabilidades de severidad CRÍTICA/ALTA como inyecciones SQL o RCE.
Si detecta algo, aborta el pipeline en segundos antes de que arranquen las suites de testing largas o una face de prebuild con diferentes job que reprensentan tiempo.
Por qué existe / qué riesgo mitiga
Las vulnerabilidades que el lint no ve: ESLint detecta bugs y estilo; no detecta
res.send(datosSinSanitizar)(XSS de respuesta directa),
jwt.decode(token, { algorithms: ['none'] })exec(cmd)con input del usuario.
SAST cubre esa capa.
Costo del hallazgo tardío: Encontrar un SQLi en CI cuesta un ciclo de PR; encontrarlo en producción es un incidente de seguridad (CWE-89, OWASP Top 10 A03). Detectarlo tempranamente cuesta solo segundos del pipeline.
¿Por qué importa?
Es fail-fast puro: detecta los problemas más críticos lo antes posible. En lugar de esperar 15 minutos de tests unitarios e integración para descubrir, por ejemplo, un SQL Injection en el nuevo controller, lo detecta en 30 segundos y evita consumir ese tiempo y compute innecesariamente.
Como vimos al inicio, en la validación local utilizamos Git Hooks para ejecutar un SAST con reglas OWASP, enfocándonos únicamente en los cambios que se encuentran en el Staging Area. ¿Por qué mencionarlo? Porque en este job de Governance aplicaremos prácticamente la misma estrategia, pero comparando los cambios de nuestra rama contra main.
Cuando decimos que el análisis "corre solo sobre el diff", significa que no analiza el repositorio completo, sino únicamente los archivos y líneas modificados entre feat/* y main (el three-dot diff: main...feat/x).
Esto es clave tanto por rendimiento como por precisión: evita reportar falsos positivos provenientes de código legacy que no modificamos y permite que el check se ejecute rápidamente incluso en repositorios grandes.
El ruleset el pegamento que convierte lo voluntario en obligatorio
Sin un mecanismo de enforcement, todos los jobs anteriores serían simplemente reportes: pueden detectar errores y marcar un check como fallido, pero eso no necesariamente impediría que el merge ocurra.
Aquí es donde entran los Rulesets de GitHub, que nos permiten definir y aplicar reglas de protección sobre branches y tags, así como controlar determinadas operaciones de push. A diferencia del modelo clásico de Branch Protection Rules, los Rulesets permiten gestionar reglas de forma más granular y establecer condiciones específicas para determinar cuándo un cambio puede integrarse.
Para asegurarnos de que no pueda realizarse un merge si alguno de los jobs definidos en Governance falla, configuramos un Ruleset que establezca esos checks como required status checks. De esta forma, el pipeline no solo reporta el resultado de las validaciones: el resultado pasa a convertirse en una condición obligatoria para poder integrar el código.
Estas reglas operan de forma condicional, dependiendo del branch, tag o contexto al que se apliquen. Y ese es precisamente el objetivo: que las políticas de governance no sean únicamente recomendaciones, sino controles efectivos de enforcement capaces de bloquear una integración cuando no se cumplen las condiciones establecidas.
PUSH TIME (bloquea el push mismo)
Require signed commits (required_signatures): commits firmados y verificados, GitHub rechaza el push antes de que llegue al remoto si algún commit nuevo no tiene .commit.verification.verified == true. Es la validación más temprana de la cadena: nada entra a main sin firma.
Block force pushes (non_fast_forward): Bloquea force-push, rechaza cualquier push que no sea fast-forward a la branch protegida. Impide reescribir historia (force-push con -f, git push --force, amend+pull conflictivo, rebase forzado).
Restrict deletions: Bloquea borrar la branch, bloquea la eliminación de la branch protegida (tanto git push origin :main como la UI de GitHub). Junto con non_fast_forward, garantiza que main sea inmutable: no se borra, no se reescribe.
MERGE-TIME (bloquea el merge del PR)
Require status checks to pass (required_status_checks): En el momento del merge, GitHub exige que los 5 checks estén verdes (verify-signatures, commit-lint, pr-title-lint, DCO, SAST). El binding es por dos campos
Require a pull request before merging (pull_request): Reviews + CODEOWNERS + threads
Require linear history (required_linear_history): Solo squash/rebase.
Require status checks to pass no es solo governance en un pipeline serio tendriamos muchos mas jobs por lo cuales esperariamos un visto bueno para la continuacion de nuestro proceso de CI/CD
Lo que buscamos con governance, es una capa pre-merge que garantice la INTEGRIDAD y TRAZABILIDAD del historial, no la calidad del código (eso es quality/build/test).
Que solo entre a main lo que cumple las reglas del juego del repo: commits firmados, bien formados, atribuidos y trazables.
El stage de governance no confía en quién escribe el código, confía en que el historial que se construye sea íntegro, verificable y auditable la base sobre la cual quality, testing y security pueden actuar después.





Top comments (0)