Skip to content

[BUG] Viewer sends duplicate Access-Control-Allow-Origin header in v3.15.x; allowedOrigins rejects valid origins in v3.14.2 #2416

@SebiShepherd

Description

@SebiShepherd

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):

  1. Self-host Typebot v3.15.2 (Viewer + Builder) via Docker.
  2. Publish a typebot and embed it on a page served from a different origin.
  3. Open the page in a browser.
  4. 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.
  1. 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):

  1. Self-host Typebot v3.14.2 via Docker.
  2. Open a typebot → Settings → Security → Allowed Origins.
  3. Add https://example.com (the exact origin where the bot is embedded).
  4. Publish the bot.
  5. 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 '{}'
  1. Returns: {"message":"Origin not allowed","code":"FORBIDDEN"}
  2. 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
``

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions