bifroest/docs/sandbox-konzept.md
2026-10-27 08:34:17 +01:00

9.6 KiB
Raw Blame History

Sandbox für Tests, Linter und Builds – Konzept

Stand: 23.10.2026. Noch nicht umgesetzt. Entscheidungen zu Ort und Isolation stehen aus (siehe Offene Fragen).

Ziel

Die KI führt auf dem Stand des Chat-Branches Tests, Linter oder einen Build aus, sieht das Ergebnis und bessert nach. Sie kommt dabei nicht an Netz, Zugangsdaten oder andere Systeme. Ergebnisse sind für Menschen sichtbar (Ansicht Änderungen, Beschreibung des Pull Requests).

Nicht Ziel: beliebige Programme mit Zugriff auf Produktivsysteme ausführen, Deployments, Pushes aus der Sandbox.

Grundsätze

  1. Code aus dem Repository ist fremder Code. Jeder mit Schreibrecht auf das Repository kann Code einschleusen, der in der Sandbox läuft; Testcode ist Code. Die Sandbox muss das aushalten.
  2. Kein Netz, keine Geheimnisse. Kein Git-Token, keine DB-Zugänge, keine Umgebungsvariablen der Plattform. Netz höchstens zum internen Paket-Mirror.
  3. Nie auf dem Rechner bzw. im Pod des Workers. Jede Ausführung bekommt einen frischen, kurzlebigen Container.
  4. Nur Ergebnisse kommen zurück. Exit-Code, gekürzte Ausgabe, optional JUnit-XML. Ändert ein Formatter Dateien, kommt das als Diff zurück und wird wie jeder andere Commit über commit_change angelegt. Aus der Sandbox wird nie gepusht.
  5. Ausgaben sind Fremdinhalt. Testausgaben gehen eingefasst an die KI (app.security.untrusted), denn auch sie können Prompt Injection enthalten.

Isolation

Variante Isolation Einschätzung
Docker/Podman rootless Teilt den Kernel mit dem Host Für die lokale Entwicklung
Pod mit restricted-v2 (OpenShift-Standard) Teilt den Kernel mit dem Node; zufällige UID, keine Capabilities, Seccomp Gleiches Niveau wie eure CI auf OpenShift; Start auf OpenShift
gVisor (runsc) Eigener Kernel im Userspace Gut, braucht Installation durch Cluster-Admins
Kata Containers / „OpenShift sandboxed containers“ Jede Ausführung als kleine VM Am stärksten; Nutzung nur über runtimeClassName: kata

Betrieb auf OpenShift (Zielumgebung)

Bifröst wird auf OpenShift betrieben, ohne Cluster-Admin-Rechte und ohne privilegierte Container. Das reicht: Die Plattform startet selbst keine Container (kein Docker/Podman im Pod), sondern lässt über die OpenShift-API für jeden Lauf einen kurzlebigen Job anlegen, wie es CI-Systeme tun.

Aufbau

  • Eigener Namespace, z. B. bifrost-sandbox, getrennt vom Namespace der Plattform. Secrets der Plattform sind von dort nicht erreichbar.
  • ServiceAccount der Plattform mit Rechten nur in diesem Namespace (Role/RoleBinding, kein Cluster-Admin):
    • jobs, pods: create, get, list, watch, delete
    • pods/log: get
  • NetworkPolicy: Egress standardmäßig verboten; erlaubt sind nur der Paket-Mirror und der Endpunkt der Plattform zum Abholen des Codes (siehe unten).
  • ResourceQuota und LimitRange: begrenzen CPU, RAM, Speicher und die Zahl gleichzeitiger Sandboxes im Namespace.
  • Je Pod:
    • activeDeadlineSeconds (z. B. 600), backoffLimit: 0, ttlSecondsAfterFinished
    • automountServiceAccountToken: false
    • securityContext: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false, alle Capabilities entfernt, seccompProfile: RuntimeDefault
    • emptyDir als einziges beschreibbares Arbeitsverzeichnis (mit sizeLimit)
    • optional runtimeClassName: kata, wenn verfügbar
  • Images: fertige Images aus der eigenen Registry (Python, Node, Java/Maven …), gebaut von der CI. Im Cluster wird nichts gebaut.

