Self-hosted runner ohne workspace-Isolation sind offen — CI/CD-Sicherheitsfallen

2026-Baseline: one job, one workspace — jetzt Branchenstandard

Cloud Mac AI Stack · L1  ·  2026.06.09  ·  ~11 Min.  ·  runbook · Copy-Paste-Konfiguration

self-hosted runner CI/CD-Sicherheitsisolation und one job, one workspace-Baseline

Viele Teams hängen einen self-hosted GitHub Runner auf Cloud Mac oder Mac mini und denken, „keine Queue, xcodebuild läuft“ reicht — der Queue- und TCO-Artikel behandelt diese Schicht.

Die echte Falle ist die nächste Schicht: geteilter workspace. Gemeinsames DerivedData, globale Dependency-Caches, ein PAT für CI und Agent — solange nur Maintainer Workflows anfassen, spart das Minuten. Mit fork PRs, bösartigen post-Steps und Claude Code / OpenHands, die .github/workflows automatisch ändern, ist ein self-hosted runner ohne Isolation offen: Secrets aus Job A liest Job B noch.

Die 2026-Branchenuntergrenze liegt hier: one job, one workspace — exklusives Verzeichnis pro Job, Cleanup beim Exit, plus Token-Rotation. Dies ist Cloud Mac AI Stack · L1 Teil 3 (Voraussetzungen: ① Ausführungsengine · ② Queue und Trade-offs): woher die CI/CD-Falle kommt, dann ein Copy-Paste-runbook. Serienindex unter § L1-Serie.

Vor dem Lesen · L1-Serie und Stack-Einstieg

L0-Fundament: kaufen vs. mieten Cloud Mac · AI-Workstation in die Cloud

L1-Serie (Reihenfolge empfohlen): ① Ausführungsengine② Queue und TCO③ dieser Artikel · CI/CD-Sicherheit und one job, one workspace

Oft im gleichen Stack (L3–L5): Claude Code-Workflow · MCP-Setup · Ollama-Parallelplanung · OpenClaw-Pipeline

Vergleich: frühere Konfiguration vs. 2026-Baseline · wo Fallen lauern

Beim Wechsel von Hosted macos-latest zu self-hosted behalten viele Teams nur „keine Queue“ (L1 ②) und nicht die Hosted-„frische Platte pro Job“. Die Tabelle und Diagramme markieren, was früher „bequem“ und heute „offen“ wirkt.

Pflichtlektüre · Kernvergleich

Gestern Bequemlichkeit = heute Exposure — sieben Zeilen auf einen Blick

Frühere gängige Praxis 2026-Branchenbaseline Fallenfolge

Dimension Frühere gängige Praxis (ca. 2024) 2026-Branchenbaseline Fallenfolge
Job-Arbeitsverzeichnis Mehrere Jobs teilen _work, Run-Dirs nie gelöscht one job, one workspace, beim Job-Ende geleert Job B liest Job A's .env, eingeschleuste Skripte
Build-Cache Geteiltes DerivedData / globale actions/cache-Keys Repo-scoped cache keys + periodisches prune Cache-Vergiftung; fork PR scannt globalen Cache
Credentials Ein PAT für CI + Agent; Secrets rotiert, Platte nicht gewischt Getrennte CI / MCP-Tokens; 30–90-Tage-Rotation + workspace-Wipe PAT-Kopien auf Platte bleiben gültig
Signing-Material Temp-Keychain in $HOME, kein post-Delete Job-scoped Keychain, mit if: always() zerstört Nächster Job oder bösartiger Step exportiert Zertifikate
Wer Workflows editiert Standard: nur 2–3 Maintainer Maintainer + fork PRs + Agent editiert CI Angriffsfläche wächst von Menschen zu halbautonomen Prozessen
Runner-Segmentierung Ein Mac für alle Repos, alle PRs Prod / Staging / fork nutzen verschiedene labels Low-Trust-Workflow berührt Prod-Signing-Env
Hosted vs. self-hosted-Mythos „self-hosted = meine Maschine, Speed optimieren“ self-hosted = du ziehst die Sicherheitsgrenze (Hosted-VM isoliert automatisch) Angenommen self-hosted sei sicherer; tatsächlich exponierter

