Skip to content

feat: inject visitor identity headers into proxied workspace app requests - #27929

Open
ianp-rr wants to merge 1 commit into
coder:mainfrom
ianp-rr:visitor-identity-headers
Open

feat: inject visitor identity headers into proxied workspace app requests#27929
ianp-rr wants to merge 1 commit into
coder:mainfrom
ianp-rr:visitor-identity-headers

Conversation

@ianp-rr

@ianp-rr ianp-rr commented Aug 6, 2026

Copy link
Copy Markdown

Adds opt-in visitor identity headers to workspace app proxying, so apps running inside a workspace can identify the authenticated Coder user visiting them (for example, to attribute actions in shared apps or drive app-level authorization).

Three deployment settings control which headers are injected, all default off:

  • --proxy-header-pass-user-idX-Coder-User-Id
  • --proxy-header-pass-usernameX-Coder-Username
  • --proxy-header-pass-user-emailX-Coder-User-Email

Incoming X-Coder-* request headers are always stripped before proxying to prevent spoofing, regardless of settings. Visitor identity is captured in the signed app token at issuance, so header injection adds no per-request database lookups. Unauthenticated (public app) requests carry no identity, so no headers are injected for them. The settings are marked for external workspace proxies and wired through coder wsproxy server, so behavior is consistent on both the built-in proxy and external proxies.

Security notes
  • Header stripping is not bypassable by case tricks: Go canonicalizes HTTP/1.1 header names.
  • No CRLF injection risk: usernames are restricted by Coder's username regex, and http.Header.Set rejects control characters.
  • App tokens are HMAC-signed; visitor fields cannot be forged client-side.
  • Email is PII and is only injected when an admin explicitly enables --proxy-header-pass-user-email.
  • Visitor fields are intentionally excluded from SignedToken.MatchesRequest; cookie scoping ensures each browser session carries its own signed token.

Verification

Integration tests spin up a full deployment with a workspace app that echoes received X-Coder-* request headers back as response headers, then assert injection, spoof-stripping, and per-setting selectivity through a real proxied request:

$ go test ./enterprise/ -run 'TestWorkspaceApps/.*/(InjectsCoderIdentityHeaders|StripsCoderIdentityHeadersFromClient|SelectiveHeaderInjection)' -count=1 -v
    --- PASS: TestWorkspaceApps/WorkspaceAppsProxyPath (0.00s)
        --- PASS: TestWorkspaceApps/WorkspaceAppsProxyPath/SelectiveHeaderInjection (6.10s)
        --- PASS: TestWorkspaceApps/WorkspaceAppsProxyPath/InjectsCoderIdentityHeaders (6.10s)
        --- PASS: TestWorkspaceApps/WorkspaceAppsProxyPath/StripsCoderIdentityHeadersFromClient (6.14s)
ok      github.com/coder/coder/v2/enterprise    7.719s

Test_ResolveRequest (token issuance) also passes with the new visitor fields.

AI disclosure

Per the AI Contribution guidelines: this PR was developed with AI assistance (Claude) under my direction, and I take ownership of its content. The change has been verified as described above.

…ied app requests

Add configurable HTTP header injection for workspace app proxying. When
enabled, the proxy injects X-Coder-User-Id, X-Coder-Username, and
X-Coder-User-Email headers identifying the authenticated visitor. Each
header is gated by an independent deployment setting (default off).

Incoming X-Coder-* headers are always stripped to prevent spoofing,
regardless of whether injection is enabled. Visitor identity is baked
into the signed app token at issuance time to avoid per-request DB
lookups.

New deployment flags:
  --proxy-header-pass-user-id
  --proxy-header-pass-username
  --proxy-header-pass-user-email

The flags are marked for external workspace proxies and wired through
the wsproxy server command so behavior is consistent on external
proxies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ianp-rr
ianp-rr marked this pull request as ready for review August 6, 2026 19:47
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.

1 participant