Warum App-Hosting auf einem separaten Origin
SupaCloud kann gehostete App-Frontends ausliefern — einfache, React- oder Svelte-Quellordner, die vom Vite-Worker gebaut werden. Diese Seite erklärt die Sicherheitsgrenze zwischen diesen gehosteten Frontends und dem SupaCloud Control Plane und warum sie auf einer separaten registrierbaren Domain leben müssen.
Das Risiko des Session-Riding
Abschnitt betitelt „Das Risiko des Session-Riding“Das SupaCloud Control Plane ist eine Cookie-authentifizierte API. Das
Session-Cookie wird mit SameSite=Lax, host-only (kein Domain-Attribut),
HttpOnly und Path=/ gesetzt. Solange eine gehostete App und die API denselben
Origin teilen, kann eine bösartige (oder kompromittierte) App /api/* aufrufen,
und der Browser hängt stillschweigend das Session-Cookie des besuchenden
Operators an. Die App wird dann mit den vollen Session-Rechten desjenigen
ausgeführt, der sie geöffnet hat.
Dieses Risiko besteht auch bei einer strikten Default-Deny-CSP: script-src 'self' und connect-src 'self' erlauben trotzdem Same-Origin-fetch-Aufrufe
an /api, und SameSite=Lax hält Cookies nur bei Cross-Site-Anfragen
zurück — nicht bei Same-Origin- oder Same-Site-Anfragen.
Warum eine Geschwister-Subdomain nicht ausreicht
Abschnitt betitelt „Warum eine Geschwister-Subdomain nicht ausreicht“Eine naheliegende erste Antwort lautet: „Apps auf apps.supacloud.run
platzieren.“ Das genügt jedoch nicht. Eine Browser-Site entspricht der
registrierbaren Domain (eTLD+1), nicht dem vollständigen Origin.
app.supacloud.run und api.supacloud.run sind dieselbe Site —
supacloud.run — daher leitet SameSite=Lax das Session-Cookie bei Anfragen
von einer zur anderen weiterhin weiter. Außerdem kann eine Subdomain unter
derselben eTLD+1 ein Domain=supacloud.run-Cookie setzen, das für alle
Geschwister-Subdomains sichtbar ist (Cookie-Tossing / Shadow-Cookie-Angriff).
Weder SameSite noch host-only-Cookies schließen diese Angriffspfade; der
einzige Mechanismus, der das unter einer gemeinsamen eTLD+1 tut, ist ein CSP-
sandbox-Attribut, das den Origin der App vollständig annulliert — ein
brüchiger Ansatz mit eigenen Fehlerszenarien.
Die Lösung: eine andere registrierbare Domain
Abschnitt betitelt „Die Lösung: eine andere registrierbare Domain“SupaCloud liefert gehostete App-Frontends auf supacloud.net aus — einer
Domain mit einer anderen eTLD+1 als die Control-Plane-Domain (supacloud.run).
Dadurch ist jede gehostete App cross-site gegenüber dem Control Plane. Mit
der bestehenden Cookie-Konfiguration schließt sich das Session-Riding
automatisch:
SameSite=Laxhält das Session-Cookie bei Cross-Site-Anfragen von der App an/apizurück.- Cross-eTLD+1-Regeln verbieten ein
Domain-Attribut, das beide Domains überspannen würde, sodass Cookie-Tossing strukturell unmöglich ist. - Die exakte Allowlist-CORS-Policy des Control Plane schließt die Apps-Domain natürlicherweise aus; eine CORS-Lockerung ist nicht erforderlich.
Was ausgeliefert wurde (Issue #410 / ADR 0041)
Abschnitt betitelt „Was ausgeliefert wurde (Issue #410 / ADR 0041)“SUPACLOUD_APPS_BASE_DOMAIN — auf dem gehosteten Deployment auf
supacloud.net gesetzt. Das ist die einzige Konfiguration, die auf SupaCloud-
Seite erforderlich ist.
Boot-Guard (secret_hydration.rs) — SupaCloud verweigert den Start, wenn
die Apps-Domain dieselbe eTLD+1 wie der Control-Plane-Host hat. Das psl-Crate
vergleicht registrierbare Domains; Gleichheit, Teilmengen- oder
Geschwister-Subdomain-Beziehungen werden alle abgelehnt.
SUPACLOUD_APPS_HOSTED_FRONTEND=true setzt zusätzlich voraus, dass
SUPACLOUD_APPS_BASE_DOMAIN gesetzt ist; der Guard erzwingt dies beim Start,
sodass der gehostete Pfad niemals ohne die Isolationsgrenze aktiviert werden
kann.
Opaques Subdomain pro Deployment (Migration 172) — jede bereitgestellte
gehostete App erhält eine stabile 80-Bit-Hex-Subdomain (z. B.
a3f8b2….supacloud.net), die einmalig vergeben wird und über Redeploys hinweg
stabil bleibt. Nur gehostete Frontends (React, Svelte, Plain-with-Source)
erhalten eine Subdomain; Form-Renderer-Apps werden ausschließlich auf dem
Control-Plane-Pfad ausgeliefert und sind von dieser Änderung nicht betroffen.
Host-Dispatch-Middleware (interfaces/http/apps_host.rs) — als äußerste
Schicht des axum-Routers eingehängt. Eine Anfrage, deren Host mit
supacloud.net oder einer Subdomain davon übereinstimmt, wird als App-Inhalt
ausgeliefert und erreicht den Control Plane niemals — keine /api-Routen,
keine SPA, keine Anmeldeseite. Nur GET und HEAD werden akzeptiert; alle
anderen Methoden werden mit 405 Method Not Allowed abgelehnt.
Pfad-Routen-Isolation (services/app_deployments/serve.rs) — wenn eine
Apps-Domain konfiguriert ist, verweigert die Control-Plane-Pfad-Route
(/a/{workspace_slug}/{route_path}) gehostete Frontends. Eine gehostete App
ist ausschließlich über ihre Subdomain auf dem separaten Origin erreichbar.
Die #113-Form-Renderer-App (Manifest-only-Plain-App) ist kein gehostetes
Frontend und wird weiterhin auf der Pfad-Route ausgeliefert.
Per-App-Callback-Token (Migration 173, Präfix scwa_) — ein serverseitiger,
vom Eigentümer ausgestellter Bearer-Token für App-zu-/api-Callbacks. Da das
JS der App das Operator-Session-Cookie nicht Cross-Origin mitführen kann,
verwenden App-Backends, die die SupaCloud-API aufrufen müssen, stattdessen
diesen Token. Er wird über POST /api/apps/{id}/deployment/token ausgestellt,
ist workspace-scoped und wird beim Undeploy automatisch widerrufen. Der Token
wird serverseitig verwendet und nie in das öffentliche Frontend eingebettet.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“| Maßnahme | Schließt | Hinweise |
|---|---|---|
Separate registrierbare Domain (supacloud.net) |
Session-Riding über SameSite=Lax |
Primäre Isolation |
| Cross-eTLD+1 | Cookie-Tossing / Shadow-Cookie-Angriff | Strukturell; kein Code erforderlich |
| Boot-Guard (eTLD+1-Prüfung) | Fehlkonfigurierte Same-Site-Apps-Domain | Fail-Closed beim Start |
| Host-Dispatch-Middleware | Gehostete App erreicht /api-Routen |
Nur GET/HEAD |
| Pfad-Routen-Verweigerung | Gehostete App auf Control-Plane-Origin | Form-Renderer ausgenommen |
| Per-App-Callback-Token | App-zu-API-Identität ohne Session | Vom Eigentümer ausgestellt, nur Hash |
Für die operative Einrichtung siehe App-Hosting konfigurieren und die Umgebungsvariablen-Referenz. Die vollständige Design-Begründung findet sich in ADR 0041.