Skip to content

fix(gateway): require auth for control UI avatar route#69775

Merged
drobison00 merged 6 commits into
openclaw:mainfrom
drobison00:fix-control-ui-avatar-auth
Apr 21, 2026
Merged

fix(gateway): require auth for control UI avatar route#69775
drobison00 merged 6 commits into
openclaw:mainfrom
drobison00:fix-control-ui-avatar-auth

Conversation

@drobison00

Copy link
Copy Markdown
Contributor

fix(gateway): require auth for control UI avatar route

Summary

Describe the problem and fix in 2–5 bullets:

If this PR fixes a plugin beta-release blocker, title it fix(<plugin-id>): beta blocker - <summary> and link the matching Beta blocker: <plugin-name> - <summary> issue labeled beta-blocker. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.

  • Problem: The Control UI avatar route served local avatar bytes and ?meta=1 avatar metadata without running the gateway auth path when auth was enabled.
  • Why it matters: Any client that could reach the Control UI HTTP surface could read configured avatar data before authenticating.
  • What changed: The avatar handler now reuses the Control UI read-auth gate, and the UI appends the existing gateway token to avatar metadata and image requests so authenticated dashboards still load avatars.
  • What did NOT change (scope boundary): No CI, gateway auth policy, or non-avatar media behavior changed, and the route remains unchanged when gateway auth is not configured.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause: handleControlUiAvatarRequest(...) applied security headers and served avatar data directly, but unlike the assistant-media route it never ran gateway auth or trusted-proxy scope checks.
  • Missing detection / guardrail: There was no regression coverage asserting that avatar metadata and bytes require auth when Control UI auth is enabled, and the UI avatar fetch path did not propagate the existing gateway token.
  • Contributing context (if known): The Control UI bootstrap and identity responses expose local avatar routes as /avatar/<agentId>, so the client also needed to add auth to those follow-up requests once the server enforced it.

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/gateway/control-ui.http.test.ts, ui/src/ui/app-chat.test.ts, ui/src/ui/chat/grouped-render.test.ts
  • Scenario the test should lock in: Avatar metadata and avatar file responses reject unauthenticated requests when gateway auth is enabled, and the UI includes the gateway token when it fetches avatar metadata or renders a local avatar route.
  • Why this is the smallest reliable guardrail: The server-side risk is in the HTTP handler boundary, and the client-side regression is in URL construction rather than rendering logic alone.
  • Existing test that already covers this (if any): Existing avatar handler tests already covered local file serving and symlink rejection without auth enabled.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Control UI avatar metadata and local avatar image reads now require the same gateway auth path as other protected Control UI reads when auth is enabled.
  • Authenticated dashboards continue to load local avatars because the UI now appends the existing gateway token to avatar metadata and image URLs.

Diagram (if applicable)

Before:
[browser] -> GET /avatar/main?meta=1 -> [avatar handler] -> [metadata returned]
[browser] -> GET /avatar/main        -> [avatar handler] -> [local bytes returned]

After:
[browser + gateway token] -> GET /avatar/main?meta=1&token=... -> [auth gate] -> [metadata returned]
[browser + gateway token] -> GET /avatar/main?token=...        -> [auth gate] -> [local bytes returned]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) Yes
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: The UI now appends the existing gateway auth token to same-origin avatar requests when auth is enabled. This matches the existing assistant-media pattern, keeps token use inside the authenticated dashboard flow, and closes the unauthenticated avatar disclosure by rejecting requests that do not present valid auth.

Repro + Verification

Environment

  • OS: Linux (sandbox task runner)
  • Runtime/container: Node.js v22.14.0, pnpm v10.33.0
  • Model/provider: N/A
  • Integration/channel (if any): Control UI HTTP route
  • Relevant config (redacted): gateway auth enabled in focused route tests; trusted-proxy headers used for scope enforcement coverage

Steps

  1. Run node scripts/run-vitest.mjs run --config test/vitest/vitest.gateway.config.ts src/gateway/control-ui.http.test.ts
  2. Run node scripts/run-vitest.mjs run --config test/vitest/vitest.unit-fast.config.ts ui/src/ui/app-chat.test.ts ui/src/ui/chat/grouped-render.test.ts
  3. Verify the rebased branch still contains only the avatar route auth changes and client token propagation for avatar URLs.

Expected

  • Avatar metadata and avatar bytes require valid gateway auth when auth is configured.
  • Authenticated Control UI avatar fetches still resolve successfully.

