openclaw - 💡(How to fix) Fix webchat: surface X-Forwarded-User (trusted-proxy) identity to agent message context [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70729Fetched 2026-04-24 05:54:18
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Author
Participants

When the gateway runs with gateway.auth.mode: trusted-proxy behind a reverse proxy that authenticates users (Tailscale, IAP, Pomerium, forward_auth, etc.) and forwards the identity via X-Forwarded-User, the authenticated email is used for session scoping (session keys prefixed with trusted-proxy:<email>) and for authorization (trustedProxy.allowUsers) — but it is not surfaced to the agent's message context. The agent has no way to know which real human is typing.

Other channels handle this correctly — senderId / senderName are threaded into the message event from Slack, Google Chat, Mattermost, etc. Webchat drops the same information on the floor.

Root Cause

When the gateway runs with gateway.auth.mode: trusted-proxy behind a reverse proxy that authenticates users (Tailscale, IAP, Pomerium, forward_auth, etc.) and forwards the identity via X-Forwarded-User, the authenticated email is used for session scoping (session keys prefixed with trusted-proxy:<email>) and for authorization (trustedProxy.allowUsers) — but it is not surfaced to the agent's message context. The agent has no way to know which real human is typing.

Other channels handle this correctly — senderId / senderName are threaded into the message event from Slack, Google Chat, Mattermost, etc. Webchat drops the same information on the floor.

Fix Action

Fix / Workaround

  1. Persist authenticated identity on the WS connection when trusted-proxy auth succeeds. Store { user, source: 'trusted-proxy' } on the connection context at handshake time (next to existing authMethod, authOk).
  2. Inject into chat.send for webchat-originated messages. In the chat.send handler, if the connection has a stored trusted-proxy user and the channel is webchat, set senderId = user and senderName = user on the message event before dispatching to the agent. This matches how other channels already populate those fields, so no agent-side changes are needed.

Code Example

agent.example.com {
  tailscale_auth
  reverse_proxy http://127.0.0.1:18789 {
    header_up X-Forwarded-User {http.auth.user.id}
  }
}

---

{
  "gateway": {
    "auth": {
      "mode": "trusted-proxy",
      "trustedProxy": { "userHeader": "x-forwarded-user" }
    },
    "trustedProxies": ["127.0.0.1", "::1", "172.16.0.0/12"]
  }
}

---

{
  "channel": "webchat",
  "client": "openclaw-control-ui",
  "sessionId": "...",
  "runId": "..."
}
RAW_BUFFERClick to expand / collapse

Summary

When the gateway runs with gateway.auth.mode: trusted-proxy behind a reverse proxy that authenticates users (Tailscale, IAP, Pomerium, forward_auth, etc.) and forwards the identity via X-Forwarded-User, the authenticated email is used for session scoping (session keys prefixed with trusted-proxy:<email>) and for authorization (trustedProxy.allowUsers) — but it is not surfaced to the agent's message context. The agent has no way to know which real human is typing.

Other channels handle this correctly — senderId / senderName are threaded into the message event from Slack, Google Chat, Mattermost, etc. Webchat drops the same information on the floor.

Why it matters

Trusted-proxy is the recommended deployment shape for team installs (proxy terminates TLS + handles SSO, gateway trusts the header). The whole point is "the proxy already knows who's on the other end." Without propagation, agents can't:

  • Greet the user by name
  • Personalize calendars, email, drive lookups (what's on **my** calendar? → agent has to ask, every single session)
  • Do identity-based routing (Tyler from engineering → engineering agent)
  • Attribute audit events to the human, only to the opaque session key

Repro

Deploy gateway behind Caddy w/ tailscale_auth:

agent.example.com {
  tailscale_auth
  reverse_proxy http://127.0.0.1:18789 {
    header_up X-Forwarded-User {http.auth.user.id}
  }
}

With openclaw.json:

{
  "gateway": {
    "auth": {
      "mode": "trusted-proxy",
      "trustedProxy": { "userHeader": "x-forwarded-user" }
    },
    "trustedProxies": ["127.0.0.1", "::1", "172.16.0.0/12"]
  }
}

Caddy's access log correctly shows user_id: [email protected] on every request. The gateway accepts trusted-proxy auth (no rejection in logs, session scoping works — trusted-proxy:[email protected] shows up as the prefix of the session key on disk). But when alice sends a chat message and asks who am I?, the agent gets metadata like:

{
  "channel": "webchat",
  "client": "openclaw-control-ui",
  "sessionId": "...",
  "runId": "..."
}

No email, no name. Agent can't answer.

Code trace

Source: dist/chat-xN4niR21.js (the chat.send handler) takes { params, respond, context, client }. The invocation site in dist/server.impl-*.js does pass req: params.req when delegating from sessions.send to chat.send, but the bundled handler signature doesn't receive req and never calls getHeader(req, auth.trustedProxy.userHeader) the way dist/openresponses-http-*.js does. Compare to dist/channel.runtime-*.js (Google Chat, Slack, Mattermost) where senderId / senderName are explicitly set on the inbound event.

The trusted-proxy user is available at the WebSocket upgrade (that's how session keys are built — see authorizeTrustedProxy in dist/auth-*.js). It just isn't persisted per-connection or re-attached to chat.send events.

Proposed fix

Two-part, both scoped to the gateway package:

  1. Persist authenticated identity on the WS connection when trusted-proxy auth succeeds. Store { user, source: 'trusted-proxy' } on the connection context at handshake time (next to existing authMethod, authOk).
  2. Inject into chat.send for webchat-originated messages. In the chat.send handler, if the connection has a stored trusted-proxy user and the channel is webchat, set senderId = user and senderName = user on the message event before dispatching to the agent. This matches how other channels already populate those fields, so no agent-side changes are needed.

Bonus: surface the same info on sessions.create metadata so the session index file on disk records who owns each session (currently just the prefix encodes it).

Open to contributing

Happy to send a PR if that shape is acceptable. Would want confirmation on:

  • Is senderId / senderName the right pair to populate, or would you prefer a distinct authenticatedUser field so downstream code can distinguish "proxy-authenticated" from "channel-native sender"?
  • Should this be feature-flagged (e.g. gateway.auth.trustedProxy.surfaceAsSender: true) or always-on when trusted-proxy is configured?

Context

Running at BDG in the bustle/openclaw-gcp deployment — Tailscale + Caddy + 10 per-team agents behind agent.bdg.com. Everything else about the trusted-proxy path is working well; this is the last gap before we can do identity-based routing and per-user skills.

extent analysis

TL;DR

The issue can be fixed by persisting the authenticated identity on the WebSocket connection and injecting it into the chat.send handler for webchat-originated messages.

Guidance

  • Verify that the X-Forwarded-User header is being correctly set by the reverse proxy (in this case, Caddy) and that the gateway is configured to trust this header.
  • Implement the proposed fix by storing the authenticated user on the connection context at handshake time and injecting it into the chat.send handler for webchat-originated messages.
  • Consider feature-flagging this behavior to allow for easier testing and rollout.
  • Review the code changes to ensure that the senderId and senderName fields are being populated correctly and that the agent can access this information.

Example

No code snippet is provided as the issue already includes a proposed fix and the exact implementation details may vary.

Notes

The fix should be scoped to the gateway package and should not require any changes to the agent-side code. The proposed fix assumes that the senderId and senderName fields are the correct fields to populate, but this may need to be confirmed.

Recommendation

Apply the proposed workaround by persisting the authenticated identity on the WebSocket connection and injecting it into the chat.send handler for webchat-originated messages, and consider feature-flagging this behavior. This will allow for easier testing and rollout of the fix.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING