{"id":660,"date":"2026-03-06T18:09:45","date_gmt":"2026-03-06T17:09:45","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=660"},"modified":"2026-03-24T18:08:01","modified_gmt":"2026-03-24T17:08:01","slug":"lab-hardening-github-actions-workflows-permissions-pinning-secrets","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-hardening-github-actions-workflows-permissions-pinning-secrets\/","title":{"rendered":"Lab: Hardening de Workflows GitHub Actions \u2014 Permisos, Pinning y Secretos"},"content":{"rendered":"<h2>Descripci\u00f3n General<\/h2>\n<p>GitHub Actions se ha convertido en la plataforma de CI\/CD m\u00e1s ampliamente adoptada tanto para software de c\u00f3digo abierto como comercial. Esa popularidad la convierte en la superficie de ataque n\u00famero uno en el panorama CI\/CD. Los workflows mal configurados filtran secretos de forma rutinaria, otorgan permisos excesivos e incorporan c\u00f3digo de terceros que puede ser manipulado silenciosamente.<\/p>\n<p>En este lab pr\u00e1ctico, endurecer\u00e1s un workflow de GitHub Actions deliberadamente inseguro utilizando las tres t\u00e9cnicas de mayor impacto disponibles actualmente:<\/p>\n<ol>\n<li><strong>Permisos m\u00ednimos<\/strong> \u2014 restringir el <code>GITHUB_TOKEN<\/code> \u00fanicamente a los scopes que cada job realmente necesita.<\/li>\n<li><strong>SHA pinning<\/strong> \u2014 referenciar cada action de terceros por su SHA de commit inmutable en lugar de un tag mutable.<\/li>\n<li><strong>Protecci\u00f3n de secretos<\/strong> \u2014 delimitar los secretos a environments con puertas de aprobaci\u00f3n y prevenir fugas a trav\u00e9s de pull requests basados en forks.<\/li>\n<\/ol>\n<p>Al finalizar el lab, tendr\u00e1s una plantilla de workflow de nivel producci\u00f3n que puedes incorporar en cualquier repositorio.<\/p>\n<h2>Requisitos Previos<\/h2>\n<ul>\n<li>Una cuenta de GitHub con permisos para crear repositorios.<\/li>\n<li>Familiaridad b\u00e1sica con la sintaxis YAML de GitHub Actions (triggers, jobs, steps).<\/li>\n<li>El CLI <code>gh<\/code> instalado (opcional pero \u00fatil para consultar SHAs de actions).<\/li>\n<\/ul>\n<h2>Configuraci\u00f3n del Entorno<\/h2>\n<h3>Crear un Repositorio de Prueba<\/h3>\n<p>Crea un nuevo repositorio p\u00fablico en GitHub llamado <code>gha-hardening-lab<\/code>. Puedes hacerlo a trav\u00e9s de la interfaz o con el CLI:<\/p>\n<pre><code>gh repo create gha-hardening-lab --public --clone\ncd gha-hardening-lab<\/code><\/pre>\n<p>Inicializa un proyecto Node.js m\u00ednimo para que el workflow tenga algo que construir:<\/p>\n<pre><code>npm init -y\ncat &lt;&lt;'EOF' &gt; index.js\nconsole.log(\"Hello from the hardening lab\");\nEOF\ngit add -A &amp;&amp; git commit -m \"Initial commit\" &amp;&amp; git push<\/code><\/pre>\n<h3>El Workflow Inicial (Inseguro)<\/h3>\n<p>Crea el archivo <code>.github\/workflows\/build.yml<\/code> con el siguiente contenido. Este workflow es intencionalmente inseguro \u2014 no tiene bloque de permissions, usa tags mutables y expone secretos de forma demasiado amplia:<\/p>\n<pre><code># .github\/workflows\/build.yml  \u2014 Punto de partida INSEGURO\nname: Build\n\non:\n  push:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: build-output\n          path: .\n<\/code><\/pre>\n<p>Haz commit y push de este archivo. Se ejecutar\u00e1 correctamente, pero tiene al menos cinco problemas de seguridad que corregir\u00e1s en los ejercicios siguientes.<\/p>\n<h2>Ejercicio 1: Permisos M\u00ednimos<\/h2>\n<h3>El Problema con los Permisos por Defecto<\/h3>\n<p>Cuando un workflow no declara un bloque <code>permissions<\/code>, el <code>GITHUB_TOKEN<\/code> recibe los permisos por defecto del repositorio. Para la mayor\u00eda de los repositorios, esto significa <strong>acceso de lectura y escritura a cada scope<\/strong> \u2014 contents, packages, issues, pull requests, deployments y m\u00e1s. Si un atacante compromete cualquier step en ese workflow, hereda todos esos permisos.<\/p>\n<p>El principio de m\u00ednimo privilegio exige que otorgues \u00fanicamente los permisos que cada job realmente requiere, y nada m\u00e1s.<\/p>\n<h3>Paso 1 \u2014 Establecer un Valor por Defecto Restrictivo a Nivel Superior<\/h3>\n<p>A\u00f1ade una clave <code>permissions<\/code> a nivel superior inmediatamente despu\u00e9s del bloque <code>on:<\/code>. Esto establece el valor por defecto para cada job en el workflow:<\/p>\n<pre><code>permissions:\n  contents: read\n<\/code><\/pre>\n<p>Si deseas comenzar con el valor por defecto m\u00e1s restrictivo posible y luego otorgar permisos por job, puedes usar un mapa vac\u00edo:<\/p>\n<pre><code>permissions: {}\n<\/code><\/pre>\n<h3>Paso 2 \u2014 A\u00f1adir Permisos por Job<\/h3>\n<p>Cada job puede sobrescribir el valor por defecto a nivel de workflow. Otorga \u00fanicamente lo que el job necesita:<\/p>\n<pre><code>jobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read       # check out code\n      actions: read        # read workflow metadata\n    steps:\n      - uses: actions\/checkout@v4\n      # ...\n<\/code><\/pre>\n<p>Si un segundo job necesita subir un asset de release, le otorgar\u00edas <code>contents: write<\/code> \u00fanicamente a ese job \u2014 nunca a nivel de workflow.<\/p>\n<h3>Antes y Despu\u00e9s<\/h3>\n<p><strong>Antes (inseguro):<\/strong><\/p>\n<pre><code>name: Build\non:\n  push:\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - run: npm install\n<\/code><\/pre>\n<p><strong>Despu\u00e9s (endurecido):<\/strong><\/p>\n<pre><code>name: Build\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      - uses: actions\/checkout@v4\n      - run: npm install\n<\/code><\/pre>\n<h3>Verificar los Permisos Efectivos<\/h3>\n<p>Despu\u00e9s de que el workflow se ejecute, abre el job en la pesta\u00f1a Actions. Haz clic en el \u00edcono de engranaje en la parte superior derecha del log del job y selecciona <strong>\u201cSet up job\u201d<\/strong>. Expande esa secci\u00f3n para ver los permisos exactos del <code>GITHUB_TOKEN<\/code> que fueron otorgados. Confirma que solo aparecen <code>contents: read<\/code> y <code>actions: read<\/code>.<\/p>\n<p>Tambi\u00e9n puedes consultar los permisos program\u00e1ticamente dentro de un step:<\/p>\n<pre><code>- name: Print token permissions\n  run: |\n    curl -s -H \"Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}\" \\\n      https:\/\/api.github.com\/repos\/${{ github.repository }} \\\n      | jq '.permissions'\n<\/code><\/pre>\n<h2>Ejercicio 2: Fijar Actions por SHA<\/h2>\n<h3>Por Qu\u00e9 los Tags Son Peligrosos<\/h3>\n<p>Cuando escribes <code>uses: actions\/checkout@v4<\/code>, est\u00e1s referenciando un tag de Git. Los tags son mutables \u2014 el mantenedor de la action (o un atacante que comprometa su cuenta) puede eliminar y recrear el tag apuntando a un c\u00f3digo completamente diferente. Tu workflow entonces ejecutar\u00eda silenciosamente el nuevo c\u00f3digo en su siguiente ejecuci\u00f3n. El SHA pinning elimina este riesgo porque un SHA de commit es inmutable.<\/p>\n<h3>Paso 1 \u2014 Encontrar el SHA de una Action<\/h3>\n<p>Usa el CLI <code>gh<\/code> para resolver un tag a su SHA de commit:<\/p>\n<pre><code># Resolver actions\/checkout@v4 a un SHA de commit\ngh api repos\/actions\/checkout\/git\/ref\/tags\/v4 --jq '.object.sha'<\/code><\/pre>\n<p>Si el tag es anotado (la mayor\u00eda lo son), el comando anterior devuelve el SHA del objeto tag. Necesitas desreferenciarlo al commit:<\/p>\n<pre><code>TAG_SHA=$(gh api repos\/actions\/checkout\/git\/ref\/tags\/v4 --jq '.object.sha')\ngh api repos\/actions\/checkout\/git\/tags\/$TAG_SHA --jq '.object.sha'<\/code><\/pre>\n<p>Alternativamente, visita el repositorio de la action en GitHub, haz clic en el tag y copia el SHA de commit completo desde la URL o el encabezado del commit.<\/p>\n<h3>Paso 2 \u2014 Fijar las Actions Comunes<\/h3>\n<p>Reemplaza cada tag mutable con el SHA completo de 40 caracteres. Siempre a\u00f1ade un comentario al final con la versi\u00f3n para facilitar la lectura:<\/p>\n<pre><code>steps:\n  - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n  - uses: actions\/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n    with:\n      node-version: 20\n  - uses: actions\/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3\n    with:\n      path: ~\/.npm\n      key: ${{ runner.os }}-node-${{ hashFiles('**\/package-lock.json') }}\n  - uses: actions\/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n    with:\n      name: build-output\n      path: dist\/\n<\/code><\/pre>\n<h3>Paso 3 \u2014 Automatizar las Actualizaciones de SHA con Dependabot<\/h3>\n<p>Fijar por SHA significa que ya no recibir\u00e1s actualizaciones autom\u00e1ticas basadas en tags. Dependabot resuelve esto abriendo pull requests cada vez que una action fijada publica una nueva versi\u00f3n.<\/p>\n<p>Crea el archivo <code>.github\/dependabot.yml<\/code>:<\/p>\n<pre><code>version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"\/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"ci\"\n<\/code><\/pre>\n<p>Despu\u00e9s de hacer push de este archivo, Dependabot escanear\u00e1 tus workflows semanalmente y abrir\u00e1 PRs para actualizar los SHAs fijados. Cada PR muestra el diff del c\u00f3digo de la action, d\u00e1ndote la oportunidad de revisar antes de hacer merge.<\/p>\n<p>Si prefieres Renovate en lugar de Dependabot, a\u00f1ade un archivo <code>renovate.json<\/code> en la ra\u00edz del repositorio:<\/p>\n<pre><code>{\n  \"$schema\": \"https:\/\/docs.renovatebot.com\/renovate-schema.json\",\n  \"extends\": [\"config:recommended\"],\n  \"github-actions\": {\n    \"enabled\": true\n  }\n}\n<\/code><\/pre>\n<h2>Ejercicio 3: Protecci\u00f3n de Secretos<\/h2>\n<h3>Repository Secrets vs. Environment Secrets<\/h3>\n<p>GitHub ofrece dos niveles de almacenamiento de secretos:<\/p>\n<ul>\n<li><strong>Repository secrets<\/strong> \u2014 disponibles para cada workflow y cada job en el repositorio. Convenientes pero excesivamente amplios.<\/li>\n<li><strong>Environment secrets<\/strong> \u2014 disponibles \u00fanicamente para jobs que declaren expl\u00edcitamente <code>environment: &lt;nombre&gt;<\/code>. Este es el enfoque recomendado para credenciales sensibles.<\/li>\n<\/ul>\n<h3>Paso 1 \u2014 Crear un Environment con Reglas de Protecci\u00f3n<\/h3>\n<p>En tu repositorio, navega a <strong>Settings \u2192 Environments<\/strong> y crea un environment llamado <code>production<\/code>. Habilita las siguientes reglas de protecci\u00f3n:<\/p>\n<ol>\n<li><strong>Required reviewers<\/strong> \u2014 a\u00f1ade al menos un miembro del equipo que debe aprobar los deployments.<\/li>\n<li><strong>Wait timer<\/strong> \u2014 opcionalmente a\u00f1ade un retraso (por ejemplo, 5 minutos) para dar tiempo a los revisores.<\/li>\n<li><strong>Deployment branches<\/strong> \u2014 restringe \u00fanicamente a <code>main<\/code>.<\/li>\n<\/ol>\n<p>Ahora a\u00f1ade tu <code>DEPLOY_TOKEN<\/code> como secreto dentro de este environment, no a nivel de repositorio.<\/p>\n<h3>Paso 2 \u2014 Referenciar el Environment en Tu Workflow<\/h3>\n<pre><code>jobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: production\n    permissions:\n      contents: read\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Deploy\n        run: .\/deploy.sh\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>La declaraci\u00f3n <code>environment: production<\/code> significa que este job se pausar\u00e1 y esperar\u00e1 a que un revisor lo apruebe antes de que cualquier step se ejecute. El secreto <code>DEPLOY_TOKEN<\/code> solo est\u00e1 disponible dentro de este environment \u2014 no puede ser accedido por otros jobs o workflows que no declaren este environment.<\/p>\n<h3>Paso 3 \u2014 Entender el Comportamiento con Forks<\/h3>\n<p>Los secretos <strong>no<\/strong> est\u00e1n disponibles para workflows disparados por eventos <code>pull_request<\/code> desde forks. Esta es una frontera de seguridad cr\u00edtica. Si creas un workflow que depende de secretos durante las verificaciones de PR, fallar\u00e1 para contribuidores externos:<\/p>\n<pre><code># Este step fallar\u00e1 para PRs basados en forks porque DEPLOY_TOKEN est\u00e1 vac\u00edo\n- name: Authenticated API call\n  run: |\n    curl -H \"Authorization: Bearer $DEPLOY_TOKEN\" https:\/\/api.example.com\/health\n  env:\n    DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>Esto es <strong>por dise\u00f1o<\/strong> \u2014 evita que forks maliciosos exfiltren tus secretos.<\/p>\n<h3>Paso 4 \u2014 El Peligro de pull_request_target<\/h3>\n<p>El trigger <code>pull_request_target<\/code> se ejecuta en el contexto del repositorio <strong>base<\/strong>, lo que significa que <em>s\u00ed<\/em> tiene acceso a los secretos. Esto es extremadamente peligroso si tambi\u00e9n haces checkout del c\u00f3digo head del PR:<\/p>\n<pre><code># PELIGROSO \u2014 NO HAGAS ESTO\non:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}  # Checks out UNTRUSTED code\n      - run: npm install  # Executes attacker-controlled code with access to secrets\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>Un atacante puede modificar <code>package.json<\/code> para incluir un script <code>postinstall<\/code> que exfiltre <code>DEPLOY_TOKEN<\/code>. Nunca combines <code>pull_request_target<\/code> con un checkout del head del PR a menos que hayas validado y aislado expl\u00edcitamente el c\u00f3digo.<\/p>\n<p><strong>Alternativa segura:<\/strong> Usa el trigger est\u00e1ndar <code>pull_request<\/code> para workflows de build y test. Reserva <code>pull_request_target<\/code> \u00fanicamente para workflows de etiquetado o comentarios que nunca ejecuten c\u00f3digo del PR.<\/p>\n<h3>Resumen de Mejores Pr\u00e1cticas<\/h3>\n<ul>\n<li>Almacena secretos sensibles en environments, no a nivel de repositorio.<\/li>\n<li>A\u00f1ade required reviewers y restricciones de branch a cada environment que contenga credenciales de producci\u00f3n.<\/li>\n<li>Usa el trigger <code>pull_request<\/code> para CI. Evita <code>pull_request_target<\/code> a menos que comprendas completamente las implicaciones de confianza.<\/li>\n<li>Dise\u00f1a workflows de forma que los jobs que necesitan secretos est\u00e9n separados de los jobs que ejecutan c\u00f3digo no confiable.<\/li>\n<\/ul>\n<h2>Ejercicio 4: Hardening Adicional<\/h2>\n<h3>Prevenir Ejecuciones Duplicadas con Concurrency<\/h3>\n<p>Sin una pol\u00edtica de concurrency, hacer push de m\u00faltiples commits en r\u00e1pida sucesi\u00f3n genera m\u00faltiples ejecuciones del workflow que desperdician recursos y pueden causar condiciones de carrera durante el deployment. A\u00f1ade un bloque <code>concurrency<\/code> a nivel de workflow:<\/p>\n<pre><code>concurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n<\/code><\/pre>\n<p>Esto cancela cualquier ejecuci\u00f3n en progreso para el mismo workflow y branch cuando se hace push de un nuevo commit.<\/p>\n<h3>Establecer L\u00edmites de Timeout<\/h3>\n<p>Un job colgado puede consumir minutos de runner indefinidamente. Siempre establece un timeout expl\u00edcito:<\/p>\n<pre><code>jobs:\n  build:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n<\/code><\/pre>\n<p>Elige un valor que d\u00e9 a tu build suficiente margen pero prevenga procesos descontrolados. Para la mayor\u00eda de builds de Node.js o Go, 10 a 20 minutos es generoso.<\/p>\n<h3>Restringir los Triggers del Workflow<\/h3>\n<p>Evita triggers sin restricciones que se disparen en cada branch:<\/p>\n<pre><code># Demasiado amplio \u2014 se ejecuta en cada push a cada branch\non:\n  push:\n<\/code><\/pre>\n<p>En su lugar, delimita los triggers a los branches que importan:<\/p>\n<pre><code>on:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n<\/code><\/pre>\n<p>Esto reduce las ejecuciones innecesarias y limita la superficie de ataque para ataques de inyecci\u00f3n basados en branches.<\/p>\n<h3>Ejecuci\u00f3n Condicional para Steps Sensibles<\/h3>\n<p>Usa condiciones <code>if:<\/code> para prevenir que steps sensibles se ejecuten en contextos donde no deber\u00edan:<\/p>\n<pre><code>- name: Deploy to production\n  if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n  run: .\/deploy.sh\n  env:\n    DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<p>Esto asegura que el step de deploy solo se ejecute en pushes a <code>main<\/code>, nunca en pull requests u otros branches, incluso si el job en s\u00ed fue disparado.<\/p>\n<h2>El Workflow Final Endurecido<\/h2>\n<p>A continuaci\u00f3n se muestra el workflow endurecido completo junto al original. Cada mejora de seguridad est\u00e1 anotada con un comentario.<\/p>\n<h3>Original (Inseguro)<\/h3>\n<pre><code>name: Build\n\non:\n  push:\n  pull_request_target:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: build-output\n          path: .\n<\/code><\/pre>\n<h3>Endurecido<\/h3>\n<pre><code>name: Build\n\n# HARDENED: Scoped triggers \u2014 only main branch, safe PR trigger\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\n# HARDENED: Restrictive default permissions for all jobs\npermissions:\n  contents: read\n\n# HARDENED: Cancel duplicate runs\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    # HARDENED: Explicit timeout\n    timeout-minutes: 15\n    # HARDENED: Per-job permissions (least privilege)\n    permissions:\n      contents: read\n      actions: read\n    steps:\n      # HARDENED: All actions pinned by SHA\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - uses: actions\/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n        with:\n          node-version: 20\n      - run: npm install\n      - run: npm test\n        # HARDENED: No secrets exposed in the build\/test job\n      - uses: actions\/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n        with:\n          name: build-output\n          path: dist\/\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    # HARDENED: Only runs on push to main\n    if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n    # HARDENED: Secrets gated behind environment with required reviewers\n    environment: production\n    timeout-minutes: 10\n    permissions:\n      contents: read\n    steps:\n      - uses: actions\/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Deploy\n        run: .\/deploy.sh\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}\n<\/code><\/pre>\n<h2>Rompi\u00e9ndolo (Fallo Intencional)<\/h2>\n<p>Para consolidar tu comprensi\u00f3n, rompe deliberadamente el workflow endurecido y observa las consecuencias.<\/p>\n<h3>Prueba 1 \u2014 Eliminar el Bloque de Permissions<\/h3>\n<p>Elimina la clave <code>permissions:<\/code> a nivel superior y los permisos por job. Haz push y ejecuta el workflow. Seguir\u00e1 teniendo \u00e9xito, pero si inspeccionas el step de setup del job, ver\u00e1s que el token ahora tiene acceso de <strong>lectura y escritura<\/strong> a cada scope. Un step comprometido podr\u00eda hacer push de c\u00f3digo, eliminar branches o modificar releases.<\/p>\n<h3>Prueba 2 \u2014 Usar una Action Sin Fijar<\/h3>\n<p>Cambia una action de vuelta a una referencia por tag:<\/p>\n<pre><code>- uses: actions\/checkout@v4\n<\/code><\/pre>\n<p>El workflow seguir\u00e1 ejecut\u00e1ndose. Pero si el tag <code>v4<\/code> alguna vez se mueve a un commit malicioso, tu workflow ejecutar\u00e1 ese c\u00f3digo sin advertencia. No hay rastro de auditor\u00eda \u2014 el tag simplemente resuelve a un SHA diferente. Fija de nuevo al SHA despu\u00e9s de esta prueba.<\/p>\n<h3>Prueba 3 \u2014 Acceder a Secretos de Producci\u00f3n desde un PR<\/h3>\n<p>Crea un feature branch y abre un pull request. El job <code>deploy<\/code> no se ejecutar\u00e1 debido a la condici\u00f3n <code>if:<\/code>. Incluso si eliminas la condici\u00f3n, el secreto <code>DEPLOY_TOKEN<\/code> del environment est\u00e1 protegido por el environment <code>production<\/code>, que restringe el deployment al branch <code>main<\/code> y requiere aprobaci\u00f3n de un revisor. El valor del secreto estar\u00e1 vac\u00edo en el contexto del PR.<\/p>\n<p>Este es exactamente el comportamiento que deseas \u2014 los secretos nunca est\u00e1n disponibles en contextos no confiables.<\/p>\n<h2>Limpieza<\/h2>\n<p>Cuando hayas terminado el lab, elimina el repositorio de prueba para evitar acumular desorden en tu cuenta:<\/p>\n<pre><code>gh repo delete gha-hardening-lab --yes<\/code><\/pre>\n<p>Si usaste un fork de un proyecto existente, puedes restablecerlo en su lugar:<\/p>\n<pre><code>git checkout main\ngit reset --hard origin\/main\ngit push --force\n<\/code><\/pre>\n<h2>Conclusiones Clave<\/h2>\n<ul>\n<li><strong>Siempre declara un bloque <code>permissions<\/code>.<\/strong> Establece un valor por defecto restrictivo a nivel de workflow y otorga scopes adicionales por job solo seg\u00fan sea necesario.<\/li>\n<li><strong>Fija cada action de terceros por su SHA completo.<\/strong> Los tags son mutables y pueden ser redirigidos silenciosamente a c\u00f3digo malicioso.<\/li>\n<li><strong>Usa Dependabot o Renovate<\/strong> para mantener los SHAs fijados actualizados autom\u00e1ticamente.<\/li>\n<li><strong>Almacena secretos sensibles en environments<\/strong> con required reviewers y restricciones de branch \u2014 nunca a nivel de repositorio.<\/li>\n<li><strong>Usa <code>pull_request<\/code>, no <code>pull_request_target<\/code><\/strong>, para workflows que construyan o prueben c\u00f3digo de PRs. El trigger <code>pull_request_target<\/code> otorga acceso a secretos a c\u00f3digo potencialmente no confiable.<\/li>\n<li><strong>A\u00f1ade <code>concurrency<\/code>, <code>timeout-minutes<\/code> y triggers delimitados por branch<\/strong> para reducir el desperdicio de recursos y reducir la superficie de ataque.<\/li>\n<\/ul>\n<h2>Pr\u00f3ximos Pasos<\/h2>\n<p>Contin\u00faa construyendo tu conocimiento de seguridad CI\/CD con estas gu\u00edas relacionadas:<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/ci-cd-execution-models-trust-assumptions-security-guide\/\">Modelos de Ejecuci\u00f3n CI\/CD y Suposiciones de Confianza<\/a> \u2014 Comprende c\u00f3mo diferentes plataformas de CI\/CD modelan la confianza y d\u00f3nde se rompen las fronteras.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/es\/sin-categoria\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">Separaci\u00f3n de Responsabilidades y M\u00ednimo Privilegio en Pipelines CI\/CD<\/a> \u2014 Dise\u00f1a pipelines donde ning\u00fan actor o credencial individual tenga m\u00e1s acceso del necesario.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Descripci\u00f3n General GitHub Actions se ha convertido en la plataforma de CI\/CD m\u00e1s ampliamente adoptada tanto para software de c\u00f3digo abierto como comercial. Esa popularidad la convierte en la superficie de ataque n\u00famero uno en el panorama CI\/CD. Los workflows mal configurados filtran secretos de forma rutinaria, otorgan permisos excesivos e incorporan c\u00f3digo de terceros &#8230; <a title=\"Lab: Hardening de Workflows GitHub Actions \u2014 Permisos, Pinning y Secretos\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-hardening-github-actions-workflows-permissions-pinning-secrets\/\" aria-label=\"Leer m\u00e1s sobre Lab: Hardening de Workflows GitHub Actions \u2014 Permisos, Pinning y Secretos\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,56],"tags":[],"post_folder":[],"class_list":["post-660","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-github-actions"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/660","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/comments?post=660"}],"version-history":[{"count":1,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/660\/revisions"}],"predecessor-version":[{"id":679,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/660\/revisions\/679"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=660"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=660"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=660"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=660"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}