7.2 KiB
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://<Gateway>: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 Freigabe-Logik wurde im Quelltext von Forgejo v8.0.3
gelesen (services/actions/notifier_helper.go, Funktion ifNeedApproval). Es gab keinen Live-Test mit einem Fork-PR;
das Verhalten des Runners und die Aussagen zu Runner-Konfiguration stammen aus Doku-Kenntnis. 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. Laut Quelltext v8.0.3: Fork-PR (Event pull_request) braucht Freigabe, außer der Nutzer hat Schreibrecht, oder es gibt im Repo schon einen freigegebenen Lauf dieses Nutzers. Eingeschränkte Nutzer (restricted) brauchen immer Freigabe. |
Hauptschutz, aber mit Lücke: Eine einzige Freigabe vertraut dem Nutzer dauerhaft. Spätere Pushes mit manipulierten Workflows laufen ohne weitere Freigabe. Die Freigabe ist also faktisch eine Entscheidung „diesem Nutzer vertraue ich den Runner an“. |
Nutzer restricted (Admin-Bereich -> Benutzerkonten, bzw. app.ini [service] DEFAULT_USER_IS_RESTRICTED = true für neue Konten) |
Eingebaut | Erzwingt die Freigabe bei jedem Lauf (Quelltext). Sinnvoll, wenn Registrierung offen ist. |
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:
- Repo (umgesetzt):
if:-Guard ansupabase-testsunde2ebleibt als Komfort (ehrliche Fork-PRs hängen nicht in „wartet auf Freigabe“); Kommentare inci.ymlbenennen ihn ausdrücklich als keinen Schutz. - Forgejo (Chris): Fork-PR-Läufe auf diesem Runner nicht freigeben, solange er nicht isoliert ist (3.) oder das Repo
nicht privat ist. Grund: Eine Freigabe gilt für alle späteren Läufe des Nutzers. Fremde Beiträge lokal prüfen oder als Branch
ins Haupt-Repo übernehmen. Neue/fremde Konten als
restrictedführen, damit jeder Lauf einzeln freigegeben werden muss. - 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)
- Zweites Forgejo-Konto ohne Schreibrecht anlegen (oder Test-Nutzer), Repo forken, im Fork
ci.ymländern (if:-Zeilen entfernen, harmlosen Schrittecho halloergänzen), PR gegenmainöffnen. - Erwartung: Lauf steht auf „Wartet auf Freigabe“ und startet erst nach Klick auf Freigeben (PR-Seite bzw. Actions-Lauf). Falls er ohne Freigabe startet (Quelltext sagt anderes): Das Repo sofort privat stellen (Einstellungen -> Repository -> Sichtbarkeit) und Runner pausieren (B.3), dann Forgejo-Version/Doku prüfen.
- Zweiten Push im selben PR machen: Läuft er jetzt ohne erneute Freigabe? Dann ist „einmal freigegeben, immer freigegeben“ bestätigt. Danach das Testkonto als
restrictedmarkieren und wiederholen: Jetzt muss jeder Lauf freigegeben werden.
B. Auf dem Mac (Runner/dind)
- Labels sind keine Grenze: Ein Fork-PR kann
runs-on: docker-hostselbst setzen. Eine Trennungdocker/docker-hostim Workflow schützt deshalb nichts, undcontainer.network/valid_volumesder Runner-Konfiguration gelten für den ganzen Runner, nicht je Label. Schutz bieten nur Freigabe, ein Daemon/VM ohne Wertvolles (Punkt 2) oder ein privates Repo. Sollendockerunddocker-hostunterschiedlich exponiert sein, braucht es getrennte Runner-Registrierungen mit eigener Konfiguration, idealerweise auf getrennten Hosts. Prüfen kannst du, ob eindocker-Job den dind erreicht (curl -s -m 3 http://<Default-Gateway>:2375/version, Gateway wie inci.ymlaus/proc/net/route); antwortet er, gilt die Warnung für alle Jobs des Runners. - Wirksam: 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;
.runnerund Token nicht ins Repo und nicht in Logs. - Notbremse: Runner stoppen (
launchctl/Prozess beenden oder in Forgejo unter Einstellungen -> Actions -> Runner deaktivieren). - Runner nur für dieses Repo registrieren (Repo-Scope statt Instanz-/Nutzer-Scope), damit andere Repos ihn nicht ansprechen können.
C. In Forgejo
- Fork-PR-Läufe nicht freigeben (siehe Entscheidung 2.); fremde Konten unter Administration -> Benutzerkonten als eingeschränkt markieren.
- Optional: Branch-Schutz für
mainmit Geschützte Dateimuster.forgejo/**;e2e/**;supabase/config.toml, damit Änderungen daran nie ohne Review inmainlanden (wirkt auf Pushes nachmain, nicht auf PR-Läufe). - Repo privat stellen, wenn externe Beiträge nicht gewollt sind (löst das Problem vollständig).
- Runner-Token regelmäßig erneuern.
Konsequenzen
- Externe Beiträge laufen nicht auf
docker-host, bis der Runner isoliert ist. - Fork-PRs bekommen für
supabase-tests/e2ekeine Prüfung, solange derif:-Guard steht; sie laufen dann nur aufdocker(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.