Note
This is my first time reporting an issue, I apologize in advance if I have failed to provide any additional necessary information.
Describe the bug
Two related CORS issues in recent Typebot Viewer versions:
Bug 1 (v3.15.0–3.15.2): The Viewer sends the Access-Control-Allow-Origin response header twice on preflight and regular API requests. Both values are identical, producing:
access-control-allow-origin: https://example.com, https://example.com
Browsers reject this per the CORS spec (must be exactly one origin or *), breaking all embedded typebots loaded from third-party origins.
Bug 2 (v3.14.2, possibly earlier): The Allowed Origins setting under Settings → Security does not work. Adding a valid origin (e.g. https://example.com) causes the published bot to return 403 Forbidden / "Origin not allowed" even when the requesting origin matches exactly. Leaving the field empty works correctly.
To Reproduce
Bug 1 (duplicate header):
- Self-host Typebot v3.15.2 (Viewer + Builder) via Docker.
- Publish a typebot and embed it on a page served from a different origin.
- Open the page in a browser.
- The chatbot fails to load. Browser console shows:
The 'Access-Control-Allow-Origin' header contains multiple values
'https://<embed-domain>, https://<embed-domain>', but only one is allowed.
- Verify directly against the container (no reverse proxy involved):
curl -sI -X OPTIONS \
-H "Origin: https://<embed-domain>" \
-H "Access-Control-Request-Method: POST" \
-H "X-Forwarded-Proto: https" \
-H "X-Forwarded-Host: <viewer-domain>" \
http://localhost:8421/api/v1/typebots/<id>/startChat
Returns two separate access-control-allow-origin lines — confirming the duplication originates in the Viewer, not the reverse proxy.
Bug 2 (allowedOrigins rejection):
- Self-host Typebot v3.14.2 via Docker.
- Open a typebot → Settings → Security → Allowed Origins.
- Add
https://example.com (the exact origin where the bot is embedded).
- Publish the bot.
- Open the embedded page or test with curl:
curl -s -X POST \
-H "Origin: https://example.com" \
-H "Content-Type: application/json" \
https://<viewer-domain>/api/v1/typebots/<id>/startChat \
-d '{}'
- Returns:
{"message":"Origin not allowed","code":"FORBIDDEN"}
- Remove all entries from Allowed Origins → republish → works.
Expected behavior
Bug 1: The Viewer should send the Access-Control-Allow-Origin header exactly once per response, with either the requesting origin or * as the value.
Bug 2: When an origin is listed in Allowed Origins, requests from that origin should be permitted. The origin matching should work regardless of the reverse proxy used.
Additional context
Why Bug 1 may go unnoticed: Nginx and Caddy automatically merge or deduplicate same-name response headers. Reverse proxies like OpenLiteSpeed and direct-access setups do not, exposing the duplicate. Vercel (used for the hosted version) likely normalizes headers server-side as well.
Root cause for Bug 1 (likely): The viewer dependencies include both cors (^2.8.5) and nextjs-cors (^2.1.2). In the compiled route handler (api/[[...rest]]/route.js), the CORS-setting function is called twice per request — once during session/state validation and once during chat start logic. Each call appends the header independently.
Version matrix:
| Version |
Duplicate ACAO header |
allowedOrigins enforcement |
| 3.14.2 |
Not affected |
Broken (rejects valid origins) |
| 3.15.0 |
Not tested |
Not tested |
| 3.15.1 |
Not tested |
Not tested |
| 3.15.2 |
Affected |
Not tested independently |
Workaround: Pin to v3.14.2, leave Allowed Origins empty.
Environment:
- Typebot Viewer/Builder: v3.14.2 and v3.15.2 (Docker)
- Reverse Proxy: OpenLiteSpeed 1.8.4 (CyberPanel)
- OS: Ubuntu 22.04.5 LTS
- Docker Compose v2
``
Note
This is my first time reporting an issue, I apologize in advance if I have failed to provide any additional necessary information.
Describe the bug
Two related CORS issues in recent Typebot Viewer versions:
Bug 1 (v3.15.0–3.15.2): The Viewer sends the
Access-Control-Allow-Originresponse header twice on preflight and regular API requests. Both values are identical, producing:Browsers reject this per the CORS spec (must be exactly one origin or
*), breaking all embedded typebots loaded from third-party origins.Bug 2 (v3.14.2, possibly earlier): The Allowed Origins setting under Settings → Security does not work. Adding a valid origin (e.g.
https://example.com) causes the published bot to return403 Forbidden/"Origin not allowed"even when the requesting origin matches exactly. Leaving the field empty works correctly.To Reproduce
Bug 1 (duplicate header):
Returns two separate
access-control-allow-originlines — confirming the duplication originates in the Viewer, not the reverse proxy.Bug 2 (allowedOrigins rejection):
https://example.com(the exact origin where the bot is embedded).{"message":"Origin not allowed","code":"FORBIDDEN"}Expected behavior
Bug 1: The Viewer should send the
Access-Control-Allow-Originheader exactly once per response, with either the requesting origin or*as the value.Bug 2: When an origin is listed in Allowed Origins, requests from that origin should be permitted. The origin matching should work regardless of the reverse proxy used.
Additional context
Why Bug 1 may go unnoticed: Nginx and Caddy automatically merge or deduplicate same-name response headers. Reverse proxies like OpenLiteSpeed and direct-access setups do not, exposing the duplicate. Vercel (used for the hosted version) likely normalizes headers server-side as well.
Root cause for Bug 1 (likely): The viewer dependencies include both
cors(^2.8.5) andnextjs-cors(^2.1.2). In the compiled route handler (api/[[...rest]]/route.js), the CORS-setting function is called twice per request — once during session/state validation and once during chat start logic. Each call appends the header independently.Version matrix:
Workaround: Pin to v3.14.2, leave Allowed Origins empty.
Environment:
- Typebot Viewer/Builder: v3.14.2 and v3.15.2 (Docker)
- Reverse Proxy: OpenLiteSpeed 1.8.4 (CyberPanel)
- OS: Ubuntu 22.04.5 LTS
- Docker Compose v2
``