Warum tolerierte man die alte Art? Privates Repo, keine fork PRs, tägliches CI nur intern — geteilte Platte sparte 3–8 Minuten. Warum muss es 2026 anders? Derselbe Cloud Mac läuft oft Agent, MCP und Multi-Repo-Jobs — jede alte Gewohnheit macht Bequemlichkeit zum Sicherheitsvorfall.

Diagramm · früher: wo geteilter workspace leakt (job-übergreifend)
  Gleicher self-hosted runner (gleicher macOS-User)

  Job A · nightly Signing
       │
       ├─► *.mobileprovision entpacken, Temp-Keychain
       ├─► schreibt ~/Library/Developer/Xcode/DerivedData   ← fühlte sich nach „Speed“ an
       └─► PAT in ~/.netrc (faules Skript)                ← fühlte sich nach „Bequemlichkeit“ an
                │
                │  nicht bereinigt · _work- und Cache-Reste  Job B · tagsüber fork PR „Doc-Fix“
       │
       └─► post-Step scannt _work / DerivedData / .netrc  ← Falle: liest Job-A-Reste

       Ergebnis: self-hosted runner offen (Hosted macos-latest wirft ganze VM pro Job weg)
Diagramm · jetzt: one job, one workspace (2026-Baseline)
  Job A  ──►  workspace A (_work/.../run-id)  ──►  if: always() Cleanup  ──►  ✓
  Job B  ──►  workspace B (frisches Verzeichnis)    ──►  if: always() Cleanup  ──►  ✓
                │
                ├─ sensibler Cache: repo-präfixierte cache keys oder cron prune
                ├─ PAT: getrennte CI- und MCP-Tokens, geplante Rotation
                └─ High-Trust / Low-Trust-Jobs: verschiedene runner labels (zweiter Cloud Mac bei Bedarf)

       Ergebnis: auditierbare Fact-Schicht; passt zu „Claude Code produziert Diff, Runner produziert Fact“

Auf einen Blick · früher vs. jetzt

  • Alte Falle: self-hosted als „schnellerer Server“ behandelt und alles Geteilbare geteilt
  • 2026-Baseline: self-hosted als „Ausführungsumgebung, die du desinfizieren musst
  • Größter Mythos: Migration von macos-latest und CPU verschoben, Isolation nicht

Wo Offenheit beginnt: drei häufige CI/CD-Sicherheitsfallen auf self-hosted runnern

Hosted macos-latest bekommt pro Job eine frische Platte, daher nehmen viele Teams beim ersten self-hosting an, „meine Maschine, Bequemlichkeit optimieren“. Die echte Kosten von self-hosted: du ziehst die Sicherheitsgrenze — die versteckte Rechnung jenseits von „keine Queue“ in L1 Teil 2.

Die drei Fehlkonfigurationen unten tauchen wiederholt bei Cloud Mac-Deployments auf; trifft zwei zu, solltest du sofort one job, one workspace einführen.

Warum 2026 one job, one workspace überall fordert

Nicht weil GitHub eine neue Regel veröffentlicht hat — die Angriffsfläche wuchs: Workflows laufen von Maintainern, fork PRs, Supply-Chain-Skripten und AI-Agents auf demselben Host. Drei Gründe, warum „geteilte Platte für Speed“ 2026 nicht mehr trägt.

1. Job-übergreifende Kontamination: Job-A-Secrets in Job B noch lesbar

Self-hosted _work desinfiziert nicht automatisch zwischen Jobs. In Job A entpackte Signing-Zerts, .env von Skripten, .netrc von post-Steps — Job B kann sie per relativen Pfaden oder Symlinks lesen. 2026 ist die Fläche größer: Claude Code und OpenHands können .github/workflows auf derselben Maschine editieren — du prüfst nicht nur den Diff, sondern was noch auf der Platte liegt, wenn sich CI ändert.

2. Globale Cache-Vergiftung: DerivedData / npm-Cache als geteiltes Backdoor

Teilen von ~/Library/Developer/Xcode/DerivedData oder breiten actions/cache-Keys mag in einem geschlossenen internen Repo funktionieren; kommen fork PRs, kann ein bösartiger post-Step den globalen Cache scannen — Hosted-VMs werden zerstört, self-hosted ohne Cleanup ist persistente Angriffsfläche. Klassischer iOS-Fail: nightly-Signing-Job und tagsüber „Doc-Fix“-PR auf demselben runner.

