Summary
TaskService.processTasks POSTs to $API_ENDPOINT/api/tasks.execute without an Authorization header. When $API_ENDPOINT points at a public URL fronted by an authentication proxy (Cloudflare Access, Authelia, oauth2-proxy, Traefik Forward Auth, etc.), the proxy responds with a 302 to its own login page. Go's default http.Client follows redirects and converts POST→GET on 302 per RFC 7231, so the resulting GET of the login URL returns 200 OK with an HTML login page. The dispatcher's status check at internal/service/task_service.go:344:
if resp.StatusCode != http.StatusOK { ... }
passes, and the scheduler logs "Task execution request dispatched successfully". The task never actually executes. Broadcasts (and any other task type routed via HTTP dispatch) sit pending indefinitely, with logs misleadingly reporting success every dispatch cycle.
Reproducible with any auth proxy, not just Cloudflare Access. Verified by hand with curl -L -X POST following the same redirect chain: 302 → GET of cloudflareaccess.com/cdn-cgi/access/login/... → HTTP 200, 33 KB HTML body.
Proposed hardening (either one, or both)
-
Refuse redirects on the internal dispatch client. Internal API calls never legitimately need to follow a redirect; if one appears, the request has been intercepted.
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
After this, the dispatcher sees the 302 directly, the status check catches it, and the error branch at task_service.go:350 logs the full response body — surfacing the misconfiguration instead of hiding it.
-
Assert response host matches request host. Defence in depth:
if resp.Request.URL.Host != endpoint.Host {
// auth proxy intercepted us
}
Either change is a few lines and turns a silent failure into a loud one. Happy to open a PR if useful.
Related context
This was originally reported in #317 as a suspected v29.2 regression. The dispatch code is actually unchanged between v29.1 and v29.2 — the bug has been latent forever and only surfaced in our deployment when our auth proxy's bypass rules stopped covering /api/tasks.execute. Closing #317 in favour of this narrower, code-focused framing.
Summary
TaskService.processTasksPOSTs to$API_ENDPOINT/api/tasks.executewithout anAuthorizationheader. When$API_ENDPOINTpoints at a public URL fronted by an authentication proxy (Cloudflare Access, Authelia, oauth2-proxy, Traefik Forward Auth, etc.), the proxy responds with a 302 to its own login page. Go's defaulthttp.Clientfollows redirects and converts POST→GET on 302 per RFC 7231, so the resulting GET of the login URL returns200 OKwith an HTML login page. The dispatcher's status check atinternal/service/task_service.go:344:passes, and the scheduler logs
"Task execution request dispatched successfully". The task never actually executes. Broadcasts (and any other task type routed via HTTP dispatch) sitpendingindefinitely, with logs misleadingly reporting success every dispatch cycle.Reproducible with any auth proxy, not just Cloudflare Access. Verified by hand with
curl -L -X POSTfollowing the same redirect chain: 302 → GET ofcloudflareaccess.com/cdn-cgi/access/login/...→ HTTP 200, 33 KB HTML body.Proposed hardening (either one, or both)
Refuse redirects on the internal dispatch client. Internal API calls never legitimately need to follow a redirect; if one appears, the request has been intercepted.
After this, the dispatcher sees the 302 directly, the status check catches it, and the error branch at
task_service.go:350logs the full response body — surfacing the misconfiguration instead of hiding it.Assert response host matches request host. Defence in depth:
Either change is a few lines and turns a silent failure into a loud one. Happy to open a PR if useful.
Related context
This was originally reported in #317 as a suspected v29.2 regression. The dispatch code is actually unchanged between v29.1 and v29.2 — the bug has been latent forever and only surfaced in our deployment when our auth proxy's bypass rules stopped covering
/api/tasks.execute. Closing #317 in favour of this narrower, code-focused framing.