Wenn Produktteams ein Browser-Dashboard oder internes SPA an ein OpenClaw-Gateway anbinden, das auf TCP 8787 lauscht, ist 2026 der erste „Geisterausfall“ selten die Modell-API—sondern CORS. curl ist grün, Safari und Chrome melden blocked by CORS policy, weil der Browser ein OPTIONS-Preflight schickt, das die Kante nicht bedient, oder weil Access-Control-Allow-Origin auf * steht, während credentials: 'include' aktiv ist. Dieses Runbook liefert explizite Origin-Allowlists, den Unterschied zwischen einfachen und credentialed Requests, nginx-Headerplatzierung und doppelte Header, plus archivierbare curl-Sonden—abgestimmt mit First-Run PATH, Node und LaunchAgent-Smoke, Portbindung und Health-Probe-Ausrichtung und Doctor-Gateway-Diagnose.
Du nimmst eine Entscheidungsmatrix, Header-Rezepte, numerische Leitplanken (204 oder 200 für OPTIONS sind ok; bei Credentials kein Wildcard) und eine Miet-Baseline nahe 16,9 $ pro Tag auf den veröffentlichten MacHTML-Preisseiten mit, um dieselbe Safari-Version zu proben, die Kunden sehen.
Wie sich Browser von curl unterscheiden
curl https://gateway.example/health beweist Erreichbarkeit, nicht Browser-Politik. User-Agenten hängen bei Cross-Site-Aufrufen ein Origin an, können simple GETs bei Custom-Headers in Preflight-POSTs verwandeln und verweigern JavaScript den Response-Body, wenn Access-Control-Allow-Origin nicht passt (oder außerhalb credentialed Flows * ist). Betrachte CORS als Vertrag zwischen Frontend und Plattform: Frontend besitzt exakte Origin-Strings; Plattform besitzt die Header-Reihenfolge auf dem Hop, der TLS beendet.
Dokumentiere jeden Aufrufer mit Schema, Host und Port—https://app.corp.internal:8443 ist nicht https://app.corp.internal. Fehlender Port in der Allowlist erzeugt sporadische Fehler nur in Staging mit nicht standardmäßigem TLS-Port. Deshalb probieren Teams auf dedizierten Mac mini-Maschinen mit derselben Safari-Build wie die Laptops der Führungsetage.
Einfache versus credentialed Fetches
Einfache GETs ohne Custom-Header können Preflights überspringen, brauchen aber dennoch Access-Control-Allow-Origin, wenn JavaScript den Body cross-origin lesen soll. Sobald Authorization, Cookie oder fetch(..., { credentials: 'include' }) dazukommen, verlangt die Spezifikation ein konkretes Origin-Echo—Wildcards sind verboten—plus Access-Control-Allow-Credentials: true.
Nur anonyme Metriken? Dann * ohne Cookies. Gemischte Modi—manche Routen credentialed, andere anonym—splitten nginx-location-Blöcke, damit Marketing-Pixel nicht versehentlich Admin-CORS erben.
OPTIONS-Preflight-Mechanik
Preflight sendet OPTIONS mit Access-Control-Request-Method und optional Access-Control-Request-Headers. Das Gateway antwortet mit Access-Control-Allow-Methods (typisch GET,POST,OPTIONS), Access-Control-Allow-Headers case-insensitive und Access-Control-Max-Age für Browser-Cache—Entwicklung oft 300 Sekunden, Produktion 600–86400 je nach Header-Rotation.
204 ohne Body oder 200 mit leerem Body sind üblich. Scheitern tut es bei 405 Method Not Allowed, weil nginx OPTIONS falsch routet, oder wenn bei dynamischem Origin-Echo Vary: Origin fehlt und CDNs falsches ACAO cachen.
Origin-Allowlists und localhost-Fallen
http://localhost:5173 (Vite) und http://127.0.0.1:5173 sind verschiedene Origins. Beide in der Entwicklung whitelisten oder den Dev-Befehl auf einen Hostnamen standardisieren. Bei SSH-Port-Forwards, die 127.0.0.1:8787 auf Laptops zeigen, bleibt das Browser-Origin die öffentliche SPA-URL—die Allowlist muss die SPA enthalten, nicht nur http://127.0.0.1.
Dynamische Spiegelung beliebiger Origin-Werte ist bequem aber gefährlich ohne serverseitige feste Menge. Nutze Map-Lookup: wenn Origin in Menge S, echo; sonst Header weglassen und fail-closed loggen. Abgelehnte Origins auf INFO mit Sampling, damit Security missbräuchliche Versuche sieht, ohne Platten zu fluten.
nginx und doppelte Header
Wenn nginx TLS beendet und an OpenClaw auf Loopback weiterreicht, entscheide, ob CORS in nginx oder im Node-Prozess lebt—beides erzeugt doppelte Header, die Browser ablehnen. Pragmatisch: nginx für Static/Marketing, OpenClaw für API, falls dort schon CLI-kompatible Header sitzen. Dokumentiere es im gleichen Runbook wie Graceful Shutdown, damit On-Call nicht zweimal „repariert“.
Bei HTTP/2-Terminierung in nginx müssen add_header-Direktiven auch Fehlerpfade treffen—ältere Konfigurationen nur 2xx, ohne always. OPTIONS im falschen server-Block ist nach Wildcard/Credential-Mismatch die zweithäufigste SPA-Falle.
Vary: Origin, CDN-Caching und veraltetes ACAO
Wenn du das anfragende Origin zurückspiegelst, hängen die Antworten inhaltlich vom Origin-Request-Header ab und müssen für Zwischencache als variant betrachtet werden. Ohne Vary: Origin können Zwischen-Caches das Access-Control-Allow-Origin von Kunde A an Sitzung B ausliefern—subtiles Datenleck plus Funktionsbug. Manche CDNs normalisieren aggressiv und entfernen Vary; öffne ein Ticket beim Provider, wenn Edge-Regeln CORS umschreiben.
Setze kurze TTLs (60 Sekunden oder weniger) für gecachte Fehlerbodies während CORS-Debugs. Kombiniere Cache-Busts mit Korrelations-IDs aus strukturierten Logs, damit HAR-Dateien zu Gateway-Logs passen, selbst wenn Nutzer Screenshots anonymisieren.
Benutzerdefinierte Header, JWTs und tool-spezifisches CORS
OpenClaw-Tool-Routen hängen oft X-Request-Id- oder X-OpenClaw-Profile-ähnliche Header an. Jeder nicht safelistete Header triggert Preflight—explizit in Access-Control-Allow-Headers listen, nicht auf * bei credentialed Flows vertrauen. Laufen JWTs über Authorization: Bearer, müssen Preflight und echter POST dasselbe ACAO liefern; sonst erscheint die klassische Meldung ohne sichtbaren Header, obwohl der POST serverseitig klappte.
Bei Schlüsselrotation temporär die Preflight-Cache-Fenster verdoppeln, damit neue Header-Namen innerhalb von 15 Minuten ankommen; danach max-age wieder straffen und im gleichen Ops-Kalender wie TLS-Renewals pflegen.
Entscheidungsmatrix
| Aufrufer | Credentialed? | ACAO-Strategie | Hinweise |
|---|---|---|---|
| Öffentliche Docs | Nein | * oder statische Allowlist | Anonym bleiben, keine Cookies |
| Internes Admin-SPA | Ja | Explizites Origin zurückspiegeln | Mit Allow-Credentials: true |
| Mobile WebView | Manchmal | Custom-Scheme-Origins | WebView-URL-Patterns prüfen |
| Dritt-SaaS-Embed | Selten | Statisches Partner-Origin | Nur vertragliche Allowlist |
curl-Sonden zum Archivieren
# Preflight (Host/Pfad ersetzen)
curl -i -X OPTIONS "https://gw.example/v1/chat" \
-H "Origin: https://app.example" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization,content-type"
# GET mit Origin für ACAO auf GET
curl -i "https://gw.example/health" \
-H "Origin: https://app.example"
Volle Response-Header im Ticket speichern; nach jedem Deploy diffen. Wenn OPTIONS-p95 an der Kante 150 ms überschreitet, GET-Health aber bei 12 ms bleibt, zuerst Routing zu kaltem Upstream prüfen, nicht sofort OpenClaw verdammen.
Rollout-Checkliste
- Alle Produktions- und Staging-SPA-Origins mit Schema, Host, Port erfassen.
- Einen einzigen CORS-Header-Besitzer festlegen—nginx oder OpenClaw—und Duplikate entfernen.
- OPTIONS in CI mit denselben
Origin-Strings wie lokal ausführen. - Automatisiert testen, dass credentialed Flows Wildcard-ACAO ablehnen.
- Nach Port- oder Headeränderungen
openclaw doctorerneut; Health weiterhin 200. - Safari- und Chrome-HAR von einer gemieteten Mini 90 Tage aufbewahren.
Security-Reviews verlangen zunehmend Nachweis, dass dynamische Origin-Spiegelung begrenzt ist; halte die Allowlist-Datei in Git mit CODEOWNERS beim Plattformteam. So werden „temporäre“ Wildcard-Commits nicht dauerhafte Angriffsfläche.
FAQ
Warum scheitert Safari, Chrome aber nicht?
Safari ist strenger bei doppelten CORS-Headern und Mixed Content; teste beide Engines auf echter macOS-Hardware.
Preflight für immer cachen?
Nein—Access-Control-Max-Age begrenzen, damit Header-Rotationen in Minuten bis Stunden propagieren, nicht Wochen.
Gelten für WebSockets dieselben CORS-Regeln?
Der Handshake ist ein HTTP-Upgrade mit Origin; validiere wie bei REST mit derselben Allowlist-Logik.
Mac mini-Mieten auf Apple Silicon liefern dieselbe Safari-/WebKit-Kombination, die Entscheider bei Gateway-Demos sehen. MacHTML-Cloud-Knoten kombinieren SSH für skriptierte curl-Suites mit optionalem VNC, wenn Designer das Network-Panel live sehen wollen. Leerlaufleistung oft um 12 W—eine Probewoche auf einer Mini kostet weniger als ein falscher CORS-Header während eines Vorstands-Termins.
Mieten umgeht schwere Capex-Zyklen: auf den veröffentlichten Seiten grob 16,9 $ pro Tag statt weiterer Hardware, die nach der Integration idle steht. Wenn CORS fertig ist, Instanz stoppen; Header bleiben in Git, Metall nicht über 36 Monate in den Büchern abschreiben.
OpenClaw-CORS auf echtem macOS-Gateway proben
Miete eine Cloud-Mac-mini, um OPTIONS, credentialed Fetches und nginx-Kanten-Header in Safari zu prüfen, bevor du Gateway-Änderungen mergst.