Shuffle/docs/adr/0003-ci-runner-sicherheit.md
cschlaefke 666ba64fcc fix(ci): document fork-PR risk for docker-host runner and guard supabase-tests
Add ADR-0003 with Forgejo 8.0.3 options and a step-by-step guide for
runner/Forgejo settings, correct the misleading guard comments in ci.yml
and apply the comfort guard to supabase-tests too.

Refs #43

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 13:50:25 +01:00

6.4 KiB
Raw Blame History

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 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://<Default-Gateway>: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.