3. Lang lebender PAT auf Platte: Secrets in der UI rotieren reicht nicht

In vielen Cloud Mac-Stacks dient ein GitHub PAT sowohl MCP-Repo-Zugriff als auch Runner-Artifact-Push. Leakt der Agent, fällt auch CI. Secrets in der GitHub-UI rotieren ohne workspace-Wipe ist wie Zylinder tauschen, aber Kopien auf der Platte liegen lassen.

Stack-Rollen · Fact-Schicht darf nicht offen laufen

Serien-Slogan (aus L1-Eröffner): Claude Code produziert Diff; GitHub Runner produziert Fact. Schöne Diffs nützen nichts, wenn Fact grün in einem schmutzigen workspace läuft. L4 MCP least privilege regelt Agent-Tokens; L1 hier regelt Disk- und Job-Grenzen — beide Schichten, nicht entweder/oder.

3
häufige CI/CD-Sicherheitsfallen
1
Job-Level-workspace-Grenze
2026
Baseline-Jahr

Modell angleichen: was GitHub Actions auf einem runner hinterlässt

Viele gleichen „workspace“ mit dem ausgecheckten Git-Baum — das ist nur ein Teil. Nach einem macOS-Job kann die Platte noch halten:

Pfad / Objekt Typischer Inhalt Risiko ohne Cleanup
_work/<repo>/<run-id>/ checkout, Build-Artefakte, Test-Output nächster Job liest generierte Dateien, bösartige Skripte außerhalb Source
~/.npm, ~/Library/Caches Dependency- und Tool-Caches Cache-Vergiftung, Cross-Repo-Dependency-Confusion
DerivedData, .swiftpm Xcode / Swift Build-Cache Symbol-Leakage, veraltete Signing-Config eingebettet
Temp-Keychain, *.mobileprovision Signing-Material hohes Risiko: nächster Job oder bösartiger Step exportiert Zerts
Env-Injection-Dateien, .netrc von CI-Skripten geschriebene Credentials Klartext-PAT bleibt

Hosted runner verwerfen die ganze Platte beim Job-Ende; self-hosted nicht. Auf einem Agent-Stack wird es schlimmer: Claude Code-Session-Dateien, Ollama-Gewichte und Runner _work teilen sich ein User-Home (Same-Machine-Scheduling in L2-Parallelplanung) — one job, one workspace heißt: beim Start annehmen, die Umgebung ist von Agent oder anderem Job schmutzig, und am Ende nur Schichten behalten, die Jobs übergreifen sollen (meist keine).

Branchenbaseline in der Praxis: was one job, one workspace bedeutet

Vier Schichten, leicht bis schwer:

  1. Verzeichnis-Isolation: jeder Job nutzt eigenes RUNNER_TEMP / Run-Verzeichnis; Skripte verbieten, nach /tmp/shared oder repo-externen „Team-Shared“-Ordnern zu schreiben.
  2. Prozess-Grenze: ein runner-Prozess darf Jobs seriell laufen, aber darf nicht ungereinigten globalen State zwischen Jobs teilen (z. B. API-Keys in ~/.zshrc exportiert).
  3. Credential-Grenze: Signing-Temp-Keychains im Job-post löschen; Secrets nur per Env-Vars, nie Platte — wenn Platte nötig, Pfad im Run-Dir und mit Job löschen.
  4. Ops-Grenze: High-Risk-Repos bekommen dedizierte runner nur für vertrauenswürdige Workflows (Label-Trennung), physisch getrennt von fork-PR-runnern — auf Cloud Mac meist zweiter Knoten, nicht auf einen Mac wetten, dass Cleanup-Skripte nie scheitern.

Bezug zu ephemeral runnern

GitHub Enterprise ephemeral self-hosted runner beenden nach jedem Job und starten frisch — automatisiert one job, one workspace. Bei persistentem runner (auf Cloud Mac üblich) ähnlichen Effekt mit Skripten + Workflow-Konventionen erreichen.

Token-Rotation: warum Verzeichnisse wischen nicht reicht