Code hinein, Ergebnis heraus

  • Empfohlen: Ein Init-Container lädt das Archiv des Branch-Stands (git archive des Commits) mit einem Einmal-Token von einem internen Endpunkt der Plattform. Das Token gilt nur für diesen Lauf und Commit und läuft nach Minuten ab. Das ist neben dem Mirror die einzige erlaubte Verbindung.
  • Alternative: Die Plattform lädt das Archiv per exec in den Pod. Das braucht das Recht pods/exec, das oft nicht vergeben wird.
  • Ergebnis: Exit-Code aus dem Job-Status, Ausgabe über pods/log (gekürzt), JUnit-XML als markierter Abschnitt der Ausgabe.

Grenze

Normale Pods teilen sich den Kernel mit dem Node. Die Isolation besteht aus SCC, Seccomp, fehlendem Netz, fehlenden Geheimnissen und Limits, nicht aus einem eigenen Kernel. Das entspricht dem Niveau von OpenShift Pipelines (Tekton), Jenkins-Agents und GitLab-Runnern auf OpenShift, denen Code aus den eigenen Repositories schon heute anvertraut wird. Mehr Isolation bringt Kata („OpenShift sandboxed containers“): einmalige Installation durch die Cluster-Admins, danach nur runtimeClassName: kata im Pod.

Variante mit OpenShift Pipelines (Tekton)

Ist OpenShift Pipelines verfügbar, kann die Plattform statt eigener Jobs einen PipelineRun starten und Status und Logs über die API lesen. Vorteil: bekannte Bedienung, Logs und Wiederverwendung vorhandener Tasks. Nachteil: Abhängigkeit vom Operator und etwas mehr Latenz.

Befehle: fest statt freier Shell

Stufe 1 kennt nur feste Befehle je Quelle, z. B.:

Name Beispiel
test uv run pytest -q
lint uv run ruff check .
build mvn -q -DskipTests package

Festgelegt von Workspace-Admins an der Quelle (Image, Setup-Befehl, Befehle) oder in einer Datei im Repository (z. B. bifrost.yml), wobei Image und Limits immer aus der Plattform kommen. Die KI ruft ein Tool wie run_check(source, name) auf. Freie Befehle wären eine eigene, höhere Risikoklasse mit Freigabe und kommen frühestens in Stufe 3.

Einstufung: run_check wirkt nur in der Sandbox, verändert nichts außerhalb und wird wie write_internal behandelt (Vorschlag: erlaubt). Freie Befehle: Freigabe nötig.

Abhängigkeiten

Der aufwendigste Teil im Alltag:

  • Im Image: Für den Start enthalten die Images die Abhängigkeiten oder installieren sie im Setup-Befehl aus dem Mirror.
  • Mirror: Paketquellen (PyPI, npm, Maven) über den internen Mirror (Nexus/Artifactory), aus dem Sandbox-Namespace ohne Zugangsdaten erreichbar oder mit einem reinen Lese-Token nur für den Mirror.
  • Cache: später je Quelle zwischenspeichern, gültig bis sich die Lockfiles ändern (z. B. PersistentVolume oder Cache-Image).

Einbindung in die Plattform

  • Schnittstelle mit zwei Backends: lokal (Podman/Docker, für Entwicklung und Tests) und OpenShift (Jobs über die Kubernetes-API). Die API lässt sich mit dem vorhandenen httpx ansprechen; eine Client-Bibliothek wäre bequemer, kommt aber nur nach Rückfrage hinzu.
  • Runs: Ein Testlauf dauert Minuten; der Run läuft mit Heartbeat weiter wie bisher. Abbruch des Runs löscht den Job.
  • Anzeige: Tool-Schritt mit Live-Status; in Änderungen ein Badge „Tests grün/rot“ am Commit; Ergebnis auch in der Beschreibung des Pull Requests.
  • Steuerung: Limits je Team (gleichzeitige Läufe, Minuten pro Tag), Audit-Log (sandbox.run mit Befehl, Commit, Dauer, Exit-Code), Metriken (aiw_sandbox_runs_total, Dauer).
  • Konfiguration (Vorschlag): AIW_SANDBOX_BACKEND (none | local | openshift), AIW_SANDBOX_NAMESPACE, AIW_SANDBOX_RUNTIME_CLASS, AIW_SANDBOX_TIMEOUT_SECONDS, AIW_SANDBOX_CPU, AIW_SANDBOX_MEMORY, AIW_SANDBOX_ALLOWED_IMAGES.

Stufen und Aufwand (grob)

Stufe Inhalt Grobe Schätzung
1 Feste Befehle, Backend lokal und OpenShift-Jobs, kein Netz, Abhängigkeiten im Image, Ergebnis im Chat ca. 1–2 Wochen
2 Paket-Mirror, Cache, JUnit-Auswertung, Badge in Änderungen und im Pull Request, Limits je Team weitere 1–2 Wochen
3 Freie Befehle mit Freigabe, länger lebende Sandbox je Chat, Kata bzw. Tekton abhängig von der Infrastruktur

Das OpenShift-Backend lässt sich vollständig erst im eigenen Cluster testen.

Alternative ohne eigene Sandbox: vorhandene CI

Die Plattform pusht den ai/*-Branch, die CI (Bitbucket Pipelines, Jenkins, Tekton) baut ihn, die Plattform liest den Build-Status über die Bitbucket-API und gibt ihn der KI.

  • Vorteil: Isolation, Images und Mirror sind dort schon vorhanden.
  • Nachteile: langsameres Feedback; widerspricht dem Grundsatz „nur Menschen pushen“ und bräuchte eine Ausnahme (z. B. automatischer Push nur auf ai/*, abgesichert durch die Branch Permission).

Geeignet als Übergang, falls die Sandbox auf sich warten lässt.

Offene Fragen an das OpenShift-Plattform-Team

  1. Bekommen wir einen eigenen Namespace für Sandboxes, mit Role/RoleBinding für einen ServiceAccount (Jobs/Pods anlegen und löschen, pods/log lesen), NetworkPolicies und ResourceQuota?
  2. Ist der Operator OpenShift sandboxed containers (Kata) verfügbar bzw. geplant (runtimeClassName)?
  3. Gibt es einen Paket-Mirror (PyPI, npm, Maven), der aus dem Cluster ohne Zugangsdaten erreichbar ist?
  4. Ist OpenShift Pipelines (Tekton) verfügbar?
  5. Aus welcher Registry dürfen Images gezogen werden, und wer baut und scannt die Sandbox-Images?

Risiken und Gegenmaßnahmen

Risiko Gegenmaßnahme
Eingeschleuster Code im Repository Kurzlebiger Container, restricted-v2, Seccomp, kein root, optional Kata
Datenabfluss Egress verboten außer Mirror und Code-Endpunkt; keine Geheimnisse im Container
Missbrauch von Ressourcen Quota, LimitRange, activeDeadlineSeconds, Limits je Team
Ausbruch aus dem Container Aktuelle Nodes, Seccomp; für höheres Schutzniveau Kata
Prompt Injection über Testausgaben Ausgabe als Fremdinhalt eingefasst; schreibende Schritte bleiben an die Regeln der Plattform gebunden
Geheimnisse in der Historie des Repositorys Nur der Stand des Commits (git archive), keine Historie; ohne Netz kein Abfluss