Saltar al contenido

WIF

Credenciales efímeras para Claude Code con workload identity federation

Por Olivares AI 10 min de lectura

Despliegas Claude Code para un equipo de plataforma. Configuras la workload identity federation de Anthropic para que cada sesión intercambie una aserción OIDC atestada por un token de corta vida. La revisión de seguridad sale limpia: sin claves estáticas, identidad por sesión, tokens que expiran. A la mañana siguiente, un ingeniero añade export ANTHROPIC_API_KEY=sk-ant-... a su perfil de shell porque un script lo necesita. La federación queda anulada silenciosamente en cada sesión que ese ingeniero ejecuta. Sin error, sin warning, sin entrada de log. La clave estática toma precedencia y la ruta atestada nunca se invoca.

No es una condición de carrera teórica. Es el orden de resolución de credenciales documentado por Anthropic, y es la forma más común en la que los despliegues de federación fallan en silencio.

El problema de las claves estáticas

La forma por defecto de autenticar Claude Code es una API key estática: una cadena sk-ant- configurada como ANTHROPIC_API_KEY. Funciona, y tiene tres propiedades que chocan con la gestión de identidades en entornos corporativos.

Sin expiración, sin señal de rotación. Una clave estática es válida hasta que alguien la revoca. No tiene tiempo de vida integrado, ni recordatorio de rotación, ni mecanismo que fuerce una reautenticación. Una clave emitida durante una prueba de concepto puede seguir autenticando cargas de producción meses después.

Identidad compartida. Cada sesión que usa la misma clave se autentica como el mismo principal. La traza de auditoría muestra qué workspace se usó, pero no puede distinguir qué ingeniero, qué máquina o qué automatización ejecutó una petición concreta. La atribución por sesión es estructuralmente imposible.

Precedencia silenciosa sobre la federación. Este es el footgun. La resolución de credenciales de Anthropic sitúa la clave estática (ANTHROPIC_API_KEY, tier 2) por encima de la ruta de federación (tier 4). Cuando ambas existen en el mismo entorno, la clave estática gana. El intercambio de federación nunca se intenta. No se lanza ningún error. La carga se ejecuta con éxito bajo la identidad de la clave estática, y cada suposición de gobernanza construida sobre la federación — identidad con scope de sesión, aserciones atestadas, expiración de tokens — queda silenciosamente invalidada.

Incluso una variable vacía (ANTHROPIC_API_KEY="") ocupa su posición de precedencia. Fallará la autenticación, pero impedirá que el runtime alcance la ruta de federación. El modo de fallo es “error de autenticación”, no “caer hacia la federación”.

Cómo funciona workload identity federation

WIF sustituye la clave estática por un intercambio: entra una aserción verificada, sale un token de corta vida. La aserción es un JWT — bien un JWT-SVID de un proveedor de identidad SPIFFE, bien un token OIDC estándar de cualquier emisor en el que la organización de Anthropic confíe.

El intercambio sigue RFC 7523 (JWT bearer grant). La carga presenta su aserción al endpoint de tokens de Anthropic junto con tres identificadores: contra qué regla de federación hacer el match (fdrl_), como qué cuenta de servicio actuar (svac_) y a qué organización pertenece el intercambio. En código, el núcleo del intercambio es un POST con un body JSON:

type exchangeRequest struct {
    GrantType        string `json:"grant_type"`         // "urn:ietf:params:oauth:grant-type:jwt-bearer"
    Assertion        string `json:"assertion"`          // el JWT-SVID o token OIDC verificado
    FederationRuleID string `json:"federation_rule_id"` // fdrl_...
    OrganizationID   string `json:"organization_id"`
    ServiceAccountID string `json:"service_account_id"` // svac_...
    WorkspaceID      string `json:"workspace_id,omitempty"`
}

La respuesta es un token response RFC 6749. El token emitido lleva el prefijo sk-ant-oat (OAT = OAuth Access Token), un scope declarado (workspace:developer para acceso completo no administrativo a la API, u org:manage_tunnels para gestión de túneles MCP), y un tiempo de vida entre 60 segundos y 24 horas.

No hay refresh token. Cuando el token emitido expira, la carga debe volver a presentar su aserción y ejecutar el intercambio de nuevo. Esto es deliberado: la propia aserción está atestada (verificada aguas arriba por la workload API de SPIFFE o por el proveedor OIDC), de modo que cada re-intercambio es una re-atestación. Un token comprometido solo es útil durante el tiempo de vida que le quede, y no existe una ruta de refresh que un atacante pueda explotar para extenderlo.

