Self-hosted runner sans isolation workspace : CI/CD exposé — pièges de sécurité

Baseline 2026 : one job, one workspace — standard du secteur

Cloud Mac AI Stack · L1  ·  2026.06.09  ·  ~11 min  ·  runbook · configs copier-coller

Isolation sécurité CI/CD self-hosted runner et baseline one job, one workspace

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

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.

À lire · comparaison centrale

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é.

Schéma · avant : fuites workspace partagé (inter-jobs)
  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)
Schéma · maintenant : one job, one workspace (baseline 2026)
  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-latest et 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.

3
pièges sécurité CI/CD courants
1
limite workspace niveau job
2026
année baseline

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 :

  1. Isolation répertoire : chaque job utilise son RUNNER_TEMP / run dir ; interdire scripts écrivant dans /tmp/shared ou dossiers « équipe partagés » hors repo.
  2. 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).
  3. Limite credentials : keychains temp signature supprimés en post job ; secrets uniquement via env, jamais disque — si disque requis, chemin dans run dir et supprimé avec le job.
  4. 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.

workflow · cleanup fin de job (exemple)
# 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élanger ANTHROPIC_API_KEY et clés signature dans un ~/.zshrc.
  • workspace Agent ≠ workspace CI : répertoire projet Claude Code ne doit pas pointer vers _work Runner ; 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 $HOME permanents
  • 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écutionPourquoi Runner est L1 Cloud Mac AI Stack (Diff → Fact)Publié
· Queue et TCOTemps queue macOS CI · self-hosted vs macos-latestPublié
· cet articleSécurité self-hosted runner · baseline one job, one workspacePublié
· Pipeline OpenClawRunner exécute steps · OpenClaw orchestre triggers et reçus (extension L1)Publié

Liens verticaux Stack (une entrée par couche) :

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
Cloud Mac Voir offres M4