You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Verify CKAN from a browser origin against ckan.tacc.utexas.edu — is CORS actually broken? #71
The working assumption is that ckan.tacc.utexas.edu needs CORS configuration before ui-react can call it from the browser. That is a three-week-old guess and has never been checked. It may be a no-op.
Check it directly: load the app on a non-tacc.utexas.edu origin (a Vercel preview will do) and exercise /datasets/browse, /datasets/search, and a region's dataset list. Watch the network panel for blocked preflights and the console for CORS errors. A curl -I -H 'Origin: https://…' against a CKAN action endpoint settles the header question in seconds.
Record the verdict as: CORS is fine (close T6 unopened) or CORS is broken, with the exact failing request and the response headers CKAN actually sends.
Context
This surface is young and unexercised. The CKAN client landed in PR #61 on 2026-07-19, and PR #63 patched a URL-conflation bug in it the next day. Issue #59 tracks remaining DATA_CATALOG_API cleanup. config.ts defaults both DATA_CATALOG_API and DATA_CATALOG_BROWSE_URL to https://ckan.tacc.utexas.edu.
Verdict: CORS is fine for production, broken for Vercel previews.
The three-week-old guess was half right, and the half that matters is the one nobody expected.
ckan.tacc.utexas.edu does send CORS headers, but Access-Control-Allow-Origin is echoed only for an allowlisted set of origins. Every other CORS header (-Allow-Methods, -Allow-Headers, -Expose-Headers, -Allow-Credentials, Vary: Origin) is sent
unconditionally, to every origin, which makes the response look CORS-enabled at a glance.
It is not — without Access-Control-Allow-Origin the browser blocks the read regardless.
So: *.tacc.utexas.edu (subdomains only, not the apex), plus localhost and 127.0.0.1 on
any scheme and port. Nothing else.
Uniform across endpoints — identical result on package_search (with and without ext_bbox), package_show, organization_list and status_show. It is an nginx origin match, not
per-action logic. CKAN 2.11.5, Server: nginx.
What this means for the app
ckanAction in ui-react/src/lib/datasets/ckan.ts issues a plain GET with only Accept: application/json and no credentials. That is a CORS simple request — no preflight
is involved, so the OPTIONS behaviour is a red herring. The request goes out, CKAN answers 200, and the browser discards the body. It surfaces as a TypeError from fetch, not as an
HTTP error.
Production (https://mint.tacc.utexas.edu) — works today. It matches the allowlist. No
CORS change is needed from TACC, and none is needed from us.
Local dev and a locally-run container — work today.localhost and 127.0.0.1 are
allowlisted, so npm run dev can be pointed straight at TACC's real CKAN.
Vercel previews — broken, and unfixable from our side.*.vercel.app is not allowlisted.
The nginx proxy would only ever serve the deployed container, and the deployed container sits
at mint.tacc.utexas.edu, which is already allowlisted. It fixes a problem that does not exist
there, and it cannot fix the one place that is actually broken (Vercel previews do not pass
through that nginx). Closed with a pointer here.
DATA_CATALOG_API and DATA_CATALOG_BROWSE_URL both stay pointed at the real host in #75.
No new config value falls out of this ticket.
Consequence 2: #76 cannot exercise /datasets/* from a Vercel preview
#76 left this open as "exercised through whatever #74 lands on — or deferred to #77". Neither
branch survives: #74 is dead, and deferring the entire data-catalog surface to the production
flip means first contact with CKAN happens after the cutover.
There is a third option #76 did not consider, and it is the cheap one: run the /datasets/* leg from localhost, which CKAN already allows. npm run dev (or the container
run locally) pointed at TACC's real Hasura and CKAN exercises the real data on an allowlisted
origin, with no proxy and no request to TACC. The rest of #76 stays on the Vercel preview as
written. Noted on #76.
This is a constraint on the test surface only. It is not a defect in ui-react, and it
changes nothing about the production deployment.
Amendment (2026-08-08, later the same day): the Vercel gap is closed
Max added ~^https://.*\.vercel\.app$ $http_origin; to the map $http_origin $cors_origin
block on CKAN's nginx and reloaded. Re-measured and confirmed live:
https://ui-react-git-develop-mint.vercel.app now gets Access-Control-Allow-Origin echoed
on package_search (with and without ext_bbox), package_show, organization_list and status_show, all 200, all with Vary: Origin.
The OPTIONS preflight also echoes it (204), so non-simple requests would work too.
The verdict above is superseded for Vercel only. Everything else in it still holds — the
allowlist mechanism, the "looks CORS-enabled but isn't" trap, the simple-request/no-preflight
analysis, and the reason #74 stays closed
(the container-nginx proxy was moot on its own terms: it only ever served the origin that
already worked).
The localhost workaround imposed on #76
is lifted — the /datasets/* leg goes back onto the Vercel preview with the rest of the test.
Caveat worth knowing
*.vercel.app is a public suffix anyone can deploy to, and this location block sends Access-Control-Allow-Credentials: true. So any Vercel-hosted page can now make credentialed
cross-origin calls to CKAN and read the responses with a visiting user's CKAN cookies attached.
The pre-existing ~^https://.*\.github\.io$ entry has the same property, so this is a
widening of an existing posture, not a new class of exposure — and CKAN here is a public
catalog, which limits what it buys an attacker.
ui-react does not need the credentialed path: ckanAction uses a plain fetch with no credentials: 'include', so it never sends cookies. If TACC wants this tightened, pinning the
regex to the actual preview project (e.g. ~^https://ui-react-[a-z0-9-]+\.vercel\.app$) costs
nothing here. Flagged, not blocking — TACC's infra, TACC's call.
Amendment 2 (2026-08-08): allowlist narrowed to the two real Vercel origins
The broad ~^https://.*\.vercel\.app$ from Amendment 1 was replaced with anchored,
exact-in-effect entries, and ~^https://.*\.github\.io$ was dropped at the same time. Live map $http_origin $cors_origin entries for this app:
Re-measured after reload — allowed: both Vercel origins, mint.tacc.utexas.edu, localhost:5173, 127.0.0.1:3000. Refused: *.github.io, bare *.vercel.app, evil.example.com, and http:// on the Vercel host. All four CKAN actions the app calls
(package_search with and without ext_bbox, package_show, organization_list) return 200 with the header from the branch alias, and the OPTIONS preflight echoes it.
This closes the credentialed-access caveat from Amendment 1. No public-suffix wildcard
remains in the allowlist, so the Access-Control-Allow-Credentials: true on that block is no
longer reachable by arbitrary third-party pages.
Smoke-test against the branch alias, https://monorepo-git-develop-mosoriobs-projects.vercel.app.
Per-deployment preview URLs (monorepo-<hash>-mosoriobs-projects.vercel.app) are not
allowlisted and cannot be — the hash changes on every push. The branch alias points at the
latest develop build, so it is the same artifact with a stable origin. A tester who grabs the
per-deployment URL out of the Vercel dashboard will see every /datasets/* call fail as a fetch TypeError with no HTTP error; that is this allowlist, not a defect.
Anyone testing a different branch needs its alias added to the map first — one line per
branch, same shape.
Note on the nginx config, for whoever touches it next
Quoted (non-regex) map keys longer than ~64 bytes need map_hash_bucket_size 128;, and
adding that to nginx.conf on this host fails with a spurious directive is duplicate error
that we did not get to the bottom of — only one copy exists anywhere in the include closure,
and 64 in the first error was just nginx's default, not a second setting. Anchored regex keys
sidestep the hash table entirely, which is why the entries above are written as regexes rather
than quoted strings. Use that form here.
Unrelated but worth cleaning: /etc/nginx/conf.d/ holds six stale backups plus a letsencrypt-backup.tar.gz, saved from being parsed only by the *.conf glob on line 99 —
while line 100 (include /etc/nginx/sites-enabled/*) has no extension filter at all.
Activity
mosoriob commentedon Aug 8, 2026
Verdict: CORS is fine for production, broken for Vercel previews.
The three-week-old guess was half right, and the half that matters is the one nobody expected.
ckan.tacc.utexas.edudoes send CORS headers, butAccess-Control-Allow-Originis echoedonly for an allowlisted set of origins. Every other CORS header (
-Allow-Methods,-Allow-Headers,-Expose-Headers,-Allow-Credentials,Vary: Origin) is sentunconditionally, to every origin, which makes the response look CORS-enabled at a glance.
It is not — without
Access-Control-Allow-Originthe browser blocks the read regardless.The allowlist, measured
curl -o /dev/null -D - -H 'Origin: <o>' https://ckan.tacc.utexas.edu/api/3/action/status_showAccess-Control-Allow-Originhttps://mint.tacc.utexas.eduhttps://anything.tacc.utexas.eduhttp://localhost:5173/:3000/ no porthttp://127.0.0.1:5173https://tacc.utexas.edu(apex)https://x.vercel.apphttps://mint.isi.eduhttps://evil.example.comSo:
*.tacc.utexas.edu(subdomains only, not the apex), pluslocalhostand127.0.0.1onany scheme and port. Nothing else.
Uniform across endpoints — identical result on
package_search(with and withoutext_bbox),package_show,organization_listandstatus_show. It is an nginx origin match, notper-action logic. CKAN 2.11.5,
Server: nginx.What this means for the app
ckanActioninui-react/src/lib/datasets/ckan.tsissues a plainGETwith onlyAccept: application/jsonand no credentials. That is a CORS simple request — no preflightis involved, so the
OPTIONSbehaviour is a red herring. The request goes out, CKAN answers200, and the browser discards the body. It surfaces as aTypeErrorfromfetch, not as anHTTP error.
https://mint.tacc.utexas.edu) — works today. It matches the allowlist. NoCORS change is needed from TACC, and none is needed from us.
localhostand127.0.0.1areallowlisted, so
npm run devcan be pointed straight at TACC's real CKAN.*.vercel.appis not allowlisted.Consequence 1: #74 is moot — closed unopened
The nginx proxy would only ever serve the deployed container, and the deployed container sits
at
mint.tacc.utexas.edu, which is already allowlisted. It fixes a problem that does not existthere, and it cannot fix the one place that is actually broken (Vercel previews do not pass
through that nginx). Closed with a pointer here.
DATA_CATALOG_APIandDATA_CATALOG_BROWSE_URLboth stay pointed at the real host in #75.No new config value falls out of this ticket.
Consequence 2: #76 cannot exercise
/datasets/*from a Vercel preview#76 left this open as "exercised through whatever #74 lands on — or deferred to #77". Neither
branch survives: #74 is dead, and deferring the entire data-catalog surface to the production
flip means first contact with CKAN happens after the cutover.
There is a third option #76 did not consider, and it is the cheap one: run the
/datasets/*leg fromlocalhost, which CKAN already allows.npm run dev(or the containerrun locally) pointed at TACC's real Hasura and CKAN exercises the real data on an allowlisted
origin, with no proxy and no request to TACC. The rest of #76 stays on the Vercel preview as
written. Noted on #76.
This is a constraint on the test surface only. It is not a defect in
ui-react, and itchanges nothing about the production deployment.
mosoriob commentedon Aug 8, 2026
Amendment (2026-08-08, later the same day): the Vercel gap is closed
Max added
~^https://.*\.vercel\.app$ $http_origin;to themap $http_origin $cors_originblock on CKAN's nginx and reloaded. Re-measured and confirmed live:
https://ui-react-git-develop-mint.vercel.appnow getsAccess-Control-Allow-Originechoedon
package_search(with and withoutext_bbox),package_show,organization_listandstatus_show, all200, all withVary: Origin.OPTIONSpreflight also echoes it (204), so non-simple requests would work too.http://x.vercel.app(wrong scheme),https://vercel.app(apex),
https://x.vercel.app.evil.com(suffix spoof),https://tacc.utexas.edu(apex),https://evil.example.com.The verdict above is superseded for Vercel only. Everything else in it still holds — the
allowlist mechanism, the "looks CORS-enabled but isn't" trap, the simple-request/no-preflight
analysis, and the reason #74 stays closed
(the container-nginx proxy was moot on its own terms: it only ever served the origin that
already worked).
The
localhostworkaround imposed on #76is lifted — the
/datasets/*leg goes back onto the Vercel preview with the rest of the test.Caveat worth knowing
*.vercel.appis a public suffix anyone can deploy to, and thislocationblock sendsAccess-Control-Allow-Credentials: true. So any Vercel-hosted page can now make credentialedcross-origin calls to CKAN and read the responses with a visiting user's CKAN cookies attached.
The pre-existing
~^https://.*\.github\.io$entry has the same property, so this is awidening of an existing posture, not a new class of exposure — and CKAN here is a public
catalog, which limits what it buys an attacker.
ui-reactdoes not need the credentialed path:ckanActionuses a plainfetchwith nocredentials: 'include', so it never sends cookies. If TACC wants this tightened, pinning theregex to the actual preview project (e.g.
~^https://ui-react-[a-z0-9-]+\.vercel\.app$) costsnothing here. Flagged, not blocking — TACC's infra, TACC's call.
mosoriob commentedon Aug 8, 2026
Amendment 2 (2026-08-08): allowlist narrowed to the two real Vercel origins
The broad
~^https://.*\.vercel\.app$from Amendment 1 was replaced with anchored,exact-in-effect entries, and
~^https://.*\.github\.io$was dropped at the same time. Livemap $http_origin $cors_originentries for this app:Re-measured after reload — allowed: both Vercel origins,
mint.tacc.utexas.edu,localhost:5173,127.0.0.1:3000. Refused:*.github.io, bare*.vercel.app,evil.example.com, andhttp://on the Vercel host. All four CKAN actions the app calls(
package_searchwith and withoutext_bbox,package_show,organization_list) return200with the header from the branch alias, and theOPTIONSpreflight echoes it.This closes the credentialed-access caveat from Amendment 1. No public-suffix wildcard
remains in the allowlist, so the
Access-Control-Allow-Credentials: trueon that block is nolonger reachable by arbitrary third-party pages.
Constraint for #76
Smoke-test against the branch alias,
https://monorepo-git-develop-mosoriobs-projects.vercel.app.Per-deployment preview URLs (
monorepo-<hash>-mosoriobs-projects.vercel.app) are notallowlisted and cannot be — the hash changes on every push. The branch alias points at the
latest
developbuild, so it is the same artifact with a stable origin. A tester who grabs theper-deployment URL out of the Vercel dashboard will see every
/datasets/*call fail as afetchTypeError with no HTTP error; that is this allowlist, not a defect.Anyone testing a different branch needs its alias added to the map first — one line per
branch, same shape.
Note on the nginx config, for whoever touches it next
Quoted (non-regex)
mapkeys longer than ~64 bytes needmap_hash_bucket_size 128;, andadding that to
nginx.confon this host fails with a spuriousdirective is duplicateerrorthat we did not get to the bottom of — only one copy exists anywhere in the include closure,
and
64in the first error was just nginx's default, not a second setting. Anchored regex keyssidestep the hash table entirely, which is why the entries above are written as regexes rather
than quoted strings. Use that form here.
Unrelated but worth cleaning:
/etc/nginx/conf.d/holds six stale backups plus aletsencrypt-backup.tar.gz, saved from being parsed only by the*.confglob on line 99 —while line 100 (
include /etc/nginx/sites-enabled/*) has no extension filter at all.