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
Kernaussage
Self-hosted runner ohne workspace-Isolation lassen CI/CD offen; die 2026-Baseline ist one job, one workspace + Token-Rotation.
- Fallenherkunft: job-übergreifende Dateireste, globale Cache-Vergiftung, lang lebende PATs auf der Platte
- Branchenbaseline: isoliertes Verzeichnis pro Job,
if: always()-Cleanup + Host-prune - Gleicher Stack wie MCP least privilege und OpenHands: L4 regelt Agent-Tokens; L1 regelt Fact-Schicht-Diskgrenzen
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.
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.
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)
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-latestund 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.
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:
- Verzeichnis-Isolation: jeder Job nutzt eigenes
RUNNER_TEMP/ Run-Verzeichnis; Skripte verbieten, nach/tmp/sharedoder repo-externen „Team-Shared“-Ordnern zu schreiben. - Prozess-Grenze: ein runner-Prozess darf Jobs seriell laufen, aber darf nicht ungereinigten globalen State zwischen Jobs teilen (z. B. API-Keys in
~/.zshrcexportiert). - Credential-Grenze: Signing-Temp-Keychains im Job-
postlöschen; Secrets nur per Env-Vars, nie Platte — wenn Platte nötig, Pfad im Run-Dir und mit Job löschen. - 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.
# .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; nieANTHROPIC_API_KEYund Signing-Keys in einer~/.zshrcmischen. - Agent workspace ≠ CI workspace: Claude Code-Projektverzeichnis darf nicht auf Runner
_workzeigen; 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ührungsengine | Warum Runner Cloud Mac AI Stack L1 ist (Diff → Fact) | Veröffentlicht |
| ② · Queue und TCO | macOS CI queue time · self-hosted vs macos-latest | Veröffentlicht |
| ③ · dieser Artikel | self-hosted runner-Sicherheit · one job, one workspace-Baseline | Veröffentlicht |
| ④ · OpenClaw-Pipeline | Runner führt Steps aus · OpenClaw orchestriert Trigger und Receipts (L1-Erweiterung) | Veröffentlicht |
Stack-Vertikal-Links (ein Einstieg pro Schicht):
- L0 · Fundament: Mac mini vs Cloud Mac · Cloud-AI-Workstation
- L2 · Inference: Ollama private inference · Parallelplanung mit Runner
- L3 · Diff: Claude Code-Workflow auf Cloud Mac · CodeGraph und übersehene Edits
- L4 · Context: MCP triple-connect hub · least-privilege exposure
- L5 · Workflow: OpenHands Agent-Plattform
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