Vous déployez Claude Code pour une équipe de plateforme. Vous configurez la fédération d’identité de charge de travail de Anthropic afin que chaque session échange une assertion OIDC attestée contre un jeton de courte durée. L’examen de sécurité est propre : pas de clés statiques, d’identité par session, de jetons qui expirent. Le lendemain matin, un ingénieur ajoute export ANTHROPIC_API_KEY=sk-ant-... à son profil shell car un script en a besoin. La fédération est désormais ignorée silencieusement pour chaque session exécutée par l’ingénieur. Aucune erreur, aucun avertissement, aucune entrée de journal. La clé statique est prioritaire et le chemin attesté n’est jamais invoqué.
Il ne s’agit pas d’une condition de concurrence théorique. Il s’agit de l’ordre documenté de résolution des informations d’identification de Anthropic, et c’est la manière la plus courante par laquelle les déploiements de fédération échouent silencieusement.
Le problème avec les clés statiques
La méthode par défaut pour authentifier Claude Code est une clé API statique : une chaîne sk-ant- définie comme ANTHROPIC_API_KEY. Cela fonctionne et possède trois propriétés qui entrent en conflit avec la gestion des identités d’entreprise.
Pas d’expiration, pas de signal de rotation. Une clé statique est valide jusqu’à ce que quelqu’un la révoque. Il n’y a pas de durée de vie intégrée, pas de rappel de rotation, pas de mécanisme forçant la réauthentification. Une clé émise lors d’une preuve de concept peut toujours authentifier les charges de travail de production des mois plus tard.
Identité partagée. Chaque session utilisant la même clé s’authentifie en tant que même principal. La piste d’audit indique quel espace de travail a été utilisé, mais elle ne peut pas distinguer quel ingénieur, quelle machine ou quel automatisme a exécuté une requête donnée. L’attribution par session est structurellement impossible.
Précédence silencieuse sur la fédération. C’est le pistolet à pied. La résolution des informations d’identification de Anthropic place la clé statique (ANTHROPIC_API_KEY, niveau 2) au-dessus du chemin de fédération (niveau 4). Lorsque les deux existent dans le même environnement, la clé statique l’emporte. L’échange de fédération n’est jamais tenté. Aucune erreur n’est générée. La charge de travail s’exécute correctement sous l’identité de la clé statique, et chaque hypothèse de gouvernance fondée sur la fédération (identité à l’échelle de la session, assertions attestées, expiration du jeton) est invalidée en silence.
Même une variable vide (ANTHROPIC_API_KEY="") occupe son emplacement de priorité. L’authentification échouera, mais cela empêchera toujours le runtime d’atteindre le chemin de la fédération. Le mode d’échec est une “erreur d’authentification”, et non un “passage à la fédération”.
Fonctionnement de la fédération d’identité de charge de travail
WIF remplace la clé statique par un échange : une assertion vérifiée entre, un jeton éphémère en sort. L’assertion est un JWT : soit un JWT-SVID provenant d’un fournisseur d’identité SPIFFE, soit un jeton OIDC standard provenant de n’importe quel émetteur auquel l’organisation Anthropic fait confiance.
L’échange fait suite à RFC 7523 (subvention au porteur JWT). La charge de travail présente son assertion au point de terminaison du jeton de Anthropic avec trois identifiants : à quelle règle de fédération correspondre (fdrl_), quel compte de service agir (svac_) et à quelle organisation appartient l’échange. Dans le code, le cœur de l’échange est un POST avec un corps JSON :
type exchangeRequest struct {
GrantType string `json:"grant_type"` // "urn:ietf:params:oauth:grant-type:jwt-bearer"
Assertion string `json:"assertion"` // le JWT-SVID ou le jeton OIDC vérifié
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 réponse est une réponse de jeton RFC 6749. Le jeton émis porte le préfixe sk-ant-oat (OAT = OAuth Access Token), une portée déclarée (workspace:developer pour un accès API non administratif complet, ou org:manage_tunnels pour la gestion du tunnel MCP) et une durée de vie comprise entre 60 secondes et 24 heures.
Il n’y a pas de jeton d’actualisation. Lorsque le jeton émis expire, la charge de travail doit présenter à nouveau son assertion et réexécuter l’échange. C’est délibéré : l’assertion elle-même est attestée (vérifiée en amont par l’API de charge de travail SPIFFE ou le fournisseur OIDC), donc chaque rééchange est une ré-attestation. Un jeton compromis n’est utile que pour sa durée de vie restante, et il n’existe aucun chemin d’actualisation qu’un attaquant puisse exploiter pour l’étendre.
Le résultat est une identité par session. Chaque session Claude Code échange sa propre assertion, reçoit son propre jeton de courte durée et s’authentifie en tant que principal distinct. La piste d’audit enregistre quel compte de service a agi, lié à quelle règle de fédération et étendu à quel espace de travail.
Détection du pistolet à pied statique-key-shadows-federation
Déployer WIF ne suffit pas. Vous devez également détecter quand quelque chose dans l’environnement le contourne silencieusement. Le connecteur inspecte l’environnement d’exécution pour détecter la présence d’informations d’identification statiques (ANTHROPIC_API_KEY ou ANTHROPIC_AUTH_TOKEN) et vérifie si la fédération est utilisée simultanément (soit via des règles de fédération déclarées, soit via le signal ANTHROPIC_IDENTITY_TOKEN_FILE). Lorsque les deux conditions sont vraies, cela génère un résultat de gouvernance de haute gravité.
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 identifie la variable qui prend le pas — jamais sa valeur
}, true
}
Le hachage détaillé de la découverte enregistre quelle variable statique est présente et quelle fédération signale qu’elle masque, sans jamais intégrer la valeur de la clé. Pas même une forme masquée. Le hachage est stable d’une exécution à l’autre, de sorte que le moteur de gouvernance le déduplique et qu’un SIEM peut l’interroger au cas par cas.
Une clé statique sans fédération utilisée n’est qu’une clé statique : le connecteur ne la signale pas. Le résultat se déclenche uniquement lorsque les deux existent, car il s’agit de la configuration spécifique dans laquelle l’opérateur pense que la fédération est active, alors qu’elle ne l’est pas.
La boucle de réconciliation : déclarée vs réelle
La détection du pistolet à clé statique ne représente que la moitié du tableau. L’autre moitié vérifie que la configuration de la fédération elle-même n’a pas dérivé. Le connecteur conserve une ligne de base déclarée (les règles de fédération que l’opérateur déclare explicitement comme régies) et la compare à l’état actif de la configuration WIF de l’organisation Anthropic.
L’état actif provient de trois points de terminaison de l’API d’administration WIF :
GET /v1/organizations/service_accounts: les comptes de service (svac_) ciblés par les règles de fédération.GET /v1/organizations/federation_issuers: les émetteurs OIDC/SPIFFE (fdis_) auxquels l’organisation fait confianceGET /v1/organizations/federation_rules: les règles (fdrl_) qui lient les émetteurs aux comptes de service
Ces points de terminaison nécessitent un jeton de porteur org:admin OAuth – un identifiant distinct de la clé API d’administration sk-ant-admin que la liste utilise. L’API d’administration WIF rejette explicitement les clés de l’API d’administration, c’est pourquoi le connecteur utilise un client authentifié distinct pour la réconciliation.
La différence entre déclaré et live produit sept catégories de résultats :
| Cas de dérive | Ce que cela signifie | Gravité |
|---|---|---|
undeclared_live_rule | Une règle en direct que l’opérateur n’a jamais déclarée ni régie | Élevé |
declared_rule_not_live | Une règle déclarée qui n’existe plus en amont | Moyen |
scope_drift | La portée réelle a divergé de la portée déclarée | Moyen (Élevé si étendu à l’ensemble de l’organisation) |
lifetime_drift | La durée de vie du jeton actif s’écarte de celle déclarée | Moyen (Élevé si plus long que déclaré) |
over_broad_subject | Une règle dynamique sans réelle contrainte de sujet | Moyen |
orphan_rule | Une règle faisant référence à un émetteur ou à un compte de service manquant | Moyen |
orphan_issuer | Un émetteur référencé par aucune règle | Faible |
Deux cas passent automatiquement à une gravité élevée : une portée active qui s’est étendue à une portée à l’échelle de l’organisation ou de l’administrateur que l’opérateur n’a pas déclarée, et une durée de vie de jeton actif qui est plus longue que la ligne de base gouvernée. Les deux élargissent le rayon d’explosion au-delà de ce que l’opérateur a approuvé. La normalisation de la portée (couper, dédupliquer, trier) empêche les faux positifs dus aux espaces ou aux différences d’ordre.
Lorsqu’aucun jeton org:admin n’est configuré, la passe de réconciliation ne s’exécute tout simplement pas. Le connecteur fonctionne avec la ligne de base déclarée uniquement et est honnête quant à sa couverture : il ne fabrique jamais de liste en direct. Lorsque l’API en direct n’est pas accessible (erreur réseau, expiration du jeton), elle émet un seul résultat reconciliation_unavailable et continue : les autorisations de liste et la détection des armes à pied ne doivent pas être couplées à la santé du jeton org:admin.
Lecture en premier et données minimales
Le connecteur suit un contrat strict de minimisation des données. Chaque appel API est un GET. Il ne crée, ne met à jour ou ne supprime jamais un objet Anthropic. Il contient uniquement des métadonnées d’identité : identifiants, noms, e-mails, rôles, indices clés. Jamais un secret clé. Jamais une clé privée. Jamais un jeton frappé au repos.
Le jeton créé à partir de l’échange WIF est renvoyé à l’appelant et n’est jamais enregistré, conservé ou émis par le connecteur. Le seul enregistrement qui parvient au grand livre de gouvernance est la structure ExchangeAudit - qui ne contient délibérément aucun jeton :
type ExchangeAudit struct {
FederationRuleID string
OrganizationID string
ServiceAccountID string
WorkspaceID string
Scope string
TokenType string
ExpiresAt time.Time
}
La même minimisation s’applique à la réconciliation en direct. Lorsque le connecteur lit la configuration JWKS d’un émetteur de fédération, il réduit la réponse à deux booléens : le mode de découverte (discovery, explicit_url ou inline) et si un certificat d’autorité de certification personnalisé est épinglé. Le matériel JWK en ligne (clés publiques, mais volumineux et jamais nécessaire pour les décisions de gouvernance) n’est jamais décodé dans un champ stocké ou émis. Le certificat CA PEM est décodé uniquement pour dériver un indicateur de présence et est immédiatement rejeté.
Les conditions CEL sur les règles de fédération sont utilisées pour l’analyse de posture (l’expression CEL d’une règle fait partie de sa limite de sécurité), mais elles ne sont jamais évaluées. Le connecteur n’a aucune dépendance au moteur CEL – l’évaluation est une préoccupation distincte.
Ce que cela permet
Les clés statiques confondent authentification et identité. Chaque session est le même principal, chaque jeton vit éternellement et la présence d’une clé dans un fichier dot désactive silencieusement toute fédération que vous pensiez vous protéger.
WIF sépare les deux. L’authentification s’effectue via une assertion attestée qui prouve que la charge de travail est bien celle qu’elle prétend. L’identité est limitée à la session : un jeton de courte durée, un compte de service spécifique, un espace de travail spécifique, une portée déclarée. Lorsque le jeton expire, la charge de travail atteste à nouveau. Lorsque la configuration dérive, la boucle de réconciliation fait apparaître le gap. Lorsqu’une clé statique masque l’ensemble du mécanisme, le connecteur la détecte et la signale avant qu’elle ne devienne une découverte d’incident.
Le résultat est un modèle d’identité dans lequel les informations d’identification expirent, les sessions sont attribuables et les hypothèses de gouvernance sont continuellement vérifiées par rapport à l’état réel de l’organisation, et non seulement à l’état souhaité.
Le connecteur d’identité, l’échange WIF et la détection des armes à pied font partie du module de gouvernance des identités. Pour le modèle de sécurité plus large – le grand livre, la carte d’accès, l’architecture de collecte en lecture première – voir sécurité.