Módulo 1: Claude Code en GitHub Actions

Secrets Management con GitHub Secrets

Secrets Management con GitHub Secrets

Descripción

Esta cápsula es la más importante para producción de toda la guía. Nada de lo que construyas en CI/CD vale la pena si tu API key queda expuesta. La cápsula 02 te dio un workflow funcional usando ${{ secrets.ANTHROPIC_API_KEY }}; aquí aprendes qué hay detrás de esa referencia, dónde y cómo se configura, los tres niveles de scope (repo, organización, environment), y qué hacer cuando algo sale mal.

Al terminar, vas a poder configurar GitHub Secrets en el nivel correcto según el caso de uso, vas a saber qué nunca debe ir en un secret, vas a tener un protocolo claro de qué hacer si una key se filtra, y vas a entender por qué hardcodear "solo para probar" es la causa #1 de incidentes de seguridad en CI/CD.


El Modelo Mental: Tres Niveles de Scope

GitHub Secrets se configuran en uno de tres scopes. Cada uno tiene un caso de uso distinto.

┌──────────────────────────────────────────────────┐
│  ORGANIZATION (nivel más alto)                    │
│  Settings → Secrets → Actions                     │
│  → Disponibles en TODOS los repos de la org       │
│  → Útil para: keys compartidas (Anthropic prod,   │
│    Sentry DSN, etc.)                              │
│  → Restricción opcional: solo a repos específicos │
│                                                   │
│   ┌────────────────────────────────────────┐     │
│   │  REPOSITORY                              │   │
│   │  Settings → Secrets → Actions            │   │
│   │  → Disponibles en TODOS los workflows    │   │
│   │    de ESTE repo                          │   │
│   │  → Útil para: keys específicas del repo  │   │
│   │  (DB credentials de un proyecto, etc.)   │   │
│   │                                           │   │
│   │   ┌──────────────────────────────────┐   │   │
│   │   │  ENVIRONMENT                      │   │   │
│   │   │  Settings → Environments → New    │   │   │
│   │   │  → Disponibles solo en jobs que   │   │   │
│   │   │    referencian ese environment    │   │   │
│   │   │  → Útil para: distinguir staging  │   │   │
│   │   │    vs producción, approval gates  │   │   │
│   │   └──────────────────────────────────┘   │   │
│   └────────────────────────────────────────┘     │
└──────────────────────────────────────────────────┘

La regla de oro: usa el scope más restrictivo que cubra tu caso. Si solo lo necesita un repo, ponlo en repo. Si solo en deploys a producción, ponlo en environment "production".


Configurando un Secret a Nivel Repositorio

Es el caso más común y el que vas a usar en el workflow del módulo.

Pasos:

  1. En tu repositorio de GitHub: Settings → Secrets and variables → Actions
  2. Click en New repository secret
  3. Name: ANTHROPIC_API_KEY (mayúsculas, exacto a como lo referencias en YAML)
  4. Value: tu API key (sk-ant-...)
  5. Click Add secret

Cómo se referencia en YAML:

- name: Run Claude Code analysis
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  run: python .github/scripts/analyze_pr.py

La sintaxis ${{ secrets.NAME }} es la forma exacta. GitHub reemplaza ese placeholder en runtime con el valor real, enmascarándolo en los logs (verás *** en lugar del valor).

Verificar que el enmascaramiento funciona

Agrega temporalmente este step para confirmar:

- name: Verify masking (TEMPORAL)
  run: echo "Key disponible (enmascarada en logs):" "$ANTHROPIC_API_KEY"
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

En los logs vas a ver: Key disponible (enmascarada en logs): ***. Si ves la key real en lugar de ***, algo está mal configurado — elimina el step y revisa el secret.

Después de verificar, borra ese step. No dejes prints de secrets en producción, aunque estén enmascarados — un cambio futuro al runner podría exponerlos.


Cuándo Usar Organization Secrets

Si tu organización tiene varios repositorios que usan Claude Code (típico en empresas), repetir el secret en cada repo es duplicación. Peor: si rotas la key, tienes que cambiarla en N lugares.

Solución: secret a nivel organización.

