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

76 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.