Jeton GitHub fuité : que faire et comment enquêter
Un PAT ou un jeton OAuth GitHub a fuité ? Révoquez-le, retrouvez son hashed_token dans le journal d'audit, cherchez IP, pays et clones inédits, puis délimitez.
En bref. Considérez que le jeton a servi tant que les logs ne prouvent pas le contraire. Révoquez-le (quiconque détient la valeur peut le faire via l'API de révocation d'identifiants). Calculez son empreinte (echo -n TOKEN | openssl dgst -sha256 -binary | base64) et cherchez hashed_token:"…" dans le journal d'audit. Exportez ensuite les événements Git, que les recherches dans l'interface et par l'API laissent de côté. Cherchez les nouveaux pays et nouvelles IP, les rafales de clones et tout ce que le jeton a créé : clés, workflows, applications, runners. Enfin, vérifiez l'absence d'infostealer sur la machine du propriétaire.
Les jetons d'accès personnels sont l'identifiant que les attaquants réutilisent le plus souvent contre GitHub. Un ghp_… de développeur oublié dans un fichier .env, un log de CI, un historique de shell copié-collé ou un butin d'infostealer donne à l'attaquant l'accès de ce développeur à toutes les organisations pour lesquelles le jeton est autorisé, sans mot de passe ni invite 2FA. Ce playbook couvre l'enquête, pas seulement le renouvellement.
Première heure : contenir sans détruire les preuves
- Exportez d'abord, révoquez ensuite, si vous pouvez faire les deux en quelques minutes. La révocation est elle-même journalisée, elle ne détruit donc rien. Mais une fois le jeton mort, l'attaquant peut basculer sur la persistance qu'il a installée, et vous voulez la photo « avant ». Si le jeton est activement exploité, révoquez immédiatement.
- Révoquez. Le propriétaire du jeton peut le supprimer dans Settings → Developer settings → Personal access tokens. Avec la valeur en clair, n'importe qui peut appeler le point d'accès de révocation, qui couvre les PAT classiques et à granularité fine, les jetons d'OAuth app et les jetons utilisateur de GitHub App. Dans les organisations sous SAML SSO, un propriétaire peut aussi révoquer l'autorisation SSO du jeton.
- Considérez que la source est toujours compromise. Si le jeton vient d'un poste de développeur, un infostealer a probablement aussi pris les cookies du navigateur, les clés SSH et les identifiants des CLI cloud. On réinstalle le poste avant de réémettre un jeton.
GitHub révoque déjà certains jetons pour vous. D'après sa documentation sur la révocation, un jeton OAuth, de GitHub App ou d'accès personnel valide poussé dans un dépôt public ou un gist public est révoqué automatiquement, tout comme un jeton OAuth ou d'accès personnel inutilisé depuis un an. Rien de tel pour un jeton fuité dans un dépôt privé, un log de build ou un site de paste.
Retrouver le jeton dans le journal d'audit
Les événements authentifiés par jeton portent trois champs, documentés dans Identifying audit log events performed by an access token :
| Champ | Signification |
|---|---|
hashed_token | SHA-256 de la valeur du jeton, encodé en base64 |
programmatic_access_type | par ex. « Personal access token (classic) », « Fine-grained personal access token », OAuth, GitHub App |
token_scopes | Scopes d'un jeton classique, par ex. repo, workflow, read:org |
Calculez l'empreinte de la valeur fuitée et cherchez-la :
echo -n "$LEAKED_TOKEN" | openssl dgst -sha256 -binary | base64
# then, in the audit log search box:
# hashed_token:"Xkr8WMW4...="
Deux réserves. Les recherches dans l'interface et par l'API excluent les événements Git : il faut les exporter pour voir les clones faits avec le jeton. Et si vous n'avez pas la valeur (seulement un soupçon), prenez le problème à l'envers. Sur Enterprise Cloud, l'export de l'inventaire des identifiants liste les valeurs hashed_token avec leurs propriétaires (sauf pour les PAT à granularité fine), ce qui permet de rattacher à une personne une empreinte inconnue vue dans le journal. L'entrée de glossaire hashed_token donne plus de détails.
Le jeton a-t-il servi à quelqu'un d'autre ?
Un jeton volé est rejoué en parallèle de son propriétaire légitime : on compare donc le jeton à lui-même.
- Nouveau pays pour le jeton. La règle
token-new-countryde l'analyseur (élevée) se déclenche quand unhashed_tokenapparaît depuis un pays jamais vu pour lui, une fois que le jeton a au moins 24 heures d'historique dans l'export. Les rejeux depuis un VPS à l'étranger correspondent à ce schéma. - Nouvelle IP pour le jeton.
token-new-ip(faible) est bruyante seule, car les portables changent de réseau. Elle compte quand elle partage un jeton avec un autre constat. - Changement d'user agent. Le
git/2.39.5 (Apple Git-154)d'un développeur qui côtoie soudaincurl/8.5.0oupython-requestssur la même empreinte. - Heure de la journée. Une activité à 2 h du matin dans le fuseau du propriétaire, plusieurs jours de suite, mérite une question.
- Volume. Une rafale de
git.clonesur de nombreux dépôts (voir l'exfiltration de dépôts).
Ces détections ont besoin de actor_ip et des données de pays. Si l'export n'a pas d'IP, activez l'affichage des IP, qui s'applique aussi aux événements existants, puis exportez à nouveau.
Qu'a fait le jeton ?
Listez tous les événements qui portent l'empreinte (dans l'analyseur, Entités → Tokens → Voir les événements) et rangez les actions en trois groupes :
| Groupe | Actions | Ce que cela signifie |
|---|---|---|
| Lecture / exfiltration | git.clone, git.fetch, repo.download_zip | Le code (et tous les secrets de son historique) est sorti |
| Écriture / altération | git.push, workflows.created_workflow_run sur une nouvelle branche, protected_branch.destroy | Du code ou des pipelines ont changé. Voir la fuite de secrets Actions |
| Persistance | public_key.create, hook.create, integration_installation.create, org.register_self_hosted_runner, personal_access_token.request_created | Des accès qui survivent à la révocation |
Les scopes du jeton fixent les limites. Un jeton classique avec repo et workflow peut pousser des fichiers de workflow ; avec admin:org, il peut modifier les paramètres de l'organisation. Un jeton à granularité fine n'atteint que les dépôts et permissions qui lui ont été accordés, et dans les organisations qui exigent une approbation, les événements personal_access_token.request_created et .access_granted montrent quand il a obtenu cet accès.
Supprimer ce que le jeton a laissé derrière lui
La révocation n'annule pas ce que l'attaquant a créé. Avant de clore l'incident :
- Supprimez les clés de déploiement et clés SSH inconnues (
public_key.create). Une clé de déploiement avec accès en écriture conserve le droit de pousser après toutes les réinitialisations de mot de passe et de jeton. - Retirez les webhooks, GitHub Apps, approbations d'OAuth apps et runners auto-hébergés ajoutés pendant la fenêtre.
- Supprimez les branches de l'attaquant et relisez
.github/workflowssur toutes les branches, pas seulement celle par défaut. - Renouvelez les secrets Actions lisibles par tout workflow que le jeton pouvait pousser, puis les clés cloud qui en font partie (voir le rebond vers le cloud).
- Cherchez d'autres jetons du même propriétaire utilisés depuis l'IP de l'attaquant : pivotez sur l'IP, pas seulement sur l'empreinte.
Éviter le prochain
- Définissez une politique de jetons d'accès personnels pour l'organisation : restreindre les jetons classiques, imposer une durée de vie maximale, exiger une approbation pour les jetons à granularité fine. L'analyseur signale l'affaiblissement de cette politique (
pat-policy-weakened). - Sur Enterprise Cloud, une liste d'IP autorisées s'applique aussi aux jetons d'accès personnels et aux clés SSH : un jeton volé ne fonctionne alors que depuis vos réseaux.
- Pour l'automatisation, préférez les GitHub Apps et leurs jetons d'installation à courte durée de vie aux PAT à longue durée de vie.
FAQ
GitHub révoque-t-il automatiquement les jetons fuités ?
GitHub révoque automatiquement un jeton OAuth, un jeton de GitHub App ou un jeton d'accès personnel valide poussé dans un dépôt public ou un gist public. Les jetons fuités ailleurs (dépôt privé, log de CI, messagerie, butin d'infostealer) ne sont pas révoqués pour vous.
Comment retrouver les événements d'un jeton dans le journal d'audit ?
Calculez son empreinte avec echo -n TOKEN | openssl dgst -sha256 -binary | base64, puis cherchez hashed_token:"VALEUR" dans le journal d'audit. Les recherches dans l'interface et par l'API excluent les événements Git : exportez-les pour vérifier les clones.
Révoquer le jeton suffit-il ?
Non. La révocation empêche les usages futurs, pas ce que l'attaquant a déjà fait ou installé : clés SSH et de déploiement, OAuth apps, workflows sur de nouvelles branches, runners et webhooks y survivent. Délimitez l'activité du jeton, supprimez la persistance et renouvelez les secrets que ses workflows pouvaient lire.