El resultado es identidad por sesión. Cada sesión de Claude Code intercambia su propia aserción, recibe su propio token de corta vida y se autentica como un principal diferenciado. La traza de auditoría registra qué cuenta de servicio actuó, vinculada a qué regla de federación, con scope a qué workspace.

Detectar el footgun de clave-estática-anula-federación

Desplegar WIF no es suficiente. También necesitas detectar cuando algo en el entorno lo sortea silenciosamente. El conector inspecciona el entorno de ejecución en busca de la presencia de una credencial estática (ANTHROPIC_API_KEY o ANTHROPIC_AUTH_TOKEN) y comprueba si la federación está simultáneamente en uso (ya sea mediante reglas de federación declaradas o a través de la señal ANTHROPIC_IDENTITY_TOKEN_FILE). Cuando ambas condiciones se cumplen, emite un hallazgo de gobernanza de severidad alta.

func (s *Source) detectShadowing(at time.Time) (model.FindingReport, bool) {
    _, hasKey := s.envLookup(envAPIKey)
    _, hasAuth := s.envLookup(envAuthToken)
    if !hasKey && !hasAuth {
        return model.FindingReport{}, false
    }
    _, hasTokenFile := s.envLookup(envIdentityTokenFile)
    federationInUse := len(s.federation) > 0 || hasTokenFile
    if !federationInUse {
        return model.FindingReport{}, false
    }
    // ...
    return model.FindingReport{
        Kind:     "governance",
        Severity: model.SeverityHigh,
        Title:    "Static Anthropic key shadows Workload Identity Federation",
        // DetailHash huellea QUE variable anula — nunca el valor
    }, true
}

El hash del hallazgo registra qué variable estática está presente y qué señal de federación anula, sin incrustar jamás el valor de la clave. Ni siquiera una forma enmascarada. El hash es estable entre ejecuciones, de modo que el motor de gobernanza lo deduplica y un SIEM puede consultarlo por caso.

Una clave estática sin federación en uso es simplemente una clave estática — el conector no la señala. El hallazgo solo se dispara cuando ambas existen, porque esa es la configuración concreta en la que el operador cree que la federación está activa y no lo está.

El bucle de reconciliación: declarado vs. real

Detectar el footgun de la clave estática es la mitad del cuadro. La otra mitad es verificar que la propia configuración de federación no ha derivado. El conector mantiene una línea base declarada — las reglas de federación que el operador declara explícitamente como gobernadas — y la compara contra el estado en vivo de la configuración WIF de la organización de Anthropic.

El estado en vivo procede de tres endpoints del WIF Admin API:

  • GET /v1/organizations/service_accounts — las cuentas de servicio (svac_) a las que apuntan las reglas
  • GET /v1/organizations/federation_issuers — los emisores OIDC/SPIFFE (fdis_) en los que la organización confía
  • GET /v1/organizations/federation_rules — las reglas (fdrl_) que vinculan emisores a cuentas de servicio

Estos endpoints requieren un bearer token OAuth org:admin — una credencial distinta de la clave sk-ant-admin del Admin API que usan las lecturas del roster. El WIF Admin API rechaza explícitamente las Admin API keys, por lo que el conector usa un cliente autenticado separado para la reconciliación.

El diff entre declarado y en vivo produce siete categorías de hallazgo:

Caso de driftQué significaSeveridad
undeclared_live_ruleUna regla en vivo que el operador nunca declaró ni gobiernaAlta
declared_rule_not_liveUna regla declarada que ya no existe aguas arribaMedia
scope_driftEl scope en vivo divergió del scope declaradoMedia (Alta si se amplió a org-wide)
lifetime_driftEl tiempo de vida del token en vivo divergió del declaradoMedia (Alta si es más largo que el declarado)
over_broad_subjectUna regla en vivo sin restricción real de subjectMedia
orphan_ruleUna regla que referencia un emisor o cuenta de servicio inexistenteMedia
orphan_issuerUn emisor no referenciado por ninguna reglaBaja

Dos casos escalan a severidad alta automáticamente: un scope en vivo que se amplió a un scope org-wide o admin que el operador no declaró, y un tiempo de vida de token en vivo más largo que la línea base gobernada. Ambos amplían el radio de explosión más allá de lo que el operador aprobó. La normalización de scopes (recortar, deduplicar, ordenar) previene falsos positivos por diferencias de espacios u orden.

