Lorsque les équipes produit branchent un tableau de bord navigateur ou un SPA interne sur une passerelle OpenClaw qui écoute sur TCP 8787 en 2026, la première « panne mystère » est rarement l’API modèle—c’est CORS. curl répond, Safari et Chrome affichent blocked by CORS policy parce que le navigateur a envoyé un prévol OPTIONS que le bord n’a pas traité, ou parce que Access-Control-Allow-Origin vaut * alors que le fetch utilise credentials: 'include'. Ce guide opérationnel couvre les listes d’origines explicites, les requêtes simples versus requêtes avec identifiants, le placement des en-têtes nginx et les doublons, ainsi que des sondes curl archivables—alignées avec premier lancement PATH, Node et fumée LaunchAgent, liaison de port et alignement des sondes de santé et diagnostic doctor de la passerelle.
Vous repartez avec une matrice de décision, des recettes d’en-têtes, des garde-fous numériques (204 ou 200 acceptables pour OPTIONS, pas de joker avec identifiants) et une base locative proche de 16,9 $ par jour sur les pages de prix MacHTML pour répéter avec la même build Safari que vos clients.
Comment le navigateur diffère de curl
curl https://gateway.example/health prouve l’atteignabilité, pas la politique navigateur. Les agents utilisateurs ajoutent Origin pour les appels cross-site, peuvent transformer un GET simple en POST précontrôlé lorsque des en-têtes personnalisés apparaissent, et refusent d’exposer le corps au JavaScript si Access-Control-Allow-Origin ne correspond pas à l’origine initiatrice (ou n’est pas * hors flux avec identifiants). Traitez le CORS comme un contrat entre deux équipes : le front possède les chaînes d’origine exactes ; la plateforme possède l’ordre d’émission sur le saut qui termine le TLS.
Documentez chaque appelant avec schéma, hôte et port—https://app.corp.internal:8443 est distinct de https://app.corp.internal. Oublier le port dans la liste blanche provoque des échecs intermittents uniquement en staging sur un port TLS non standard. C’est pourquoi les équipes répètent sur un Mac mini dédié avec la même Safari que les laptops de la direction.
Requêtes simples versus requêtes avec identifiants
Les GET simples sans en-têtes personnalisés peuvent éviter le prévol, mais exigent toujours Access-Control-Allow-Origin si JavaScript doit lire le corps en cross-origin. Dès que Authorization, Cookie ou fetch(..., { credentials: 'include' }) interviennent, les navigateurs exigent une origine renvoyée explicitement—les jokers sont interdits par la spécification—avec Access-Control-Allow-Credentials: true.
Si vous ne publiez que des métriques anonymes, gardez * et évitez les cookies. Les modes mixtes—certaines routes avec identifiants, d’autres anonymes—scindent les blocs location nginx pour que les pixels marketing n’héritent pas par erreur des en-têtes CORS d’administration.
Mécanique du prévol OPTIONS
Le prévol envoie OPTIONS avec Access-Control-Request-Method et éventuellement Access-Control-Request-Headers. La passerelle doit répondre avec Access-Control-Allow-Methods listant les verbes autorisés (souvent GET,POST,OPTIONS), Access-Control-Allow-Headers en écho insensible à la casse, et Access-Control-Max-Age pour mettre en cache la poignée de main—300 secondes en développement, 600–86400 en production selon la fréquence de rotation des en-têtes.
Renvoyez 204 sans corps ou 200 avec corps vide ; les deux sont largement acceptés. Les échecs typiques : 405 Method Not Allowed parce que nginx route OPTIONS vers le mauvais amont, ou absence de Vary: Origin lorsque vous échoinez dynamiquement—les CDN peuvent alors mettre en cache un mauvais ACAO pour tous les clients.
Listes d’origines et piège localhost
http://localhost:5173 (Vite) et http://127.0.0.1:5173 sont des origines différentes. Ajoutez les deux en développement local ou standardisez la commande dev sur un seul hôte. Pour les tunnels SSH qui exposent 127.0.0.1:8787 sur les laptops ingénieurs, l’origine navigateur reste l’URL publique du SPA—votre liste blanche doit inclure l’origine du SPA, pas seulement http://127.0.0.1.
La réflexion dynamique—copier toute Origin entrante dans Access-Control-Allow-Origin—est pratique mais dangereuse sans validation côté serveur sur un ensemble fixe. Préférez une table de correspondance : si l’origine est dans l’ensemble S, renvoyez-la ; sinon omettez l’en-tête pour un échec fermé côté navigateur. Journalisez les origines rejetées en INFO avec échantillonnage pour l’audit sécurité sans saturer les disques.
nginx et doubles en-têtes
Lorsque nginx termine le TLS et transmet vers OpenClaw en loopback, décidez si le CORS vit dans nginx ou dans le processus Node—faire les deux produit des en-têtes dupliqués que certains navigateurs rejettent. Une répartition pragmatique : nginx pour les actifs statiques et pages marketing ; OpenClaw pour les routes API si la passerelle émet déjà des en-têtes compatibles CLI. Quel que soit le choix, documentez-le dans le même runbook que l’arrêt gracieux pour éviter les doubles correctifs en incident.
Si HTTP/2 se termine sur nginx, assurez-vous que les directives add_header s’appliquent aussi aux chemins d’erreur—les anciennes configs ne les ajoutent qu’en 2xx sans always. Les OPTIONS qui tombent dans le mauvais bloc server sont la deuxième cause majeure d’échecs SPA mystérieux après le décalage joker / identifiants.
Vary: Origin, cache CDN et ACAO obsolète
Lorsque vous renvoyez l’origine demandée, les réponses varient avec l’en-tête Origin de la requête. Sans Vary: Origin, les caches intermédiaires peuvent servir le Access-Control-Allow-Origin du client A à la session du client B—fuite subtile et bug fonctionnel. Certains CDN normalisent agressivement et retirent Vary ; ouvrez un ticket fournisseur si les règles de bord réécrivent le CORS.
Pendant le débogage CORS, fixez des TTL courts (60 secondes ou moins) sur les corps d’erreur mis en cache pour qu’un mauvais déploiement ne reste pas des heures en périphérie. Associez le bust de cache aux identifiants de corrélation des logs structurés pour relier les HAR aux journaux passerelle même lorsque les captures sont anonymisées.
En-têtes personnalisés, JWT et CORS spécifique aux outils
Les routes d’outils OpenClaw ajoutent souvent des en-têtes style X-Request-Id ou X-OpenClaw-Profile. Tout en-tête hors liste sûre du navigateur déclenche un prévol : énumérez-les explicitement dans Access-Control-Allow-Headers—ne comptez pas sur * pour les flux avec identifiants. Si les JWT passent par Authorization: Bearer, vérifiez que le prévol et le POST réel renvoient la même ligne ACAO ; sinon apparaît l’erreur classique « pas d’Access-Control-Allow-Origin » même si le POST a réussi côté serveur.
Lors de la rotation des clés de signature, doublez temporairement la fenêtre de cache du prévol pour que les navigateurs récupèrent les nouveaux noms d’en-tête en moins de 15 minutes, puis resserrez le max-age et notez-le sur le même calendrier opérationnel que les renouvellements TLS.
Matrice de décision
| Appelant | Avec identifiants ? | Stratégie ACAO | Notes |
|---|---|---|---|
| Site de documentation public | Non | * ou liste statique | Rester anonyme ; pas de cookies |
| SPA d’administration interne | Oui | Renvoyer l’origine explicite | Avec Allow-Credentials: true |
| WebView mobile | Parfois | Origines de schéma personnalisé | Valider les motifs d’URL WebView |
| Intégration SaaS tierce | Rare | Origine partenaire statique | Listes contractuelles uniquement |
Sondes curl à archiver
# Prévol (remplacer hôte/chemin)
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 simple avec Origin pour vérifier ACAO sur GET
curl -i "https://gw.example/health" \
-H "Origin: https://app.example"
Conservez les en-têtes complets dans le ticket ; différez après chaque déploiement. Si la latence p95 des OPTIONS au bord dépasse 150 ms alors que le GET de santé reste autour de 12 ms, examinez d’abord le routage vers un amont froid plutôt que d’accuser immédiatement OpenClaw.
Liste de contrôle de mise en production
- Inventorier chaque origine SPA de production et de staging avec schéma, hôte et port.
- Désigner un seul propriétaire des en-têtes CORS—nginx ou OpenClaw—et supprimer les doublons.
- Exécuter des OPTIONS en CI avec les mêmes chaînes
Originqu’en local. - Vérifier par tests automatisés que les flux avec identifiants rejettent ACAO joker.
- Relancer
openclaw doctoraprès changement de port ou d’en-têtes ; santé toujours 200. - Capturer des HAR Safari et Chrome depuis une mini louée et les conserver 90 jours.
Les revues sécurité demandent de plus en plus une preuve que la réflexion d’origine dynamique est bornée ; gardez le fichier de liste blanche dans Git avec CODEOWNERS côté plateforme pour éviter que des commits « temporaires » en joker ne deviennent une surface d’attaque permanente.
FAQ
Pourquoi Safari échoue là où Chrome réussit ?
Safari est plus strict sur les en-têtes CORS dupliqués et le contenu mixte ; testez les deux moteurs sur du matériel macOS réel.
Faut-il mettre en cache le prévol indéfiniment ?
Non—utilisez un Access-Control-Max-Age borné pour que les rotations d’en-têtes se propagent en minutes à heures, pas en semaines.
Les WebSockets suivent-ils les mêmes règles CORS ?
La poignée de main est une mise à niveau HTTP avec Origin ; validez-la avec la même logique de liste blanche que pour REST.
La location de Mac mini Apple Silicon vous donne la pile Safari et WebKit que les décideurs utilisent pour démontrer votre UI de passerelle. Les nœuds cloud MacHTML combinent SSH pour des suites curl scriptées et un VNC optionnel lorsque les designers observent le panneau Réseau en direct. La puissance au repos est souvent d’environ 12 W, donc laisser une mini de répétition allumée une semaine de sprint coûte moins souvent que pousser un mauvais en-tête CORS en production et faire un rollback pendant un conseil d’administration.
La location contourne aussi les cycles d’achat lourds : vous payez environ 16,9 $ par jour sur les pages de tarifs publiées au lieu d’acheter une autre machine qui restera inactive après l’intégration. Quand le travail CORS est fini, arrêtez l’instance ; les en-têtes restent dans Git, le matériel ne se déprécie pas sur vos livres sur 36 mois.
Répéter le CORS OpenClaw sur une passerelle macOS réelle
Louez un Mac mini cloud pour valider OPTIONS, requêtes avec identifiants et en-têtes nginx en périphérie dans Safari avant de fusionner les changements de passerelle.