workspace-Cleanup behebt Dateireste; Token-Rotation sorgt dafür, dass kopierte Dateien ablaufen, selbst wenn exfiltriert. Mindestens drei Credential-Typen rotieren:

  • Runner-Registrierungs-Token: runner entfernen und neu registrieren (oder per Org-Policy rotieren), damit stale Registrierung auf alten Maschinen nicht missbraucht wird.
  • CI GitHub PAT / App: minimale Scopes (Repo lesen vs. Packages schreiben), getrennt von MCP PAT-Policy — ein Token für Agent + CI vermeiden.
  • Apple-Signing und Third-Party-API-Keys: kurzlebige Credentials oder pro Job aus Secrets injizieren; nie in runner-Home-plist schreiben.

Keine Silberkugel-Kadenz: privates Repo, keine fork PRs, Workflows nur von Maintainern editierbar — 90 Tage reichen oft; mit offenen Contributors oder Agent, der Workflows auto-submittet, auf 30 Tage verkürzen und nach jedem Vorfall sofort runner entfernen und neu registrieren.

Runbook: one job, one workspace in den Workflow backen

Diese Snippets in bestehende Pipelines einfügen, um die Baseline auditierbar zu machen. Annahme: macOS self-hosted runner mit Standard-_work-Layout. Läuft derselbe Cloud Mac auch Claude Code, bevorzugt dedizierter macOS-User für Runner. MCP-Verkabelung: Setup-Guide.

workflow · Job-End-Cleanup (Beispiel)
# .github/workflows/ios-ci.yml Snippet
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 · täglich 03:00 cron
set -euo pipefail
WORK_ROOT="${HOME}/actions-runner/_work"
# Run-Dirs älter als 48h löschen (mtime)
find "$WORK_ROOT" -mindepth 3 -maxdepth 3 -type d -mtime +2 -exec rm -rf {} +
# Optional: DerivedData-Einträge älter als 7 Tage prune
find ~/Library/Developer/Xcode/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true

Verifikation: in zwei aufeinanderfolgenden Jobs ls -la "$GITHUB_WORKSPACE/.." und wichtige Cache-Pfade ausgeben; bestätigen, dass der zweite Job keine Marker-Dateien vom ersten sieht (z. B. touch /tmp/job-marker-$GITHUB_RUN_ID im post und auf Reste prüfen).

Häufige Fehlkonfiguration

actions/cache nutzen, um unsignierte Third-Party-Binaries unter globalem cache key ohne Repo-Scope zu cachen — baut eine job- und repo-übergreifende Shared-Schicht auf dem runner. Entweder cache keys und Branches enger fassen oder Cache-Verzeichnis in prune-Skripte aufnehmen.

Cloud Mac Co-Deployment: Agent + Runner-Sicherheitsgrenze

Typischer Cloud Mac AI Stack: Claude Code (Diff) + Runner (Fact) + optional Ollama auf einem Host. Spart Queue-Zeit und git pull, aber geteilte, nicht bereinigbare globale Verzeichnisse bedeuten offenes CI zieht Agent-Sessions in den Blast Radius:

  • User-Trennung: Runner unter runner-User, Agent unter Developer-User; nie ANTHROPIC_API_KEY und Signing-Keys in einer ~/.zshrc mischen.
  • Agent workspace ≠ CI workspace: Claude Code-Projektverzeichnis darf nicht auf Runner _work zeigen; Agent-Patches gehen über git, nicht direkte Writes in CI-Cache-Bäume.
  • Memory-Contention ≠ geteilte Platte: Ollama vs. Runner Memory ist Parallelplanung; hoher Swap ist keine Ausrede, DerivedData weiter zu teilen.
  • Egress-IP und labels: runner, die internes Staging erreichen, dürfen nicht auch fork PRs nehmen; Agent-submittierte Workflows zuerst Low-Privilege-labels, Prod-runner erst nach menschlicher Promotion.

Wann aufschieben (und wann sofort handeln)

Szenario Aufschiebbar? Hinweise
Privates Monorepo, nur 2–3 Maintainer, keine fork PRs Kurzfristig ja Trotzdem monatliches manual prune + vierteljährliche Token-Rotation
Open-Source-Repo führt Actions auf externen PRs aus Nein Dedizierter runner oder zurück zu Hosted macOS
Claude Code / OpenHands / MCP schreibt ins Repo Nein Standard one job, one workspace; sensiblen Cache-Share verbieten
Signing-Zerts in CI entschlüsselt Nein Job-scoped Keychain + post-Delete erforderlich

