Desktop Builderlab code exchange fails with HTTP 403 behind Cloudflare geo/WAF (CN direct path); error discards response body
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Describe the bug**
On first-community onboarding, Builderlab browser login completes and returns a one-time `code` to the local callback (`http://127.0.0.1:/callback/?code=...`), but Desktop then fails at:
```text
Builderlab code exchange failed with HTTP 403 Forbidden
```
The UI only surfaces the status line. The response body is discarded, so users cannot tell this is a Cloudflare block page rather than a Builderlab application error.
This is reproducible from a China egress IP when the Desktop process does **not** use an HTTP(S) proxy. The same exchange endpoint succeeds when the request is routed through a non-blocked egress (e.g. JP).
**Root cause (client + edge)**
1. **Cloudflare blocks direct Desktop traffic from some regions**
- Direct POST to `https://app.builderlab.xyz/api/goose/v1/auth/login/exchange` from CN egress returns **HTTP 403** with a Cloudflare HTML page (`Attention Required! | Cloudflare`), `server: cloudflare`, not an Express/business JSON body.
- The same POST via a proxy egress (JP) reaches the app and returns a normal business response (e.g. `400 Unknown login exchange code` for a dummy code, or `200` with `session_credential` for a real code).
2. **Desktop hides the real failure**
In `desktop/src-tauri/src/builderlab.rs` (`start_builderlab_login`), exchange failure is:
```rust
if !response.status().is_success() {
return Err(format!(
"Builderlab code exchange failed with HTTP {}",
response.status()
));
}
```
The body is never read. A CF challenge HTML page and a real API error both collapse to the same user-visible string.
3. **Desktop does not honor typical proxy configuration for GUI launches**
- `reqwest::Client` is built without explicit proxy configuration beyond defaults.
- macOS GUI apps launched from Finder/Dock do **not** inherit shell `HTTP_PROXY`/`HTTPS_PROXY`.
- If system proxy is off, Desktop uses the direct (blocked) path even when the user has a working local proxy for CLI/browser tooling.
- Browser OAuth can still succeed (different path / proxy extension), which makes the failure look like “login worked but Buzz says 403”.
4. **Session is memory-only**
`BuilderlabSession` is an in-process `Mutex>` with no disk/keychain persistence and no injection command. Users cannot recover a session obtained outside the app.
**Steps to reproduce**
1. From a network egress that Cloudflare blocks for `app.builderlab.xyz` (reproduced with CN public IP; system HTTP/HTTPS proxy disabled).
2. Install Buzz Desktop `0.5.2` on macOS.
3. Onboarding → Create a community / I own the community → **Sign in to continue**.
4. Complete Builderlab login in the browser; observe local callback success page (“You’re signed in”).
5. Return to Buzz.
**Actual**
Buzz shows:
```text
Builderlab code exchange failed with HTTP 403 Forbidden
```
Packet-level / curl equivalent of what Desktop is doing:
```bash
# Direct CN egress → Cloudflare HTML 403
curl -i --noproxy '*' -X POST \
'https://app.builderlab.xyz/api/goose/v1/auth/login/exchange' \
-H 'Content-Type: application/json' \
-d '{"code":"x"}'
# HTTP/2 403
# server: cloudflare
# body: Cloudflare "Attention Required!" HTML
# Proxied non-blocked egress → application layer
curl -i -x http://127.0.0.1:7890 -X POST \
'https://app.builderlab.xyz/api/goose/v1/auth/login/exchange' \
-H 'Content-Type: application/json' \
-d '{"code":"x"}'
# HTTP/2 400
# x-powered-by: Express
# body: Unknown login exchange code
```
With a real one-time code from the local callback, proxied exchange returns `200` + `session_credential`, and `/v1/auth/me` succeeds for an account that has `BUILDERLAB_PRODUCT_BUZZ`. So this is not “account lacks product access”; it is the Desktop request path being blocked before Express.
**Expected behavior**
1. Hosted onboarding works from regions where browsers can complete Auth0, **or** Desktop clearly explains that Builderlab API access is blocked (Cloudflare/WAF/geo) and suggests proxy / self-hosted alternatives.
2. On exchange failure, surface a truncated response body / `cf-ray` / content-type so “Cloudflare 403 HTML” is distinguishable from API 403/400.
3. Prefer documenting or supporting proxy for Desktop network (system proxy, or explicit env when launched from a wrapper).
4. Optional: set `Origin: https://app.builderlab.xyz` on exchange for consistency with `authenticated_json` (identity bind already requires Origin).
**Version and platform**
- Buzz version: 0.5.2 (`xyz.block.buzz.app`)
- OS: macOS (darwin arm64)
- Network: CN direct egress blocked by Cloudflare; local proxy `127.0.0.1:7890` works
- Browser login host: `login.builderlab.xyz` / API: `app.builderlab.xyz/api/goose`
**Logs / additional context**
Source references:
- Exchange + error formatting: `desktop/src-tauri/src/builderlab.rs` (`start_builderlab_login`)
- Origin required only on later APIs: same file (`authenticated_json`, `BUILDERLAB_ORIGIN`)
- HTTP client construction: `desktop/src-tauri/src/app_state.rs` (`http_client`)
- Hosted sign-in UI entry: `desktop/src/features/communities/ui/WelcomeSetup.tsx` (`setIsHostedSignInOpen`)
Related UX issues (not the same root cause, but same onboarding surface):
- #2484 Builderlab TLS / Cloudflare friction on login host
- #2816 self-host path hidden behind “Join” while “I own” forces Builderlab
- #2312 hosted vs self-hosted explanation gap
**Suggested fixes (any subset helps)**
1. **Error quality**: on non-2xx exchange, include status + content-type + first N bytes of body / `cf-ray` header in the user-visible error.
2. **Network**: document that Desktop uses the system network stack; for restricted regions, enable system proxy or launch with `HTTPS_PROXY`. Consider respecting macOS system proxy more explicitly if reqwest defaults are insufficient for some setups.
3. **Product**: on repeated exchange 403 with HTML/CF signatures, offer “Connect a self-hosted relay instead” instead of only retrying hosted sign-in.
4. **Parity**: send the same `Origin` header on exchange as other Builderlab calls.
Happy to provide `cf-ray` samples and Desktop version details if useful.
Contributor guide
Assessment
This issue has not been assessed yet.