Actual

  • The focused gateway and UI tests passed on the rebased branch, covering token-auth success, unauthenticated rejection, trusted-proxy scope rejection, and UI token propagation for avatar URLs.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Inspected the avatar and assistant-media handlers side by side, confirmed the avatar route now uses the same auth gate when configured, and validated the UI now adds the gateway token to avatar metadata and local avatar image URLs.
  • Edge cases checked: Trusted-proxy requests without operator.read, local avatar bytes, remote-avatar metadata, and existing symlink rejection behavior.
  • What you did not verify: A full browser end-to-end Control UI session, or the repo-wide staged hook lane; the repo hook currently fails on unrelated existing gateway tests outside this change.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Mitigation: A Control UI code path outside the tested chat/avatar flows could still render a local avatar URL without the gateway token. The patch updates both the chat avatar metadata fetch and the assistant avatar rendering path, and the server-side auth gate prevents silent pre-auth fallback.

@aisle-research-bot

aisle-research-bot Bot commented Apr 21, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Bearer token sent to arbitrary same-origin paths via unvalidated avatarUrl in refreshChatAvatar
2 🟡 Medium Control UI embeds auth token in assistant-media URLs (token in query string)
1. 🟠 Bearer token sent to arbitrary same-origin paths via unvalidated avatarUrl in refreshChatAvatar
Property Value
Severity High
CWE CWE-201
Location ui/src/ui/app-chat.ts:582-619

Description

The Control UI chat avatar refresh logic performs an authenticated secondary fetch to whatever avatarUrl is returned by the /avatar/<agentId>?meta=1 response.

  • avatarUrl is only validated by isRenderableControlUiAvatarUrl, which allows any absolute same-origin path beginning with / (excluding //).
  • If an attacker can influence the gateway response (e.g., compromised gateway/plugin, malicious agent/avatar config returning kind:"remote" with a local path), the UI will send Authorization: Bearer <token> to an unintended endpoint such as /api/... or /admin/....
  • The response body is then converted to a Blob URL (URL.createObjectURL(await avatarRes.blob())) and stored as the avatar URL, potentially exposing sensitive data through subsequent rendering/debugging and enabling side-effectful privileged GET requests.

Vulnerable code:

if (!authHeader || !isLocalControlUiAvatarUrl(avatarUrl)) {
  setChatAvatarUrl(host, avatarUrl);
  return;
}
const avatarRes = await fetch(avatarUrl, {
  method: "GET",
  headers: { Authorization: authHeader },
});
const blobUrl = URL.createObjectURL(await avatarRes.blob());
setChatAvatarUrl(host, blobUrl);

Recommendation

Constrain the authenticated follow-up fetch to a strict allowlist of expected avatar endpoints, instead of any path starting with /.

Suggested approach:

  • Only allow paths under the avatar prefix (and basePath), e.g. ${normalizeBasePath(basePath)}/avatar/.
  • Parse with new URL(avatarUrl, window.location.origin) and verify same origin and path prefix.
  • Otherwise, do not attach the Authorization header; fall back to setting the URL directly or clearing the avatar.

Example:

function isAllowedAvatarPath(basePath: string, avatarUrl: string): boolean {
  const base = normalizeBasePath(basePath) || "";
  const u = new URL(avatarUrl, window.location.origin);
  if (u.origin !== window.location.origin) return false;
  const allowedPrefix = `${base}/avatar/`;
  return u.pathname.startsWith(allowedPrefix);
}// ...
if (authHeader && isAllowedAvatarPath(host.basePath, avatarUrl)) {
  const avatarRes = await fetch(avatarUrl, { headers: { Authorization: authHeader } });// ...
} else {
  setChatAvatarUrl(host, avatarUrl);
}

Additionally, consider changing the meta response to return only an agent id or a fixed avatar route (not an arbitrary URL) when authentication is required, to avoid client-side token forwarding entirely.

2. 🟡 Control UI embeds auth token in assistant-media URLs (token in query string)
Property Value
Severity Medium
CWE CWE-598
Location ui/src/ui/chat/grouped-render.ts:835-851

Description

The Control UI now appends the gateway auth token as a token query parameter when constructing same-origin __openclaw__/assistant-media URLs.

This is a sensitive-data-in-URL issue:

  • The token is placed in the request URL (?token=...).
  • URLs commonly leak via browser history, server/proxy access logs, monitoring/APM tooling, and can be copied/shared accidentally.
  • Even though the gateway sets Referrer-Policy: no-referrer, that only mitigates Referer leakage; it does not prevent logging/storage of the URL itself.

Vulnerable code:

const params = new URLSearchParams({ source });
const normalizedToken = authToken?.trim();
if (normalizedToken) {
  params.set("token", normalizedToken);
}
return `${normalizedBasePath}/__openclaw__/assistant-media?${params.toString()}`;

Recommendation

Avoid putting authentication tokens in URLs.

Preferred options:

  1. Use an authenticated fetch + Blob URL for media:
    • Request GET /__openclaw__/assistant-media?... with an Authorization: Bearer ... header (or same-site HttpOnly cookie).
    • Convert the response to a Blob and set img.src = URL.createObjectURL(blob).

Example sketch:

const res = await fetch(metaOrMediaUrlWithoutToken, {
  headers: { Authorization: `Bearer ${token}` },
  credentials: "same-origin",
});
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);
img.src = objectUrl;
  1. If headerless embedding is required (e.g., <img src>), use short-lived, scoped, non-reusable signed URLs (not the primary gateway token), and ensure they are not logged.