Pre-Launch-Checkliste (druckbar)

  • Jeder Job hat if: always()-Cleanup-Step oder äquivalentes Host-prune
  • Temp-Keychain / Signing-Dateien landen nicht in permanenten $HOME-Pfaden
  • High-Risk-Repos und Low-Trust-Workflows nutzen verschiedene runner labels
  • Runner-Registrierungs-Token und CI PAT haben Rotationskalender (30–90 Tage empfohlen)
  • Erster Workflow-PR von neuem Contributor bekommt menschliches Review, kein direkter Treffer auf Prod-runner
  • Agent und CI nutzen verschiedene PAT / App — kein Single-Token für MCP und Runner
  • Abgleich mit L1 Teil 2: ② fixiert „langsam“; ③ fixiert „ist self-hosted offen“

L1-Serie · wie Stack-Schichten verbinden

Dieser Artikel schließt die L1 (Fact-Schicht)-Sicherheitslinie: warum Runner existiert → ob self-hosted sich lohnt → wie die 2026 one job, one workspace-Baseline landet. Tabelle der Reihe nach lesen; vertikal zu L0, horizontal zu L3–L5.

Teil Thema Status
· AusführungsengineWarum Runner Cloud Mac AI Stack L1 ist (Diff → Fact)Veröffentlicht
· Queue und TCOmacOS CI queue time · self-hosted vs macos-latestVeröffentlicht
· dieser Artikelself-hosted runner-Sicherheit · one job, one workspace-BaselineVeröffentlicht
· OpenClaw-PipelineRunner führt Steps aus · OpenClaw orchestriert Trigger und Receipts (L1-Erweiterung)Veröffentlicht

Stack-Vertikal-Links (ein Einstieg pro Schicht):

Nach der L1-Trilogie, wenn Agent und CI einen Cloud Mac teilen, ist die nächste Schicht meist L3 Diff-Entscheidungen (warum Claude Code ein traditionelles IDE ersetzt — anders als vs Cursor und der Workstation-Artikel) — dann die L6 End-to-End-Karte (geplant).

FAQ

Verlangsamt one job, one workspace CI?
Cold Starts werden langsamer — deshalb teilten Teams Platten. Die 2026-Balance: nur sensible Artefakte und Job-Dirs löschen; erneuerbaren Cache mit repo-präfixierten cache keys, nicht nie geprüntes globales DerivedData.

Wann ist ein self-hosted runner „offen“?
Wenn mehrere Jobs/Repos Signing-Material, .netrc oder breiten globalen Cache in einem User-Home ohne if: always()-Cleanup teilen — besonders mit fork PRs oder Agent-editierten Workflows.

Kann MCP least privilege allein geteilte runner-Platte fixen?
Nein. MCP least privilege regelt Agent-Tool-Calls; Runner muss Dateien auf Platte regeln. Bösartige fork-Workflows umgehen MCP und können _work-Reste scannen.

Gibt es einen GitHub-One-Click-Schalter?
Hosted runner approximieren „One Click“; self-hosted braucht Workflow-post-Steps, Host-cron und optional ephemeral mode. Kein einzelnes actions/checkout-Flag ersetzt die volle Grenze.

Wie trennt sich das von OpenClaw-Orchestrierung?
OpenClaw handhabt Trigger-Reihenfolge und Receipts; Runner führt Steps aus. Isolation lebt in Workflow und Host — nicht annehmen, OpenClaw wischt Platte (siehe L1 ④ · OpenClaw-Pipeline).

Wo starte ich die L1-Serie?
Empfohlen: ① Ausführungsengine② Queue③ dieser Artikel. Volle Tabelle unter § L1-Serie.

L1-Trilogie fertig · nächste Schicht Diff

Fact-Schicht gesichert — Zeit für Claude Code

L1 beantwortet, wo CI Fact ausführt. Als Nächstes meist L3: Claude Code-Workflow auf Cloud Mac und IDE-Ersatzlogik (anders als vs Cursor Deep Dive).

Claude Code-Workflow lesen
Cloud Mac M4-Tarife ansehen