Topic: [Bug] Duplicate Access-Control-Allow-Origin header makes the API unusable from browsers

Posted under Site Bug Reports & Feature Requests

Summary

The API sends the header Access-Control-Allow-Origin twice in every response that carries an Origin header. The Fetch spec allows exactly one value, so browsers reject the response - regardless of which origin asks. As a result, no web page can talk to the blep.art API, even though the API help page explicitly advertises that it can.

Steps to reproduce

Request any endpoint with an Origin header:

curl -sS -o /dev/null -D - -H "Origin: https://example.org" \
  "https://blep.art/post_sets.json?limit=1" | grep -i access-control-allow-origin

Actual output:

access-control-allow-origin: *
access-control-allow-origin: *

Expected: one such line.

Evidence

Header count on several endpoints:

blep.art  /                        with Origin: 2   without Origin: 1
blep.art  /post_sets.json          with Origin: 2   without Origin: 1
blep.art  /posts.json              with Origin: 2   without Origin: 1
blep.art  /forum_topics.json       with Origin: 2   without Origin: 1
blep.art  /help/api                with Origin: 2   without Origin: 1

So one header is sent unconditionally, and a second one is appended when an Origin is present. The preflight is fine - OPTIONS returns 200 with Access-Control-Allow-Methods including PATCH - which makes the failure confusing, because the preflight succeeds and only the real request is dropped.

In the browser every cross-origin request fails with a CORS error:

TypeError: Failed to fetch

Tested with headless Chromium from file:// and from http://localhost:8765, against endpoints that are readable without logging in (/post_sets.json?limit=1). Same result everywhere, so it is not an authentication or credentials problem.

Why this looks blep-specific

e6ai.net runs the same codebase and behaves correctly - exactly one header, and only when Origin is present:

e6ai.net  /post_sets/705.json      with Origin: 1   without Origin: 0

So the duplicate is most likely introduced in this deployment: the Rails app sets a static Access-Control-Allow-Origin: * unconditionally, and an additional CORS layer (middleware or a Cloudflare rule) adds another one for the actual request origin. Two identical values are still invalid - the header must appear once.

Suggested fix

Send the header from exactly one layer:

  • either drop the static header from the app and let the CORS layer handle it (this also matches e6ai.net, which sends nothing when there is no Origin),
  • or drop the CORS layer and keep the static header.

Worth checking that no reverse-proxy or Cloudflare rule adds a header too, since the count only grows from 1 to 2 when an Origin is present.

Impact

  • Any browser-based tool, bookmarklet or userscript that reads from the API is impossible to build. The API help page promises the opposite ("GET and POST requests are possible cross-origin"), so people will keep running into this and assume their own setup is broken.
  • With the header fixed, the API becomes usable from the browser for GET and POST, which is exactly what the documentation already describes.
  • Command-line clients are unaffected, which is probably why this went unnoticed so far.

I ran into this while writing a small userscript, and worked around it by running inside the site. The API itself stays unusable from a plain web page until the header appears only once.

Happy to re-test and confirm once it is changed - a single curl as shown above is enough to verify.