Additionally:

  • Ensure access logging/redaction explicitly strips token query parameters if any legacy support remains.

Analyzed PR: #69775 at commit 031412a

Last updated on: 2026-04-21T19:42:55Z

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: M maintainer Maintainer-authored PR labels Apr 21, 2026
@greptile-apps

greptile-apps Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes a real security gap: handleControlUiAvatarRequest previously served both avatar bytes and ?meta=1 metadata without running the gateway auth path, exposing them to any client that could reach the Control UI HTTP surface. The fix wires authorizeControlUiReadRequest into the avatar handler — exactly mirroring the existing handleControlUiAssistantMediaRequest pattern — and propagates the gateway token from the UI for both the metadata fetch and the rendered avatar <img> src.

  • The server-side auth gate in control-ui.ts and its wiring in server-http.ts are correctly implemented and consistent with the assistant-media route.
  • One minor UX side-effect: the welcome-state avatar briefly shows a broken image before the first refreshChatAvatar cycle completes (see inline comment), because chatAvatarUrl starts as null and the bootstrap-config raw URL now requires auth.

Confidence Score: 5/5

Safe to merge — the security fix is correctly implemented and no P0/P1 issues were found.

The core auth gate is correctly applied to both the meta and bytes code paths, server wiring mirrors the established assistant-media pattern, and client token propagation handles all URL schemes correctly. The one new inline finding is a brief broken-avatar flash during initial render (P2 UX, not a security concern). Previously surfaced style concerns (relative URL, helper duplication) are not repeated here.

No files require special attention for correctness. ui/src/ui/views/chat.ts has the minor initial-render UX issue noted inline.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/views/chat.ts
Line: 741-750

Comment:
**Briefly broken avatar on initial render when auth is enabled**

`resolveAgentAvatarUrl` prefers `avatarUrl` over `avatar`. On the very first render (before `refreshChatAvatar` completes), `props.assistantAvatarUrl` is `null` so it falls back to `props.assistantAvatar` — the raw bootstrap-config URL without a token. With the avatar route now requiring auth, the browser's `<img>` request for `/avatar/<id>` receives a 401 until the first refresh cycle sets `chatAvatarUrl`. The result is a brief broken-avatar flash in the welcome state whenever auth is enabled.

A lightweight mitigation is to eagerly populate `host.chatAvatarUrl` with the token-appended raw avatar URL at the start of `refreshChatAvatar` (before the async fetch), using the already-available `resolveChatAvatarAuthToken` result, so the very first render already sends a credentialed URL.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "chore: add changelog for control UI avat..." | Re-trigger Greptile

Comment thread ui/src/ui/app-chat.ts Outdated
Comment thread ui/src/ui/app-chat.ts Outdated
@drobison00
drobison00 force-pushed the fix-control-ui-avatar-auth branch 2 times, most recently from 639c958 to 5951299 Compare April 21, 2026 16:18
@drobison00

Copy link
Copy Markdown
Contributor Author

@greptile review
@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59512990c0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-chat.ts Outdated
@drobison00
drobison00 force-pushed the fix-control-ui-avatar-auth branch from 5951299 to 6905f2a Compare April 21, 2026 17:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6905f2a3fc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-chat.ts Outdated
@drobison00
drobison00 force-pushed the fix-control-ui-avatar-auth branch from 6905f2a to 9bc8365 Compare April 21, 2026 17:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf3d97499b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/app-chat.ts
@drobison00
drobison00 force-pushed the fix-control-ui-avatar-auth branch from cf3d974 to 792ba13 Compare April 21, 2026 19:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 031412a032

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/src/ui/chat/grouped-render.ts
@drobison00
drobison00 merged commit 2ce16e5 into openclaw:main Apr 21, 2026
92 checks passed
medikoo pushed a commit to medikoo/openclaw that referenced this pull request Apr 24, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant