Beaucoup d'équipes accrochent un self-hosted GitHub Runner sur Cloud Mac ou Mac mini et pensent que « pas de queue, xcodebuild tourne » suffit — l'article queue et TCO couvre cette couche.
Le vrai piège est la couche suivante : workspace partagé. DerivedData commun, caches globaux, un PAT pour CI et Agent — acceptable quand seuls les mainteneurs touchent les workflows ; avec fork PRs, post steps malveillants et Claude Code / OpenHands modifiant .github/workflows, un self-hosted runner sans isolation est exposé : les secrets du job A restent lisibles dans le job B.
Le plancher industrie 2026 s'aligne ici : one job, one workspace — répertoire exclusif par job, nettoyage à la fin, plus rotation des tokens. C'est Cloud Mac AI Stack · L1 partie 3 (prérequis : ① moteur d'exécution · ② queue et arbitrages) : d'où vient le piège CI/CD, puis un runbook copier-coller. Index série à § série L1.
Avant de lire · série L1 et entrée Stack
Fondation L0 : acheter vs louer Cloud Mac · déplacer le poste AI vers le cloud
Série L1 (lire dans l'ordre) : ① moteur d'exécution → ② queue et TCO → ③ cet article · sécurité CI/CD et one job, one workspace
Souvent dans le même stack (L3–L5) : workflow Claude Code · setup MCP · planification parallèle Ollama · pipeline OpenClaw
Réponse directe
Les self-hosted runner sans isolation workspace laissent le CI/CD exposé ; la baseline 2026 est one job, one workspace + rotation des tokens.
- Origine des pièges : résidus fichiers inter-jobs, empoisonnement cache global, PAT longue durée sur disque
- Baseline secteur : répertoire isolé par job, cleanup
if: always()+ prune hôte - Même stack que MCP moindre privilège et OpenHands : L4 gouverne les tokens Agent ; L1 gouverne les limites disque Fact
Comparer : ancienne config vs baseline 2026 · où se cachent les pièges
En passant de macos-latest hosted à self-hosted, beaucoup ne gardent que « pas de queue » (voir L1 ②) et n'héritent pas du « disque neuf par job » hosted. Le tableau et diagrammes ci-dessous marquent ce qui semblait « pratique » et paraît « exposé » aujourd'hui.
Convenance d'hier = exposition d'aujourd'hui — sept lignes en un coup d'œil
Ancienne pratique courante Baseline industrie 2026 Conséquence du piège
| Dimension | Ancienne pratique courante (vers 2024) | Baseline industrie 2026 | Conséquence du piège |
|---|---|---|---|
| Répertoire de travail job | Plusieurs jobs partagent _work, run dirs jamais supprimés |
one job, one workspace, vidé à la fin du job | Job B lit le .env du job A, scripts implantés |
| Cache build | DerivedData partagé / clés actions/cache globales |
Clés cache scope repo + prune périodique | Empoisonnement cache ; fork PR scanne le cache global |
| Identifiants | Un PAT pour CI + Agent ; secrets tournés mais disque non effacé | Tokens CI / MCP séparés ; rotation 30–90 j + wipe workspace | Copies PAT sur disque restent valides |
| Matériel de signature | Keychain temp dans $HOME, pas de delete post |
Keychain scope job, détruit avec if: always() |
Job suivant ou step malveillant exporte les certs |
| Qui édite les workflows | Par défaut : 2–3 mainteneurs seulement | Mainteneurs + fork PRs + Agent éditant CI | Surface d'attaque passe des humains aux processus semi-autonomes |
| Segmentation runner | Un Mac pour tous repos, toutes PRs | Prod / staging / fork utilisent labels différents | Workflow low-trust touche l'env de signature prod |
| Mythe hosted vs self-hosted | « self-hosted = ma machine, optimiser la vitesse » | self-hosted = vous tracez la frontière sécurité (VM hosted auto-isole) | On pensait self-hosted plus sûr ; en fait plus exposé |
Pourquoi tolérer l'ancienne façon ? Repo privé, pas de fork PRs, CI quotidien interne seulement — disque partagé économisait 3–8 minutes. Pourquoi changer en 2026 ? Le même Cloud Mac fait souvent tourner Agent, MCP et jobs multi-repos — toute vieille habitude transforme la commodité en incident sécurité.
Même self-hosted runner (même utilisateur macOS) Job A · signature nightly │ ├─► décompresse *.mobileprovision, keychain temp ├─► écrit ~/Library/Developer/Xcode/DerivedData ← semblait « vitesse » └─► PAT dans ~/.netrc (script paresseux) ← semblait « pratique » │ │ non nettoyé · résidus _work et cache ▼ Job B · fork PR « fix doc » en journée │ └─► post step scanne _work / DerivedData / .netrc ← piège : lit résidus job A Résultat : self-hosted runner exposé (hosted macos-latest jette toute la VM par job)
Job A ──► workspace A (_work/.../run-id) ──► cleanup if: always() ──► ✓ Job B ──► workspace B (répertoire frais) ──► cleanup if: always() ──► ✓ │ ├─ cache sensible : clés cache préfixées repo, ou cron prune ├─ PAT : tokens CI et MCP séparés, rotation planifiée └─ jobs high-trust / low-trust : labels runner différents (second Cloud Mac si besoin) Résultat : couche Fact auditable ; aligné avec « Claude Code produit Diff, Runner produit Fact »
En bref · avant vs maintenant
- Ancien piège : traiter self-hosted comme « un serveur plus rapide » et tout partager
- Baseline 2026 : traiter self-hosted comme « un environnement d'exécution à désinfecter »
- Plus grand mythe : migrer depuis
macos-latestet déplacer le CPU, pas l'isolation
Où commence l'exposition : trois pièges sécurité CI/CD courants sur self-hosted runner
Hosted macos-latest obtient un disque frais par job, donc beaucoup d'équipes au premier self-hosting supposent « ma machine, optimiser le confort ». Le vrai coût du self-hosted : vous tracez la frontière sécurité — la facture cachée au-delà de « pas de queue » dans L1 partie 2.
Les trois mauvaises configs ci-dessous reviennent souvent sur les déploiements Cloud Mac ; en toucher deux, adoptez one job, one workspace immédiatement.
Pourquoi 2026 pousse one job, one workspace partout
Pas parce que GitHub a publié une nouvelle règle — la surface d'attaque a grandi : workflows lancés par mainteneurs, fork PRs, scripts supply-chain et AI Agents sur le même hôte. Trois raisons pour lesquelles « disque partagé pour la vitesse » ne tient plus en 2026.
1. Contamination inter-jobs : secrets job A encore lisibles dans job B
Le _work self-hosted ne se désinfecte pas automatiquement entre jobs. Certs de signature décompressés en job A, .env de scripts, .netrc de post steps — job B peut les lire via chemins relatifs ou symlinks. En 2026 la surface est plus large : Claude Code et OpenHands peuvent éditer .github/workflows sur la même machine — vous auditez pas seulement le diff mais ce qui reste sur disque quand le CI change.
2. Empoisonnement cache global : DerivedData / cache npm comme porte dérobée partagée
Partager ~/Library/Developer/Xcode/DerivedData ou des clés actions/cache larges peut marcher en repo interne fermé ; dès les fork PRs, un post malveillant peut scanner le cache global — les VM hosted sont détruites, self-hosted sans cleanup est une surface d'attaque persistante. Échec iOS classique : job signature nightly et PR « fix doc » en journée sur le même runner.
3. PAT longue durée sur disque : tourner les secrets dans l'UI ne suffit pas
Dans beaucoup de stacks Cloud Mac, un PAT GitHub sert à la fois l'accès repo MCP et le push d'artefacts Runner. Si l'Agent l'expose, le CI tombe aussi. Tourner les secrets dans l'UI GitHub sans effacer le workspace, c'est changer le cylindre mais laisser les copies sur disque.
Rôles Stack · la couche Fact ne peut pas tourner exposée
Slogan série (de ouverture L1) : Claude Code produit Diff ; GitHub Runner produit Fact. De beaux diffs ne servent à rien si Fact tourne vert dans un workspace sale. L4 MCP moindre privilège gouverne les tokens Agent ; L1 ici gouverne disque et limites job — les deux couches, pas l'un ou l'autre.
Aligner le modèle : ce que GitHub Actions laisse sur un runner
Beaucoup assimilent « workspace » à l'arbre git checkout — ce n'est qu'une partie. Après un job macOS, le disque peut encore contenir :
| Chemin / objet | Contenu typique | Risque si non nettoyé |
|---|---|---|
_work/<repo>/<run-id>/ |
checkout, artefacts build, sortie tests | job suivant lit fichiers générés, scripts malveillants hors source |
~/.npm, ~/Library/Caches |
caches dépendances et outils | empoisonnement cache, confusion dépendances cross-repo |
DerivedData, .swiftpm |
cache build Xcode / Swift | fuite symboles, ancienne config signature embarquée |
Keychain temp, *.mobileprovision |
matériel signature | risque élevé : job suivant ou step malveillant exporte certs |
fichiers injection env, .netrc |
credentials écrits par scripts CI | PAT en clair persiste |
Les runner hosted jettent tout le disque en fin de job ; self-hosted non. Sur un stack Agent c'est pire : fichiers session Claude Code, poids Ollama et _work Runner peuvent partager un home utilisateur (planification même machine dans L2 planification parallèle) — one job, one workspace signifie : au départ supposer l'environnement sale par Agent ou autre job, et à la fin ne garder que les couches qui doivent traverser les jobs (souvent aucune).
Baseline secteur en pratique : ce que signifie one job, one workspace
Quatre couches, du plus simple au plus dur :
- Isolation répertoire : chaque job utilise son
RUNNER_TEMP/ run dir ; interdire scripts écrivant dans/tmp/sharedou dossiers « équipe partagés » hors repo. - Limite processus : un processus runner peut exécuter jobs en série, mais ne doit pas partager état global non nettoyé entre jobs (ex. clés API exportées dans
~/.zshrc). - Limite credentials : keychains temp signature supprimés en
postjob ; secrets uniquement via env, jamais disque — si disque requis, chemin dans run dir et supprimé avec le job. - Limite ops : repos à haut risque ont runners dédiés workflows de confiance seulement (séparation labels), physiquement à part des runners fork PR — sur Cloud Mac cela veut dire un second nœud, pas parier qu'un Mac ne verra jamais échouer les scripts de cleanup.
Lien avec les runner ephemeral
Les ephemeral self-hosted runner GitHub Enterprise quittent après chaque job et repartent frais — automatisant one job, one workspace. Avec un runner persistant (courant sur Cloud Mac), effet similaire via scripts + conventions workflow.
Rotation tokens : pourquoi effacer les répertoires ne suffit pas
Le cleanup workspace corrige les résidus fichiers ; la rotation tokens assure que les copies expireront même si exfiltrées. Tourner au moins trois types de credentials :
- Token enregistrement runner : retirer et ré-enregistrer le runner (ou tourner selon politique org) pour qu'une inscription obsolète sur vieilles machines ne soit pas abusée.
- GitHub PAT / App CI : scopes minimaux (lecture repo vs écriture packages), gérés séparément de la politique PAT MCP — éviter un token pour Agent + CI.
- Signature Apple et clés API tierces : credentials courte durée ou injection par job depuis secrets ; ne jamais écrire dans plist home runner.
Pas de cadence miracle : repo privé, pas de fork PRs, workflows éditables par mainteneurs seulement — 90 jours suffisent souvent ; avec contributeurs ouverts ou Agent soumettant workflows auto, raccourcir à 30 jours et après incident immédiatement retirer et ré-enregistrer le runner.
Runbook : intégrer one job, one workspace au workflow
Collez ces snippets dans pipelines existants pour rendre la baseline auditable. Suppose runner macOS self-hosted avec layout _work par défaut. Si le même Cloud Mac fait aussi tourner Claude Code, préférez un utilisateur macOS dédié pour Runner. Câblage MCP : guide setup.
# extrait .github/workflows/ios-ci.yml jobs: build: runs-on: [self-hosted, macos, cloud-mac] steps: - uses: actions/checkout@v4 - name: Build iOS run: xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' build - name: Scrub workspace (always) if: always() run: | rm -rf "$RUNNER_TEMP"/* rm -rf "$GITHUB_WORKSPACE"/build security delete-keychain ci_temp.keychain-db 2>/dev/null || true
#!/usr/bin/env bash # /usr/local/bin/runner-prune-work.sh · cron quotidien 03:00 set -euo pipefail WORK_ROOT="${HOME}/actions-runner/_work" # Supprimer run dirs plus vieux que 48h (mtime) find "$WORK_ROOT" -mindepth 3 -maxdepth 3 -type d -mtime +2 -exec rm -rf {} + # Optionnel : prune entrées DerivedData plus vieilles que 7 jours find ~/Library/Developer/Xcode/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true
Vérification : dans deux jobs consécutifs, afficher ls -la "$GITHUB_WORKSPACE/.." et chemins cache clés ; confirmer que le second job ne voit pas les marqueurs du premier (ex. touch /tmp/job-marker-$GITHUB_RUN_ID en post et vérifier résidus).
Mauvaise configuration courante
Utiliser actions/cache pour mettre en cache des binaires tiers non signés sous clé cache globale sans scope repo — cela construit une couche partagée inter-jobs et inter-repos sur le runner. Soit resserrer clés cache et branches, soit inclure le répertoire cache dans les scripts prune.
Co-déploiement Cloud Mac : limite sécurité Agent + Runner
Stack Cloud Mac AI typique : Claude Code (Diff) + Runner (Fact) + Ollama optionnel sur un hôte. Économise temps queue et git pull, mais partager répertoires globaux non nettoyables signifie CI exposé entraîne les sessions Agent dans le blast radius :
- Séparation utilisateurs : Runner sous user
runner, Agent sous user développeur ; ne jamais mélangerANTHROPIC_API_KEYet clés signature dans un~/.zshrc. - workspace Agent ≠ workspace CI : répertoire projet Claude Code ne doit pas pointer vers
_workRunner ; patches Agent passent par git, pas écritures directes aux arbres cache CI. - Contention mémoire ≠ disque partagé : mémoire Ollama vs Runner est planification parallèle ; Swap élevé n'excuse pas de continuer à partager DerivedData.
- IP sortante et labels : runners joignant staging interne ne doivent pas aussi prendre fork PRs ; workflows soumis par Agent touchent d'abord labels bas privilège, runner prod après promotion humaine.
Quand reporter (et quand agir maintenant)
| Scénario | Reporter ? | Notes |
|---|---|---|
| Monorepo privé, 2–3 mainteneurs seulement, pas de fork PRs | Court terme oui | Faire quand même prune manuel mensuel + rotation tokens trimestrielle |
| Repo open source exécute Actions sur PRs externes | Non | Runner dédié ou retour au macOS hosted |
| Claude Code / OpenHands / MCP écrit dans le repo | Non | Par défaut one job, one workspace ; interdire cache sensible partagé |
| Certs signature déchiffrés en CI | Non | Keychain scope job + delete post requis |
Checklist pré-lancement (imprimable)
- Chaque job a un step cleanup
if: always()ou prune hôte équivalent - Keychain temp / fichiers signature n'atterrissent pas dans chemins
$HOMEpermanents - Repos high-risk et workflows low-trust utilisent labels runner différents
- Token enregistrement runner et PAT CI ont calendrier rotation (30–90 jours suggérés)
- Première PR workflow d'un nouveau contributeur a review humaine, pas hit direct runner prod
- Agent et CI utilisent PAT / App différents — pas un seul token pour MCP et Runner
- Recoupement L1 partie 2 : ② corrige « lent » ; ③ corrige « self-hosted exposé »
Série L1 · comment les couches Stack se connectent
Cet article clôt la ligne sécurité L1 (couche Fact) : pourquoi Runner existe → si self-hosted vaut le coup → comment atterrir la baseline 2026 one job, one workspace. Lire le tableau dans l'ordre ; vertical vers L0, horizontal vers L3–L5.
| Partie | Sujet | Statut |
|---|---|---|
| ① · Moteur d'exécution | Pourquoi Runner est L1 Cloud Mac AI Stack (Diff → Fact) | Publié |
| ② · Queue et TCO | Temps queue macOS CI · self-hosted vs macos-latest | Publié |
| ③ · cet article | Sécurité self-hosted runner · baseline one job, one workspace | Publié |
| ④ · Pipeline OpenClaw | Runner exécute steps · OpenClaw orchestre triggers et reçus (extension L1) | Publié |
Liens verticaux Stack (une entrée par couche) :
- L0 · fondation : Mac mini vs Cloud Mac · poste AI cloud
- L2 · inférence : inférence privée Ollama · planification parallèle avec Runner
- L3 · Diff : workflow Claude Code sur Cloud Mac · CodeGraph et edits manqués
- L4 · contexte : hub triple-connect MCP · exposition moindre privilège
- L5 · workflow : plateforme Agent OpenHands
Après la trilogie L1, si Agent et CI partagent un Cloud Mac, la couche suivante est souvent décisions couche L3 Diff (pourquoi Claude Code remplace un IDE traditionnel — différent de vs Cursor et l'article workstation) — puis la carte bout-en-bout L6 (prévue).
FAQ
one job, one workspace ralentit-il le CI ?
Les cold starts ralentissent — c'est pourquoi les équipes partageaient le disque. L'équilibre 2026 : supprimer seulement artefacts sensibles et dirs par job ; cache renouvelable avec clés préfixées repo, pas DerivedData global jamais pruné.
Quand un self-hosted runner est-il « exposé » ?
Quand plusieurs jobs/repos partagent matériel signature, .netrc ou large cache global dans un home utilisateur sans cleanup if: always() — surtout avec fork PRs ou workflows édités par Agent.
MCP moindre privilège seul peut-il corriger disque runner partagé ?
Non. MCP moindre privilège gouverne appels outils Agent ; Runner doit gouverner fichiers sur disque. Workflows fork malveillants contournent MCP et peuvent scanner résidus _work.
Existe-t-il un switch GitHub en un clic ?
Les runner hosted approchent « un clic » ; self-hosted nécessite post steps workflow, cron hôte et mode ephemeral optionnel. Aucun flag actions/checkout unique ne remplace toute la frontière.
Comment cela se sépare-t-il de l'orchestration OpenClaw ?
OpenClaw gère ordre des triggers et reçus ; Runner exécute les steps. L'isolation vit dans workflow et hôte — ne pas supposer qu'OpenClaw efface le disque (voir L1 ④ · pipeline OpenClaw).
Par où commencer la série L1 ?
Suggéré : ① moteur d'exécution → ② queue → ③ cet article. Table complète à § série L1.
Trilogie L1 terminée · couche Diff suivante
Couche Fact sécurisée — place à Claude Code
L1 répond où le CI exécute Fact. Ensuite souvent L3 : workflow Claude Code sur Cloud Mac et logique remplacement IDE (différent du deep dive vs Cursor).
Lire le workflow Claude Code