CORS_ORIGIN names a host the site is not served from, with a trailing slash #212
Labels
No Label
P0
P1
P2
P3
accessibility
backend
bug
content
data-integrity
enhancement
frontend
infra
integration
owner
owner-input
performance
phase-7
phase-8
release-blocker
security
seo
ui
ux
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: null/Queue-North-Website#212
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by
scripts/check-env.shon 2026-08-18, running against the deployed container's environment.What is true now
CORS_ORIGIN=https://queuenorth.com/— with a trailing slash.docker-compose.yml:23commitsCORS_ORIGIN=https://queuenorth.com— without one. The running environment has drifted from the repository.Dockerfile:48hasENV CORS_ORIGIN=*as the image default.https://qn.isnull.dev. None of the three names it.Confirmed against production:
What it costs — and what it does not. Nothing is broken today, and the issue says so rather than overstating it.
src/lib/api.js:1defaultsAPI_BASE_URLto/api, so the site's own forms are same-origin and never trigger a CORS check. And because the configured value carries a trailing slash it matches no real browser origin, so no cross-origin request is permitted either — it fails closed, which is the safe direction.So this is wrong and latent, not wrong and live. It becomes live the moment anything is served from a different origin than the API, or the site moves to queuenorth.com and the trailing slash silently blocks every form on it.
What to do. Decide which origin is correct first — that is the part this issue cannot decide:
qn.isnull.devis the permanent home, setCORS_ORIGIN=https://qn.isnull.devindocker-compose.ymland in the deployed environment.queuenorth.comis where this is going, keep the host and drop the trailing slash.docker-compose.yml, since they currently disagree.The trap.
Dockerfile:48'sCORS_ORIGIN=*is the fallback if the compose environment ever stops being applied. A wildcard withcredentials: true— whichserver/index.js:138sets — is a combination browsers reject outright, so that failure would present as every form breaking at once rather than as a security hole. Worth knowing which symptom to expect.Why filed and not fixed. Choosing the origin requires knowing whether queuenorth.com is intended to become the public home of this deployment. That is the owner's answer, not a code change.
Verify:
curl -X OPTIONS https://qn.isnull.dev/api/leads -H 'Origin: https://evil.example' -H 'Access-Control-Request-Method: POST' -D -returns anaccess-control-allow-originnaming the origin the site is actually served from, with no trailing slash, anddocker-compose.ymlmatches the deployed environment.Webhook note, recorded here because this issue is the one about configuration drift.
The repository webhook to privacyllc.dev was registered on 2026-08-18 and initially rejected every delivery with
401 bad_signature— it had been created without the shared signing secret. It now carriesFORGEJO_WEBHOOK_SECRET. Caught by checking that a delivery actually arrived rather than by assuming registration was enough.Decision made by
_nullon 2026-08-18:queuenorth.comis the permanent public origin.qn.isnull.devstays as a second ingress to the same container.That answers the question this issue was blocked on. It also corrected a factual error in the original report and in
docs/OPERATIONS.md, both of which said queuenorth.com was not this deployment. It is:assets/index-pTFwovIx.jsand this server's own/api/healthshape.queuenorth.com->24.41.108.95, which is this network's own public IP -> nginx-proxy-manager on thor/exodus ->qn-website-devon nebula.qn.isnull.dev-> Cloudflare -> the same container.www.queuenorth.com301s to the apex. Both origins carry HSTS and 301 plain HTTP;preflight.shis clean against each.Done in the repository (commit to follow this comment):
Dockerfile— the fallback wasENV CORS_ORIGIN=*, nowhttps://queuenorth.com. Worth stating why that mattered: the server setscredentials: true, and browsers reject*with credentials outright, so the wildcard fallback would have broken every form rather than over-permitting.docker-compose.yml— already correct,https://queuenorth.comwith no slash. No change needed.scripts/healthcheck.shandscripts/preflight.shnow default to the production origin, with the second front door reachable viaHEALTHCHECK_BASE_URL/PREFLIGHT_ORIGIN.check-env.sh's description for this variable said the default was "NOT this deployment". Corrected.What is left, and it is one character.
The wrong value is not in this repository. It is in the Portainer stack that actually runs the site:
https://192.168.1.11:9443, stack id 58 (qn-website-dev), file at/data/compose/58/docker-compose.yml.- CORS_ORIGIN=https://queuenorth.com/<- the trailing slash.That file is a separate copy from the one in git and the two have drifted. Editing the repository does not change production.
Why this has not been done. Fixing it means redeploying stack 58, which recreates the container and takes both public front doors down together for a few seconds — a restart of a customer-facing site. Nothing is broken today: the site's own forms are same-origin so no CORS check ever runs, and the trailing slash makes the header match no real origin, which fails closed rather than open. It is worth folding into the next deploy that happens for another reason rather than restarting production for a latent issue.
docs/OPERATIONS.mdnow documents the whole deploy path, which was an open gap when this issue was filed.Verify (unchanged):
curl -X OPTIONS https://queuenorth.com/api/leads -H 'Origin: https://evil.example' -H 'Access-Control-Request-Method: POST' -D -returnsaccess-control-allow-origin: https://queuenorth.comwith no trailing slash, and Portainer stack 58 line 21 matches this repository'sdocker-compose.yml.Fixed and DEPLOYED in v0.9.4 on 2026-08-18.
The wrong value was never in this repository — docker-compose.yml has always been correct. It was line 21 of Portainer stack 58's own compose file, a separate copy that had drifted. Corrected by
bash scripts/deploy.sh --tag v0.9.4 --fix-cors, which rewrote that line in the same PUT that moved the image, so production restarted once rather than twice.Decision that unblocked it: _null confirmed queuenorth.com as the permanent public origin.
Live evidence, after the deploy:
No trailing slash, and it names the origin the site is actually served from. The stack file and this repository's docker-compose.yml now agree on CORS_ORIGIN.
Also verified in the same deploy: both front doors healthy, the live bundle still carries the reCAPTCHA site key, and the leads table is unchanged at 3 rows.
Verify: the curl above returns
access-control-allow-origin: https://queuenorth.comwith no trailing slash, and line 21 of Portainer stack 58 matches docker-compose.yml.