Pomerium exposed OAuth2 access and ID tokens in user info endpoint response
Description
Pomerium is an identity and context-aware access proxy. Prior to version 0.26.1, the Pomerium user info page (at /.pomerium) unintentionally included serialized OAuth2 access and ID tokens from the logged-in user's session. These tokens are not intended to be exposed to end users. This issue may be more severe in the presence of a cross-site scripting vulnerability in an upstream application proxied through Pomerium. If an attacker could insert a malicious script onto a web page proxied through Pomerium, that script could access these tokens by making a request to the /.pomerium endpoint. Upstream applications that authenticate only the ID token may be vulnerable to user impersonation using a token obtained in this manner. Note that an OAuth2 access token or ID token by itself is not sufficient to hijack a user's Pomerium session. Upstream applications should not be vulnerable to user impersonation via these tokens provided the application verifies the Pomerium JWT for each request, the connection between Pomerium and the application is secured by mTLS, or the connection between Pomerium and the application is otherwise secured at the network layer. The issue is patched in Pomerium v0.26.1. No known workarounds are available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/pomerium/pomeriumGo | < 0.26.1 | 0.26.1 |
Affected products
1Patches
2eb8dc899e9ce4c7c4320afb2core/userinfo: remove excess userinfo data (#5148)
2 files changed · +59 −30
internal/handlers/userinfo.go+59 −12 modified@@ -1,11 +1,8 @@ package handlers import ( - "encoding/json" "net/http" - "google.golang.org/protobuf/encoding/protojson" - "github.com/pomerium/datasource/pkg/directory" "github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/pkg/grpc/identity" @@ -39,15 +36,9 @@ func (data UserInfoData) ToJSON() map[string]any { m := map[string]any{} m["csrfToken"] = data.CSRFToken m["isImpersonated"] = data.IsImpersonated - if bs, err := protojson.Marshal(data.Session); err == nil { - m["session"] = json.RawMessage(bs) - } - if bs, err := protojson.Marshal(data.User); err == nil { - m["user"] = json.RawMessage(bs) - } - if bs, err := protojson.Marshal(data.Profile); err == nil { - m["profile"] = json.RawMessage(bs) - } + m["session"] = data.sessionJSON() + m["user"] = data.userJSON() + m["profile"] = data.profileJSON() m["isEnterprise"] = data.IsEnterprise if data.DirectoryUser != nil { m["directoryUser"] = data.DirectoryUser @@ -62,6 +53,62 @@ func (data UserInfoData) ToJSON() map[string]any { return m } +func (data UserInfoData) profileJSON() map[string]any { + if data.Profile == nil { + return nil + } + + m := map[string]any{} + claims := make(map[string]any) + for k, v := range data.Profile.GetClaims().AsMap() { + claims[k] = v + } + m["claims"] = m + return m +} + +func (data UserInfoData) sessionJSON() map[string]any { + if data.Session == nil { + return nil + } + + m := map[string]any{} + claims := make(map[string]any) + for k, vs := range data.Session.GetClaims() { + claims[k] = vs.AsSlice() + } + m["claims"] = claims + var deviceCredentials []any + for _, dc := range data.Session.GetDeviceCredentials() { + deviceCredentials = append(deviceCredentials, map[string]any{ + "typeId": dc.GetTypeId(), + "id": dc.GetId(), + }) + } + m["deviceCredentials"] = deviceCredentials + m["expiresAt"] = data.Session.GetExpiresAt().AsTime() + m["id"] = data.Session.GetId() + m["userId"] = data.Session.GetUserId() + return m +} + +func (data UserInfoData) userJSON() map[string]any { + if data.User == nil { + return nil + } + + m := map[string]any{} + claims := make(map[string]any) + for k, vs := range data.User.GetClaims() { + claims[k] = vs.AsSlice() + } + m["claims"] = claims + m["deviceCredentialIds"] = data.User.GetDeviceCredentialIds() + m["id"] = data.User.GetId() + m["name"] = data.User.GetName() + return m +} + // UserInfo returns a handler that renders the user info page. func UserInfo(data UserInfoData) http.Handler { return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
ui/src/types/index.ts+0 −18 modified@@ -14,35 +14,17 @@ export type Group = { }; export type Profile = { - providerId: string; - idToken: string; - oauthToken: string; claims: Record<string, unknown>; }; export type Session = { - audience: string[]; claims: Claims; deviceCredentials: Array<{ typeId: string; id: string; }>; expiresAt: string; id: string; - idToken: { - expiresAt: string; - issuedAt: string; - issuer: string; - raw: string; - subject: string; - }; - issuedAt: string; - oauthToken: { - accessToken: string; - expiresAt: string; - refreshToken: string; - tokenType: string; - }; userId: string; };
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-rrqr-7w59-637vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-39315ghsaADVISORY
- github.com/pomerium/pomerium/commit/4c7c4320afb2ced70ba19b46de1ac4383f3daa48ghsax_refsource_MISCWEB
- github.com/pomerium/pomerium/security/advisories/GHSA-rrqr-7w59-637vghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.