Ce qui se passe
L'équipe derrière Strix, un agent de test d'intrusion autonome, envisageait
d'utiliser Baseten comme fournisseur d'inférence. Avant de lui confier des
données, elle a pointé son agent sur *.baseten.co, sans identifiants ni accès
au code. Vingt-cinq minutes plus tard, l'agent tenait un jeton d'accès
personnel GitHub valide, avec les droits d'administration sur des dépôts
internes.
L'étape de construction qui avait enregistré ce jeton datait du 3 mars 2023. Il fonctionnait encore en juillet 2026.
La chaîne, étape par étape
Le détail qui fait la leçon est en bas à droite : le jeton n'était pas dans les
fichiers de l'image, mais dans sa configuration, au champ
history[].created_by qui enregistre la commande de chaque étape de build. Ce
fichier se télécharge avec l'image. Nettoyer un fichier de secrets dans une
couche ne sert donc à rien si l'historique en garde une copie.
Le motif en cause
Un build avait besoin de récupérer des dépendances privées depuis GitHub. Le jeton est passé en argument de build, et Docker a enregistré sa valeur :
ARG GITHUB_TOKEN
RUN GITHUB_TOKEN=${GITHUB_TOKEN} bash -c '\
if [[ "${GITHUB_TOKEN}" != "" ]]; then \
git config --global --add \
url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"; \
fi'Deux problèmes se cumulent : l'expansion de la variable dans la commande
enregistrée, et git config --global qui écrit l'URL authentifiée dans la
configuration Git de l'image. Docker
documente explicitement ce risque.
Ce que pouvait le jeton
Compte basetenbot, organisation basetenlabs, en-tête
X-OAuth-Scopes: repo, et sur les dépôts :
| Dépôt | Accès |
|---|---|
| dépôt du produit principal | admin: true, push: true |
| dépôt GitOps qui pilote les clusters | admin: true, push: true |
| tap Homebrew | admin: true, push: true |
| 4 dépôts privés, dont des dépôts par client | lecture / écriture |
Les chercheurs se sont arrêtés là : aucun clone du dépôt client, aucun push, aucune modification de configuration.
Chronologie de la divulgation
- 13 juillet 2026, 23 h 10 — signalement du jeton actif, du projet Harbor public et des permissions.
- 14 juillet, matin — Baseten rend le projet Harbor privé ; les chercheurs signalent que le jeton fonctionne toujours.
- 14 juillet, 16 h 34 — Baseten confirme la criticité, révoque le jeton et demande la suppression des images téléchargées.
- 17 juillet — les constats secondaires sont clos.
Comment s'y prendre chez soi
- Regarder ce qu'un anonyme peut tirer de votre registre, y compris les vieux tags et les projets oubliés.
- Lire l'historique de build, pas seulement les couches :
docker history --no-trunc <image>, ou inspecter les champshistory[].created_bydu blob de configuration. - Sortir les secrets des arguments de build, au profit d'un montage de secret BuildKit — et vérifier que la commande qui le consomme ne le réécrit pas dans l'image.
- Calibrer les droits : un jeton qui récupère une dépendance a besoin de la lire, pas d'administrer le dépôt du produit. Et lui donner une expiration.
À retenir
Un jeton dans une image publique reste exploitable tant qu'il n'est pas révoqué,
quelle que soit la correction apportée au Dockerfile : l'image a déjà été
téléchargée. Et si un agent a mis 25 minutes à remonter cette chaîne sans indice,
le délai d'exposition n'est plus une notion théorique.