From 50bf440e244e708f3f9f97beefc47ef951c64511 Mon Sep 17 00:00:00 2001 From: Bruno Perez Date: Thu, 21 May 2026 11:43:22 +0200 Subject: [PATCH] docs: correct Wingman's gateway CORS requirements The cross-origin section was wrong on two counts: it said the Manifest backend allows Wingman "out of the box" (it's dev-only), and it listed only three allowed headers. The agent and SDK profiles send X-Stainless-* headers the list omitted, so following the docs reproduced a CORS preflight failure. - Say the backend allows the Wingman origin in dev mode only - List the X-Stainless-* headers, recommend reflecting request headers - Note the Private Network Access header for loopback gateways over HTTPS --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a040cb9..8b0cce8 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,20 @@ You can pre-fill via query string: `?baseUrl=https://your.gateway&apiKey=mnfst_. ### Cross-origin requirements -Wingman runs at `wingman.manifest.build` and your gateway runs elsewhere, so the gateway must allow the Wingman origin via CORS. The Manifest backend allows it out of the box; self-hosters running a different backend need: +Wingman runs at `wingman.manifest.build` and your gateway runs elsewhere, so the gateway must allow the Wingman origin via CORS. The Manifest backend allows it **in dev mode** (`NODE_ENV !== 'production'`), which covers local and self-hosted-dev gateways. Production builds keep CORS off — the dashboard is same-origin there — so pointing Wingman at a production gateway means opting into CORS for the Wingman origin yourself. + +Whatever the backend, it must answer the preflight with at least: ``` Access-Control-Allow-Origin: https://wingman.manifest.build -Access-Control-Allow-Headers: Authorization, Content-Type, X-API-Key +Access-Control-Allow-Headers: Authorization, Content-Type, X-API-Key, X-Stainless-Lang, X-Stainless-Package-Version, X-Stainless-OS, X-Stainless-Arch, X-Stainless-Runtime, X-Stainless-Runtime-Version, X-Stainless-Retry-Count ``` -`Access-Control-Allow-Credentials` should stay **false** — Wingman uses bearer keys, never cookies. +The `X-Stainless-*` headers matter: the OpenClaw, Hermes, and OpenAI SDK profiles replay them to mimic the real SDK fingerprint, so an allow-list that omits them fails the preflight and the request never leaves the browser. Hermes sends a couple more (`X-Stainless-Async`, `X-Stainless-Read-Timeout`), so the robust option is to reflect the request's `Access-Control-Request-Headers` instead of hard-coding a list. + +If the gateway is on a loopback address (`localhost` / `127.0.0.1`) and you load Wingman over HTTPS, Chrome's Private Network Access also wants `Access-Control-Allow-Private-Network: true` on the preflight. + +`Access-Control-Allow-Credentials` can stay **false** — Wingman uses bearer keys, never cookies. If you're behind a corporate firewall or running a fully air-gapped Manifest, clone this repo and `npm run dev` — Wingman runs entirely client-side.