VYPR
Critical severityNVD Advisory· Published Mar 27, 2026· Updated Mar 27, 2026

OpenBao has Reflected XSS in its OIDC authentication error message

CVE-2026-33758

Description

OpenBao is an open source identity-based secrets management system. Prior to version 2.5.2, OpenBao installations that have an OIDC/JWT authentication method enabled and a role with callback_mode=direct configured are vulnerable to XSS via the error_description parameter on the page for a failed authentication. This allows an attacker access to the token used in the Web UI by a victim. The error_description parameter has been replaced with a static error message in v2.5.2. The vulnerability can be mitigated by removing any roles with callback_mode set to direct.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/openbao/openbaoGo
< 0.0.0-20260325133417-6e2b2dd84f0e0.0.0-20260325133417-6e2b2dd84f0e

Affected products

1

Patches

1
6e2b2dd84f0e

Resolve GHSA-cpj3-3r2f-xj59 (#2709)

https://github.com/openbao/openbaoGian KlugMar 25, 2026via ghsa
4 files changed · +73 17
  • builtin/credential/jwt/html_responses.go+23 11 modified
    @@ -3,7 +3,11 @@
     
     package jwtauth
     
    -import "fmt"
    +import (
    +	"encoding/json"
    +	"fmt"
    +	"html"
    +)
     
     const successHTML = `
     <!DOCTYPE html>
    @@ -159,7 +163,7 @@ const successHTML = `
              <span class="icon">
               <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
         <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/>
    -  </svg> 
    +  </svg>
               </span>
               Check out the official OpenBao documentation
             </a>
    @@ -170,7 +174,7 @@ const successHTML = `
     `
     
     func errorHTML(summary, detail string) string {
    -	const html = `
    +	const htmlTmpl = `
     <!DOCTYPE html>
     <html lang="en" >
     
    @@ -234,7 +238,7 @@ hr {
     }
     .message.is-danger .message-title {
       color: #7f222c;
    -  
    +
     }
     .message .message-body {
       border: 0;
    @@ -327,7 +331,7 @@ h1 + p {
           <div class="message is-danger">
            <svg width="20" height="20" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
       <path d="M19 3c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h14zm-2 12.59L13.41 12 17 8.41 15.59 7 12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59z"></path>
    -</svg> 
    +</svg>
             <div class="message-content">
               <div class="message-title">
                 %s
    @@ -344,7 +348,7 @@ h1 + p {
            <span class="icon">
             <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
       <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/>
    -</svg> 
    +</svg>
             </span>
             Check out the official OpenBao documentation
           </a>
    @@ -354,11 +358,11 @@ h1 + p {
     
     </html>
     `
    -	return fmt.Sprintf(html, summary, detail)
    +	return fmt.Sprintf(htmlTmpl, html.EscapeString(summary), html.EscapeString(detail))
     }
     
     func formpostHTML(path, code, state string) string {
    -	const html = `
    +	const htmlTmpl = `
     <!DOCTYPE html>
     <html lang="en">
     <head>
    @@ -509,17 +513,25 @@ func formpostHTML(path, code, state string) string {
              <span class="icon">
               <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
         <path d="M13.307 1H11.5a.5.5 0 1 1 0-1h3a.499.499 0 0 1 .5.65V3.5a.5.5 0 1 1-1 0V1.72l-1.793 1.774a.5.5 0 0 1-.713-.701L13.307 1zM12 14V8a.5.5 0 1 1 1 0v6.5a.5.5 0 0 1-.5.5H.563a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 .5-.5H8a.5.5 0 0 1 0 1H1v12h11zM4 6a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1H4zm0 2.5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1H4zM4 11a.5.5 0 1 1 0-1h5a.5.5 0 1 1 0 1H4z"/>
    -  </svg> 
    +  </svg>
               </span>
               Check out the official OpenBao documentation
             </a>
           </div>
         </div>
     	<script>
    -		window.opener.postMessage({ source: 'oidc-callback', path: "%s", code: "%s", state: "%s"}, window.origin);
    +		window.opener.postMessage({ source: 'oidc-callback', path: %s, code: %s, state: %s}, window.origin);
     	</script>
       </body>
     </html>
     `
    -	return fmt.Sprintf(html, path, code, state)
    +	return fmt.Sprintf(htmlTmpl, jsStr(path), jsStr(code), jsStr(state))
    +}
    +
    +// jsStr returns JSON-encoded string literal, safe to embed directly in a <script> block
    +// adds surrounding quotes
    +func jsStr(s string) string {
    +	// json.Marshal on a string returns no errors
    +	b, _ := json.Marshal(s)
    +	return string(b)
     }
    
  • builtin/credential/jwt/path_oidc.go+40 4 modified
    @@ -10,6 +10,7 @@ import (
     	"net/http"
     	"net/url"
     	"slices"
    +	"strconv"
     	"strings"
     	"time"
     
    @@ -36,6 +37,17 @@ const (
     	noCode = "no_code"
     )
     
    +// RFC 6749 §4.1.2.1 defined error codes for Authorization Code Grant error responses
    +const (
    +	oidcErrInvalidRequest          = "invalid_request"
    +	oidcErrUnauthorizedClient      = "unauthorized_client"
    +	oidcErrAccessDenied            = "access_denied"
    +	oidcErrUnsupportedResponseType = "unsupported_response_type"
    +	oidcErrInvalidScope            = "invalid_scope"
    +	oidcErrServerError             = "server_error"
    +	oidcErrTemporarilyUnavailable  = "temporarily_unavailable"
    +)
    +
     // oidcRequest represents a single OIDC authentication flow. It is created when
     // an authURL is requested. It is uniquely identified by a state, which is passed
     // throughout the multiple interactions needed to complete the flow.
    @@ -85,8 +97,17 @@ func pathOIDC(b *jwtAuthBackend) []*framework.Path {
     					Type:  framework.TypeString,
     					Query: true,
     				},
    +				"error": {
    +					Type:  framework.TypeString,
    +					Query: true,
    +				},
     				"error_description": {
    -					Type: framework.TypeString,
    +					Type:  framework.TypeString,
    +					Query: true,
    +				},
    +				"error_uri": {
    +					Type:  framework.TypeString,
    +					Query: true,
     				},
     			},
     
    @@ -266,9 +287,24 @@ func (b *jwtAuthBackend) pathCallback(ctx context.Context, req *logical.Request,
     		deleteRequest = false
     	}
     
    -	errorDescription := d.Get("error_description").(string)
    -	if errorDescription != "" {
    -		return loginFailedResponse(useHttp, errorDescription), nil
    +	// error parameter per OpenID Connect Core 1.0 spec
    +	// If present, the login has failed
    +	oidcError := strings.ToLower(strings.TrimSpace(d.Get("error").(string)))
    +	if oidcError != "" {
    +		// strconv.Quote - for log-safe string output.
    +		b.Logger().Warn("OIDC callback received error from provider",
    +			"error", strconv.Quote(oidcError),
    +			"error_description", strconv.Quote(d.Get("error_description").(string)),
    +			"error_uri", strconv.Quote(d.Get("error_uri").(string)),
    +		)
    +		switch oidcError {
    +		case oidcErrInvalidRequest, oidcErrUnauthorizedClient, oidcErrAccessDenied,
    +			oidcErrUnsupportedResponseType, oidcErrInvalidScope, oidcErrServerError,
    +			oidcErrTemporarilyUnavailable:
    +			return loginFailedResponse(useHttp, oidcError), nil
    +		default:
    +			return loginFailedResponse(useHttp, "An unknown error occurred during OIDC authentication. Check server logs for details"), nil
    +		}
     	}
     
     	clientNonce := d.Get("client_nonce").(string)
    
  • changelog/2709.txt+3 0 added
    @@ -0,0 +1,3 @@
    +```release-note:security
    +auth/jwt: prevent XSS via `error_description` parameter in `callback_mode=direct` auth methods. CVE-2026-33758.
    +```
    
  • website/content/api-docs/auth/jwt.mdx+7 2 modified
    @@ -468,8 +468,13 @@ or direct callback modes, and is not used in device callback mode.
     - `client_nonce` `(string: <optional>)` - Optional client-provided nonce that must
       match the `client_nonce` value provided during the prior request to the
       [auth_url](jwt.mdx#oidc-authorization-url-request) API.
    -- `error_description` `(string: <optional>)` - Detailed description of an error if there was an error.
    -  If present, will be included in the error message passed back to the requester.
    +- `error` `(string: <optional>)` - Error code from the OIDC provider per OpenID Connect Core 1.0 spec.
    +  If present, indicates the authorization failed. Known RFC 6749 error codes are returned to the
    +  requester; unrecognized codes result in a static failure message. The raw value is always logged server-side.
    +- `error_description` `(string: <optional>)` - Human-readable error description from OIDC provider.
    +  Logged server-side only.
    +- `error_uri` `(string: <optional>)` - URI of a human-readable error page from the OIDC provider.
    +  Logged server-side only.
     
     ### Sample request
     
    

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

6

News mentions

0

No linked articles in our index yet.