From 3529fe0da16e9f79b34ce9d13cfcaf4035b9c751 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 8 Apr 2026 12:26:06 +0000 Subject: [PATCH] types: fix OIDC identifier path traversal dropping subject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit url.JoinPath resolves path-traversal segments like '..' and '.', which silently drops the OIDC subject from the identifier. For example, Iss='https://example.com' with Sub='..' produces 'https://example.com' — the subject is lost entirely. This causes distinct OIDC users to receive colliding identifiers. Replace url.JoinPath with simple string concatenation using a slash separator. This preserves the subject literally regardless of its content. url.PathEscape does not help because dots are valid URL path characters and are not escaped. --- hscontrol/types/users.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/hscontrol/types/users.go b/hscontrol/types/users.go index 2593bda0..92ed73ad 100644 --- a/hscontrol/types/users.go +++ b/hscontrol/types/users.go @@ -304,21 +304,14 @@ func (c *OIDCClaims) Identifier() string { subject := c.Sub var result string - // Try to parse as URL to handle URL joining correctly - if u, err := url.Parse(issuer); err == nil && u.Scheme != "" { //nolint:noinlineerr - // For URLs, use proper URL path joining - if joined, err := url.JoinPath(issuer, subject); err == nil { //nolint:noinlineerr - result = joined - } - } - - // If URL joining failed or issuer wasn't a URL, do simple string join - if result == "" { - // Default case: simple string joining with slash - issuer = strings.TrimSuffix(issuer, "/") - subject = strings.TrimPrefix(subject, "/") - result = issuer + "/" + subject - } + // Always use simple string concatenation with a slash separator. + // url.JoinPath resolves path-traversal segments like ".." and ".", + // which can silently drop the subject and cause identifier collisions + // between distinct OIDC users (e.g., Sub=".." produces the same + // identifier as an empty Sub). + issuer = strings.TrimSuffix(issuer, "/") + subject = strings.TrimPrefix(subject, "/") + result = issuer + "/" + subject // Clean the result and return it return CleanIdentifier(result)