Skip to content

fix(nginx): stop Control UI Bearer tokens from clobbering basic auth - #97

Merged
andrasbacsai merged 4 commits into
mainfrom
cursor/merge-control-ui-bearer-auth-b194
Oct 19, 2026
Merged

andrasbacsai merged 4 commits into
mainfrom
cursor/merge-control-ui-bearer-auth-b194

Conversation

@andrasbacsai

Copy link
Copy Markdown
Member

Fixes #65

Replacement for #87. This branch merges @codezz's fix/control-ui-bearer-vs-basic-auth (head dcaa92bd) into current main with a merge commit, so @codezz's commits stay in the history. Please merge this PR with a merge commit, not squash or rebase. Squashing would drop dcaa92bd, and GitHub would not mark #87 as merged.

#87

What landed

@codezz fixed the repeated basic-auth prompt: the Control UI sends Authorization: Bearer, which replaces the browser's Basic credentials, and nginx answers 401 + WWW-Authenticate: Basic. nginx now skips basic auth only for the exact gateway routes that Bearer request is for, forwards the client's header, and does not append its own token= query parameter. /, the WebSocket, /rpc, and /browser/ stay on basic auth.

configure.js sets gateway.trustedProxies to 127.0.0.1 and ::1 when that list is not already set. nginx overwrites X-Forwarded-For with the TCP peer while basic auth is on, so OpenClaw's auth rate limit is not stuck on loopback. gateway.auth.mode stays token.

The merge onto current main was clean. Both sides are in the tree:

scripts/nginx-bearer.test.js builds its basic-auth password with crypto.randomBytes on each run, so the file no longer contains a password literal.

Tests

  • node scripts/utils.test.js: 292 passed
  • node scripts/nginx-bearer.test.js: 55 passed
  • node scripts/smoke.js inside the image built from OPENCLAW_GIT_REF=v2026.10.5: openclaw ok: OpenClaw 2026.10.5 (1b58cb9)

Image build used Dockerfile.base then Dockerfile, same as CI. pnpm build && pnpm ui:build completed in one layer. The build log has no EXDEV.

With basic auth, Playwright loaded the Control UI, submitted the gateway token, and a one-time device approval connected the session. No browser dialog, and no 401 with WWW-Authenticate: Basic after the page loaded.

Control UI connected after device approval

Open in Web Open in Cursor 

codezz and others added 4 commits September 16, 2026 12:49
The Control UI authenticates to the gateway with `Authorization: Bearer
<token>`. A request carries only one Authorization header, so on every fetch
the UI makes itself, that Bearer replaces the browser's Basic credentials.
auth_basic cannot parse it, returns 401 + WWW-Authenticate, and the browser
prompts again. Entering the correct password cannot help: the next UI fetch
overwrites the header again.

This is the same class of bug as #37 (webhook Authorization header overwritten
by the nginx proxy), and it is fixed the same way PR #38 fixed that one: skip
basic auth for the affected routes and pass the client's token through so the
gateway validates it.

Scoped to routes the gateway itself authenticates (/api/, /__openclaw__/, /rpc,
/control-ui-config.json). "/" is deliberately excluded: the gateway serves the
UI shell unauthenticated, so exempting it would let any bogus Bearer past basic
auth. The nginx-injected `token=` query parameter is suppressed on passthrough
requests for the same reason, otherwise the carve-out would hand out
authenticated access instead of delegating it.

When AUTH_PASSWORD is unset the generated config is byte-identical to before.

Refs #65
Skip basic auth only for the exact paths the Control UI fetches with
Authorization: Bearer, and forward the client's header instead of
injecting the gateway token. Anchor every regex. Keep /, the WebSocket,
and /browser/ on basic auth, and leave the generated config unchanged
when AUTH_PASSWORD is unset.

Trust the loopback nginx hop via gateway.trustedProxies and overwrite
X-Forwarded-For with the TCP peer so auth rate limits are not stuck on
127.0.0.1.

Co-authored-by: Andras Bacsai <andrasbacsai@users.noreply.github.com>
…openclaw

Co-authored-by: Andras Bacsai <andrasbacsai@users.noreply.github.com>
A fixed password literal in nginx-bearer.test.js was flagged by secret
scanners. The harness only needs any non-empty basic-auth password, so
build one from random bytes on each run.

Co-authored-by: Andras Bacsai <andrasbacsai@users.noreply.github.com>
@gitguardian

gitguardian Bot commented Oct 19, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
36526964 Triggered Generic Password dcaa92b scripts/nginx-bearer.test.js View secret
36526964 Triggered Generic Password 7d742c6 scripts/nginx-bearer.test.js View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@andrasbacsai
andrasbacsai marked this pull request as ready for review October 19, 2026 12:51
@andrasbacsai
andrasbacsai merged commit 7478e22 into main Oct 19, 2026
2 of 3 checks passed
@batmanscode

Copy link
Copy Markdown
Contributor

With basic auth, Playwright loaded the Control UI, submitted the gateway token, and a one-time device approval connected the session. No browser dialog, and no 401 with WWW-Authenticate: Basic after the page loaded.

Will this be needed everytime one logs into the control UI? i.e. need to always do the device approval

@codezz

codezz commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@batmanscode device approval should be done once, unless you clear cookies from what I tested

@batmanscode

Copy link
Copy Markdown
Contributor

@batmanscode device approval should be done once, unless you clear cookies from what I tested

Thanks :D

Off the top of your head do you see a way for login via any browser (without cookie based device approval?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nginx Basic Auth Re-prompts Continuously After Login (Regression in Latest Versions)

4 participants