Cuando no se configura un token org:admin, la pasada de reconciliación simplemente no se ejecuta. El conector trabaja con la línea base solo-declarada y es honesto sobre su cobertura: nunca fabrica un roster en vivo. Cuando no se puede alcanzar la API en vivo (error de red, token expirado), emite un único hallazgo reconciliation_unavailable y continúa — los grants del roster y la detección del footgun no deben estar acoplados a la salud del token org:admin.

Read-first y datos mínimos

El conector sigue un contrato estricto de minimización de datos. Cada llamada a la API es un GET. Nunca crea, actualiza ni elimina un objeto de Anthropic. Transporta solo metadatos de identidad: IDs, nombres, emails, roles, indicios de clave. Nunca un secreto de clave. Nunca una clave privada. Nunca un token emitido en reposo.

El token emitido por el intercambio WIF se devuelve al llamante y nunca se registra, persiste ni emite por el conector. El único registro que llega al ledger de gobernanza es la estructura ExchangeAudit — que deliberadamente no contiene ningún token:

type ExchangeAudit struct {
    FederationRuleID string
    OrganizationID   string
    ServiceAccountID string
    WorkspaceID      string
    Scope            string
    TokenType        string
    ExpiresAt        time.Time
}

La misma minimización se aplica a la reconciliación en vivo. Cuando el conector lee la configuración JWKS de un emisor de federación, reduce la respuesta a dos booleanos: el modo de descubrimiento (discovery, explicit_url o inline) y si hay un certificado CA personalizado anclado. El material JWK inline — claves públicas, pero voluminosas y nunca necesarias para decisiones de gobernanza — nunca se decodifica en un campo almacenado o emitido. El PEM del certificado CA se decodifica solo para derivar un flag de presencia y se descarta inmediatamente.

Las condiciones CEL de las reglas de federación se transportan para análisis de postura (la expresión CEL de una regla forma parte de su frontera de seguridad), pero nunca se evalúan. El conector no tiene dependencia de motor CEL — la evaluación es una preocupación separada.

Lo que esto posibilita

Las claves estáticas mezclan autenticación con identidad. Cada sesión es el mismo principal, cada token vive para siempre, y la presencia de una clave en un dotfile anula silenciosamente cualquier federación que creías que te estaba protegiendo.

WIF las separa. La autenticación se produce a través de una aserción atestada que prueba que la carga es quien dice ser. La identidad tiene scope de sesión: un token de corta vida, una cuenta de servicio concreta, un workspace concreto, un scope declarado. Cuando el token expira, la carga se re-atesta. Cuando la configuración deriva, el bucle de reconciliación saca la divergencia a la superficie. Cuando una clave estática anula todo el mecanismo, el conector lo detecta y lo reporta antes de que se convierta en un hallazgo de incidente.

El resultado es un modelo de identidad donde las credenciales expiran, las sesiones son atribuibles y las suposiciones de gobernanza se verifican continuamente contra el estado real de la organización — no solo contra el estado que alguien pretendió.


El conector de identidad, el intercambio WIF y la detección del footgun forman parte del módulo de gobernanza de identidad. Para el modelo de seguridad más amplio — el ledger, el mapa de accesos, la arquitectura de recopilación read-first — consulta seguridad.

Artículos relacionados

Preguntas frecuentes

¿La clave estática tiene que contener una API key válida para anular la federación?

No. La precedencia de credenciales de Anthropic se basa en presencia, no en validez. Un ANTHROPIC_API_KEY="" vacío ocupa su posición de precedencia igual que una clave real: se sitúa por encima de los niveles de federación en el orden de resolución, así que el runtime nunca llega a la ruta atestada. El conector detecta esto comprobando si la variable de entorno está definida (os.LookupEnv), no si su valor es no vacío. Una clave vacía fallará la autenticación, pero anulará la federación con la misma eficacia que una clave real.

¿Qué ocurre si no tengo un token OAuth org:admin para la reconciliación en vivo?

El conector funciona sin él. Modela exactamente lo que el operador declara en la configuración de federación, emite el roster NHI gobernado y las aristas de grants permitidos a partir de esas declaraciones, y ejecuta la detección del footgun de clave estática. Simplemente omite la pasada de reconciliación en vivo, porque los endpoints del WIF Admin API rechazan todo lo que no sea un bearer token OAuth org:admin. Obtienes una línea base solo-declarada que es honesta sobre su cobertura: nunca fabrica un roster en vivo.

Mira a qué pueden llegar tus agentes

Olivares AI es la plataforma abierta y self-hosted para tu parque de IA. Despliégalo en tu propia infraestructura y obtén el mapa de acceso que tus equipos de seguridad y plataforma llevan pidiendo.