# ADR-0003: CI-Sicherheit – Fork-PRs und der Runner `docker-host` - **Status:** Vorgeschlagen (Chris entscheidet über die Maßnahmen unten), Issue #43 - **Datum:** 2026-11-03 ## Kontext Das Repo ist öffentlich. Der Runner `shuffle-mac-runner` (Label `docker-host`) läuft auf Chris' Mac mit Docker-in-Docker; die Jobs `supabase-tests` und `e2e` sprechen den dind-Daemon per TCP an (`DOCKER_HOST=tcp://:2375`, ohne TLS). Wer dort Code ausführt, hat Root-Äquivalent im dind (Container mit Host-Mounts, Zugriff auf alles, was der dind sieht). Bei `on: pull_request` wird die Workflow-Datei aus dem PR-Head gelesen. Ein Fork-PR kann Bedingungen wie `head.repo.full_name == github.repository` entfernen oder ein beliebiges `runs-on:` setzen. **Ein `if:` in `ci.yml` schützt daher nicht** und kein Inhalt des Repos kann einen Fork-PR daran hindern, `runs-on: docker-host` anzufordern. Stand der Prüfung (Forgejo 8.0.3+gitea-1.22.0, Runner v5.0.4): Die Aussagen zum Verhalten stammen aus Kenntnis der Gitea-1.22-/Forgejo-8-Quellen und der Doku, **nicht** aus einem Live-Test. Prüfschritte stehen unten. ## Was Forgejo 8.0.3 bietet | Mechanismus | Verfügbarkeit | Bewertung | |---|---|---| | Freigabe für Fork-PR-Workflows (`need approval`) | Eingebaut, **keine Repo-Einstellung zum Ein-/Ausschalten**. Läufe von Fork-PRs (Event `pull_request`) von Nutzern ohne Schreibrecht warten auf „Freigeben“ durch einen Maintainer. | Hauptschutz. Schwäche: Nach einer Freigabe gelten spätere Läufe desselben Nutzers im Repo als freigegeben (Quelltext-Annahme), d. h. ein neuer Push mit geändertem Workflow läuft evtl. ohne erneute Freigabe. | | `pull_request_target` | Läuft mit Workflow aus dem Base-Branch | Nicht verwenden, um PR-Code auf `docker-host` zu bauen (klassische Pwn-Request-Lücke). | | Runner-Scoping (Instanz/Organisation/Nutzer/Repo) | Eingebaut | Begrenzt, *welche Repos* den Runner nutzen, nicht *welche Events*. Hilft hier nicht gegen Fork-PRs im selben Repo. | | Runner-Labels | Eingebaut | Jeder Workflow darf jedes Label anfordern. Kein Schutz. | | Event-/Repo-Filter im Runner (`runner.yaml`) | Nicht vorhanden | -- | | Repo privat stellen | Eingebaut | Beseitigt das Problem (Fremde können keine PRs öffnen), kostet Öffentlichkeit. | | Forks instanzweit verbieten (`app.ini`, Forgejo-Doku prüfen: `[repository] DISABLE_FORKS`/ähnlich) | In 8.0.3 nicht verifiziert | Nur als Prüfpunkt. | ## Entscheidung (Vorschlag) Verteidigung in drei Schichten; nur die erste lässt sich im Repo ausdrücken, der Rest braucht Chris: 1. **Repo (umgesetzt):** `if:`-Guard an `supabase-tests` und `e2e` bleibt als Komfort (ehrliche Fork-PRs hängen nicht in „wartet auf Freigabe“); Kommentare in `ci.yml` benennen ihn ausdrücklich als keinen Schutz. 2. **Forgejo (Chris):** Freigabe-Pflicht nutzen und Disziplin: Fork-PR-Läufe **nie** freigeben, ohne vorher die Änderungen an `.forgejo/`, `e2e/`, `supabase/`, `app/pubspec*` und allen Skripten gelesen zu haben. Bis zur Isolation (3.) bevorzugt: Fork-PRs gar nicht ausführen, sondern den Branch lokal prüfen oder in ein Branch im Haupt-Repo übernehmen. 3. **Runner/Mac (Chris):** Blast Radius des dind begrenzen (siehe Anleitung). Solange 2. und 3. nicht erledigt sind, gilt `docker-host` als **nicht für externe Beiträge geeignet**. Empfehlung: vor dem ersten externen Beitrag, spätestens vor Release, entweder 3. umsetzen oder das Repo privat stellen. ## Anleitung für Chris (nicht automatisiert; nichts davon wurde ausgeführt) ### A. Verhalten prüfen (10 Minuten, empfohlen als Erstes) 1. Zweites Forgejo-Konto ohne Schreibrecht anlegen (oder Test-Nutzer), Repo forken, im Fork `ci.yml` ändern (`if:`-Zeilen entfernen, harmlosen Schritt `echo hallo` ergänzen), PR gegen `main` öffnen. 2. Erwartung: Lauf steht auf „Wartet auf Freigabe“ und startet erst nach Klick auf **Freigeben** (PR-Seite bzw. Actions-Lauf). Falls er ohne Freigabe startet: Das Repo sofort privat stellen (Einstellungen -> Repository -> Sichtbarkeit) und Runner pausieren (B.3), dann Forgejo-Version/Doku prüfen. 3. Zweiten Push im selben PR machen: Läuft er jetzt ohne erneute Freigabe? Dann ist die Annahme „einmal freigegeben, immer freigegeben“ bestätigt. ### B. Auf dem Mac (Runner/dind) 1. Prüfen, ob `docker`-Jobs (nicht nur `docker-host`) den dind erreichen: In einem Testlauf mit `runs-on: docker` einen Schritt `curl -s -m 3 http://:2375/version` ausführen (Gateway wie in `ci.yml` aus `/proc/net/route`). Antwortet er, kann jeder Fork-PR-Code auf **jedem** Label den Daemon übernehmen. Dann dind-Port nur an einen eigenen Docker-Netzwerk-Alias binden, der nur `docker-host`-Jobs zugewiesen ist (`container.network`/`options` in der Runner-Konfiguration), oder TLS (Port 2376 mit Client-Zertifikaten) einführen, wobei die Zertifikate nur in `docker-host`-Jobs gemountet werden (`valid_volumes` in der Runner-Konfiguration). 2. Besser: Runner in eine Wegwerf-VM (z. B. Lima/Colima/UTM) oder auf einen separaten Rechner/Hetzner-Server verlagern, ohne Host-Mounts, ohne Zugriff aufs LAN, ohne Zugangsdaten und ohne persönliche Daten auf dem Mac. Ziel: Ein Ausbruch aus dem dind trifft nichts Wertvolles. Eigenes Registrierungs-Token verwenden; `.runner` und Token nicht ins Repo und nicht in Logs. 3. Notbremse: Runner stoppen (`launchctl`/Prozess beenden oder in Forgejo unter Einstellungen -> Actions -> Runner deaktivieren). 4. Runner nur für dieses Repo registrieren (Repo-Scope statt Instanz-/Nutzer-Scope), damit andere Repos ihn nicht ansprechen können. ### C. In Forgejo 1. Fork-PR-Läufe nur nach Review der Workflow-/Skriptänderungen freigeben (siehe Entscheidung 2.). 2. Optional: Branch-Schutz für `main` mit **Geschützte Dateimuster** `.forgejo/**;e2e/**;supabase/config.toml`, damit Änderungen daran nie ohne Review in `main` landen (wirkt auf Pushes nach `main`, nicht auf PR-Läufe). 3. Repo privat stellen, wenn externe Beiträge nicht gewollt sind (löst das Problem vollständig). 4. Runner-Token regelmäßig erneuern. ## Konsequenzen - Externe Beiträge brauchen manuelle Freigabe und Review mit Blick auf CI-Dateien. - Fork-PRs bekommen für `supabase-tests`/`e2e` keine Prüfung, solange der `if:`-Guard steht; sie laufen dann nur auf `docker` (Format, Analyse, Tests, Web-Build). Ein Maintainer kann den Branch bei Bedarf ins Haupt-Repo übernehmen, um die Docker-Jobs zu starten. - Offen: Ergebnis der Prüfung A und Umsetzung B. Bis dahin Issue #43 nicht schließen.