Pasos:

  1. En la organización: Settings → Secrets and variables → Actions
  2. New organization secret
  3. Name: ANTHROPIC_API_KEY
  4. Value: la key
  5. Repository access: elige entre:
    • Public repositories — disponible en todos los repos públicos
    • Private repositories — disponible en todos los privados (típicamente lo que quieres)
    • Selected repositories — solo a los que elijas explícitamente (más seguro)

Recomendación: "Selected repositories" siempre que sea factible. Si una key debe llegar solo a 5 repos, no la expongas a 50.

Override en repo específico

Si configuraste el secret a nivel org pero un repo necesita una key distinta (ej. cuenta de testing), puedes definir el secret con el mismo nombre en ese repo. El secret de repo gana sobre el de organización en ese workflow específico.


Cuándo Usar Environment Secrets

Environments son el scope más fino. Útiles cuando:

  • Distingues entre staging y producción. El workflow puede usar la API key de staging para deploys de prueba y la de producción para deploys reales — sin riesgo de cruce.
  • Quieres requerir approval humano antes de un deploy. Environments pueden tener "required reviewers" configurados.
  • Quieres restringir secrets a ramas específicas. Solo main puede leer secrets del environment "production", por ejemplo.

Pasos:

  1. Settings → Environments → New environment
  2. Nombre: production (o staging, etc.)
  3. Configurar:
    • Required reviewers: lista de personas que deben aprobar deploys
    • Deployment branches: restringir a main o ramas específicas
  4. Add secret dentro del environment
  5. Name: ANTHROPIC_API_KEY, Value: la key

Cómo se referencia en YAML:

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # ← clave para acceder a los secrets del env
    steps:
      - name: Deploy
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: ./scripts/deploy.sh

Sin la línea environment: production, el job no tiene acceso a los secrets de ese environment.


Variables vs Secrets

GitHub Actions distingue entre variables (no sensibles) y secrets (sensibles). Ambos se configuran en la misma UI pero en pestañas distintas.

TipoCaso de uso¿Visible en logs?¿Editable después?
VariableNombre de modelo, URL de API base, flags✅ Sí✅ Sí
SecretAPI keys, passwords, tokens❌ No (***)Solo el valor (no el nombre)

Regla simple:

  • ¿Si alguien la ve, es problema? → Secret
  • ¿Si alguien la ve, no pasa nada? → Variable

Ejemplo:

env:
  CLAUDE_MODEL: ${{ vars.CLAUDE_MODEL }}              # variable
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # secret

vars.CLAUDE_MODEL (no secrets) es la sintaxis para variables.


Qué NUNCA Debe Ir en un Secret

Los secrets son para credenciales. Hay cosas que parecen sensibles pero no van en secrets:

Cosa¿Va en secret?Dónde va
API key de Anthropic✅ SíSecret
Database password✅ SíSecret
OAuth client secret✅ SíSecret
OAuth client ID⚠️ VariableVariable (no es secreto)
Nombre del modelo (ej. claude-haiku-4-5)❌ NoVariable o hardcoded
URL pública de un service❌ NoVariable o hardcoded
Información personal (PII)❌ NoNunca pasarla a CI
Datos de clientes reales❌ NoNunca pasarlos a CI

Caso especial: PII y datos de clientes no deben tocar el CI. Si tu test necesita datos reales, está mal diseñado — usa fixtures sintéticos. Pasar datos reales por logs (aunque sean enmascarados) es un riesgo regulatorio (GDPR, HIPAA, LGPD).


Protocolo de Incidente: API Key Filtrada

Tú eres humano. Tarde o temprano, alguien del equipo va a commitear una key por accidente. El protocolo importa más que evitarlo perfectamente.

Si descubres una key en un commit (tuyo o de alguien más):

PASO 1 — REVOCAR INMEDIATAMENTE (5 minutos)
→ Anthropic console → API Keys → tu key
→ Click "Revoke"
→ La key deja de funcionar en cualquier llamada futura
→ ESTO ES LO PRIMERO. No esperes a "limpiar el git".

PASO 2 — GENERAR KEY NUEVA (5 minutos)
→ Anthropic console → "Create Key"
→ Configurar permisos mínimos necesarios

PASO 3 — ACTUALIZAR EL SECRET (5 minutos)
→ GitHub Secrets → ANTHROPIC_API_KEY → "Update"
→ Pegar la key nueva
→ Cualquier workflow que corra después usa la key nueva

PASO 4 — LIMPIAR EL HISTORIAL (opcional, complejo)
→ Si la key estuvo en git aunque sea 1 minuto, está
  archivada para siempre en el historial
→ git filter-repo o BFG Repo-Cleaner pueden reescribir
  el historial, pero requieren coordinación con el equipo
  (force push, todos los devs tienen que re-clonar)
→ Para repos públicos, considera el commit "perdido"
  (alguien pudo haberlo clonado entre el commit y la
  limpieza)
→ Para repos privados, vale el esfuerzo si la key
  era crítica

PASO 5 — POST-MORTEM (1 hora)
→ ¿Cómo pasó? (ej. "tenía la key en mi .env y agregué
  .env por accidente")
→ ¿Qué controles agregamos? (pre-commit hooks que
  detectan secrets, .gitignore reforzado, CI que
  detecta secrets en cada push)

Lo importante: no borrar el commit y rezar. Revocar primero, limpiar después. Si ya revocaste, la key del commit es inútil aunque alguien la encuentre.


Detección Automática de Secrets en CI

GitHub tiene secret scanning activado por defecto en repos públicos: detecta patrones conocidos (sk-ant-..., AKIA..., glpat-...) y notifica al owner. En repos privados es feature pago (Advanced Security).

Alternativa gratuita: pre-commit hooks con gitleaks o detect-secrets.

Setup con gitleaks (recomendado)

# Instalar gitleaks
brew install gitleaks  # macOS
# o: docker pull zricethezav/gitleaks

# Crear pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
gitleaks protect --staged --verbose
EOF
chmod +x .git/hooks/pre-commit

Antes de cada commit, gitleaks escanea los archivos staged buscando patrones de secrets. Si detecta uno, bloquea el commit y te muestra qué encontró.

Para CI, agregar como step:

- name: Detect secrets
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Trampas Comunes en Secrets Management

Error 1: "Lo configuro como secret pero también lo printeo para debug"

Síntoma: Aunque está configurado como secret, el equipo lo ve en logs porque algún script hace print(api_key).

Por qué pasa: Para debug "rápido", alguien agrega un print. GitHub enmascara ${{ secrets.X }} automáticamente, pero si el script imprime el valor en runtime, el enmascaramiento a veces no funciona (el matching es por strings exactos).

Cómo corregir: Nunca printear secrets, ni siquiera "para debug". Si necesitas verificar que un secret está disponible, printea solo "ANTHROPIC_API_KEY está configurado: True/False" (no el valor).

Error 2: Reusar la misma key en development y production

Síntoma: El equipo usa una sola key para todo. Si un dev junior la filtra, el incidente afecta producción.

Por qué pasa: "Tenemos una sola cuenta, una sola key, ¿para qué dos?". Pero la blast radius de un incidente se reduce drásticamente si están separadas.

Cómo corregir: Crear keys separadas en Anthropic console: una para dev/CI tests, otra para production. Configurarlas en environments distintos. Si dev se filtra, prod no se afecta.

Error 3: Configurar el secret a nivel organization "por conveniencia"

Síntoma: Todos los repos de la org tienen acceso a la key de producción, aunque solo 3 la necesitan.

Por qué pasa: Setear el secret a nivel org una sola vez es más cómodo. Pero expone la key a todos los workflows de todos los repos.

Cómo corregir: Usar "Selected repositories" en el secret de organización, o ponerlo solo a nivel repo en los pocos que lo necesitan.

Error 4: Forks y pull requests externos

Síntoma: Un contribuidor externo abre un PR. El workflow corre con sus modificaciones y... no tiene acceso al secret. El job falla.

Por qué pasa: GitHub deliberadamente no expone secrets a workflows que corren desde forks (sería trivial robarlos). Es el comportamiento correcto.

Cómo corregir: Diseñar el workflow asumiendo que PRs externos no tienen acceso a secrets. Para repos open-source, considerar correr el análisis después del merge (en push a main) o requerir que un maintainer aprobe la ejecución del workflow para PRs externos.

Error 5: Rotar el secret pero olvidar actualizar todos los workflows

Síntoma: Rotaste la key, configuraste la nueva en ANTHROPIC_API_KEY. Pero el workflow deploy.yml referenciaba ANTHROPIC_KEY (sin underscore) — ahora falla.

Por qué pasa: Inconsistencia en nombres de secrets entre workflows. Cada uno usa una variación.

Cómo corregir: Auditar todos los workflows después de rotar. Mantener un nombre consistente en todo el repo (ANTHROPIC_API_KEY siempre, no a veces ANTHROPIC_KEY).


Diagnóstico: Verifica Tu Setup

Pregunta 1: ¿Tu API key está en algún archivo del repositorio?

Busca: git grep -i "sk-ant" y git log --all --full-history -- "*.py" "*.yml" "*.yaml" | grep -i "sk-ant". Si aparece algo, revoca ahora mismo y rota.

Pregunta 2: ¿Tu workflow funciona pero el secret está bien configurado?

Si el script falla con "API key missing" pero ${{ secrets.ANTHROPIC_API_KEY }} está en el YAML, revisa: (1) el nombre coincide exactamente entre YAML y Settings, (2) el secret está a nivel del repo correcto (si es fork, no está disponible).

Pregunta 3: ¿Tienes keys separadas para dev/test y producción?

Si no, haz el cambio antes de seguir. Lleva 10 minutos y reduce blast radius dramáticamente.

Pregunta 4: ¿Tienes un protocolo claro para "qué hacer si una key se filtra"?

Si no, escríbelo ahora. La presión de un incidente real no es momento de inventar el proceso. Anclalo en .github/SECURITY.md o un runbook.

Pregunta 5: ¿Tu CI detecta secrets en commits antes de aceptarlos?

Si no, configura gitleaks o detect-secrets. Es 30 minutos de setup y previene el caso "comiteamos por accidente y no nos damos cuenta hasta que es tarde".


Ejercicios

Ejercicio 1: Configurar secret y verificar enmascaramiento (Fácil)

Configura ANTHROPIC_API_KEY en tu repo. Agrega un step temporal que printea el valor. Verifica que en los logs aparece ***. Elimina el step.

Ejercicio 2: Configurar environment con approval gate (Medio)

Crea un environment production con required reviewers. Modifica el workflow de la cápsula 02 para que use ese environment. Abre un PR y mergea — el workflow debería esperar approval antes de continuar.

Ver solución
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      # ... resto del workflow

En Settings → Environments → New environment "production" → Required reviewers: agregar tu usuario. Cuando el job alcance este step, GitHub pausa y espera aprobación.

Ejercicio 3: Implementar secret detection con gitleaks (Medio)

Configura gitleaks como pre-commit hook. Intenta comitear un archivo con una key falsa (sk-ant-test123...) y verifica que el commit se bloquea.


Resumen

  • Tres niveles de scope: organization, repository, environment — usa el más restrictivo aplicable
  • Variables vs secrets: todo lo sensible en secret; lo demás en variable
  • Nunca printear secrets, ni "para debug"
  • Keys separadas entre dev/test y production reducen blast radius
  • Protocolo de incidente: revocar primero, limpiar después
  • Detección automática: gitleaks o secret scanning para evitar el "lo comiteamos sin darnos cuenta"
  • Forks no tienen acceso a secrets — diseñar el workflow contemplándolo

Próxima cápsula: 04 — Parsear output y generar artefactos. Tu workflow ya se ejecuta de forma segura, pero el output sigue en logs que nadie lee. La cápsula 04 te enseña a llevarlo al PR mismo: comments, annotations, artifacts descargables.


Recursos Adicionales

  1. GitHub Encrypted Secrets — Doc oficial de secrets
  2. GitHub Environments — Setup de environments
  3. gitleaks — Detector de secrets para git
  4. detect-secrets — Alternativa de Yelp
  5. GitHub Secret Scanning — Feature nativa de GitHub
  6. Anthropic API Key Best Practices — Guía oficial de Anthropic
  7. BFG Repo-Cleaner — Para limpiar el historial cuando hay incidente