Skip to content

fix(cors): allow loopback origins + deny cleanly instead of 500#1

Open
tmlxrd wants to merge 1 commit into
mainfrom
fix/cors-loopback
Open

fix(cors): allow loopback origins + deny cleanly instead of 500#1
tmlxrd wants to merge 1 commit into
mainfrom
fix/cors-loopback

Conversation

@tmlxrd

@tmlxrd tmlxrd commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

The CORS origin callback called cb(new Error()) for any origin not in ALLOWED_ORIGINS, which makes the cors middleware forward to next(err) → a 500 on the OPTIONS preflight. Browsers surface that as a useless "Failed to fetch".

And iClaw is local-first: the desktop app and the npx build POST share ciphertext from http://127.0.0.1:<ephemeral-port>, which can't be enumerated in a static allow-list — so every chat-share upload hit that 500.

Fix

  • Accept any loopback HTTP origin (127.0.0.1 / localhost / [::1], any port). The Origin header is browser-set (can't be forged cross-site) and the share API only ever stores opaque ciphertext.
  • Deny other origins with cb(null, false) — a clean CORS denial, no 500.

Verified

Live test against the real cors package:

  • loopback origin → 204 + Access-Control-Allow-Origin
  • allow-listed origin → 204 + ACAO ✅ (unchanged)
  • disallowed origin → clean deny, no ACAO, no 500

tsc --noEmit clean.

Note: the iClaw client also got a local proxy (iClaw 2c77b23) that makes share work regardless — this is the root-cause fix on the cloud side. Merging to main deploys to app.iclaw.digital.

🤖 Generated with Claude Code

The CORS origin callback called cb(new Error()) for any origin not in
ALLOWED_ORIGINS, which makes the cors middleware forward to next(err) → a 500 on
the OPTIONS preflight (the browser just reports "Failed to fetch"). And iClaw is
local-first: the desktop app and the npx build POST from http://127.0.0.1:<port>
with an ephemeral port that can't be enumerated in a static allow-list — so every
share upload hit that 500.

Accept any loopback HTTP origin (127.0.0.1 / localhost / [::1], any port) — the
Origin header is browser-set and the share API only stores opaque ciphertext —
and deny other origins with cb(null, false), a clean CORS denial (no 500).

Verified: OPTIONS → 204 + ACAO for loopback & allow-listed origins; clean deny
(no ACAO, no 500) for others; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant