Environment
ghcr.io/usefulsoftwareco/executor-selfhost:beta (2.0.0-beta.10), Docker, published on 127.0.0.1:4400
- TLS terminated by a reverse proxy in front of the container
BETTER_AUTH_URL=https://executor.example.com
Symptom
Dashboard cards (for example Tools and Skills on an app's overview) show "Unable to complete this request", and the header shows "Could not reach the server". A full page reload renders the page; client-side navigation fails again, and the Retry button does not always recover. MCP is unaffected.
Cause
The dashboard sends its reads as POST /api/dashboard/batch. sameOrigin in packages/dashboard-start/src/implementation/batch-host.ts requires request.headers.origin === url.origin, where url comes from HttpServerRequest.toURL(request). Inside the container the request is always plain HTTP, so the derived origin is http://<host> while the browser sends Origin: https://<host>. The front proxy in apps/hosted/self-host/native/main.go uses ReverseProxy.Rewrite without SetXForwarded, so an X-Forwarded-Proto header from the outer proxy does not reach the server either. The result is an empty 403.
Reproduce (no sign-in needed)
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://executor.example.com/api/dashboard/batch \
-H 'content-type: application/json' -H 'origin: https://executor.example.com' -d '{}'
# 403
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://executor.example.com/api/dashboard/batch \
-H 'content-type: application/json' -H 'origin: http://executor.example.com' -d '{}'
# 400 (passes the origin check, rejects the empty body)
The same happens directly against 127.0.0.1:4400 with a Host header for the public name, with or without X-Forwarded-Proto: https.
Suggested fix
Compare the Origin header against the configured BETTER_AUTH_URL origin instead of the origin derived from the in-container request, or forward and honor X-Forwarded-Proto.
Workaround
Rewrite Origin from https://<host> to http://<host> at the outer proxy, only for POST /api/dashboard/batch and only when it already equals the instance's own HTTPS origin.
Related
The built-in Executor management app has the setup-time origin written into its source: executorAppSource(origin, ...) in apps/hosted/server/src/implementation/executor-app.ts sets source.url, allowedOrigin and baseUrl. After BETTER_AUTH_URL changes, the app keeps fetching <old origin>/openapi.json and cannot load its tools until it is removed and added again from the catalog.
Environment
ghcr.io/usefulsoftwareco/executor-selfhost:beta(2.0.0-beta.10), Docker, published on127.0.0.1:4400BETTER_AUTH_URL=https://executor.example.comSymptom
Dashboard cards (for example Tools and Skills on an app's overview) show "Unable to complete this request", and the header shows "Could not reach the server". A full page reload renders the page; client-side navigation fails again, and the Retry button does not always recover. MCP is unaffected.
Cause
The dashboard sends its reads as
POST /api/dashboard/batch.sameOrigininpackages/dashboard-start/src/implementation/batch-host.tsrequiresrequest.headers.origin === url.origin, whereurlcomes fromHttpServerRequest.toURL(request). Inside the container the request is always plain HTTP, so the derived origin ishttp://<host>while the browser sendsOrigin: https://<host>. The front proxy inapps/hosted/self-host/native/main.gousesReverseProxy.RewritewithoutSetXForwarded, so anX-Forwarded-Protoheader from the outer proxy does not reach the server either. The result is an empty 403.Reproduce (no sign-in needed)
The same happens directly against
127.0.0.1:4400with aHostheader for the public name, with or withoutX-Forwarded-Proto: https.Suggested fix
Compare the
Originheader against the configuredBETTER_AUTH_URLorigin instead of the origin derived from the in-container request, or forward and honorX-Forwarded-Proto.Workaround
Rewrite
Originfromhttps://<host>tohttp://<host>at the outer proxy, only forPOST /api/dashboard/batchand only when it already equals the instance's own HTTPS origin.Related
The built-in Executor management app has the setup-time origin written into its source:
executorAppSource(origin, ...)inapps/hosted/server/src/implementation/executor-app.tssetssource.url,allowedOriginandbaseUrl. AfterBETTER_AUTH_URLchanges, the app keeps fetching<old origin>/openapi.jsonand cannot load its tools until it is removed and added again from the catalog.