VYPR
High severityNVD Advisory· Published Mar 6, 2026· Updated Mar 9, 2026

OliveTin: JWT Audience Validation Bypass in Local Key and HMAC Modes

CVE-2026-30223

Description

OliveTin gives access to predefined shell commands from a web interface. Prior to version 3000.11.1, when JWT authentication is configured using either "authJwtPubKeyPath" (local RSA public key) or "authJwtHmacSecret" (HMAC secret), the configured audience value (authJwtAud) is not enforced during token parsing. As a result, validly signed JWT tokens with an incorrect aud claim are accepted for authentication. This allows authentication using tokens intended for a different audience/service. This issue has been patched in version 3000.11.1.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OliveTin before 3000.11.1 fails to enforce JWT audience validation when using local RSA or HMAC authentication, allowing tokens intended for other services to authenticate.

md:

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/OliveTin/OliveTinGo
< 0.0.0-20260304231339-e97d8ecbd8d60.0.0-20260304231339-e97d8ecbd8d6

Affected products

2
  • Range: <3000.11.1
  • OliveTin/OliveTinv5
    Range: < 3000.11.1

Patches

1
e97d8ecbd8d6

security: GHSA-g962-2j28-3cg9 (HIGH) JWT Audience Validation Bypass in Local Key and HMAC Modes

https://github.com/OliveTin/OliveTinjamesreadMar 3, 2026via ghsa
2 files changed · +40 6
  • service/internal/auth/otjwt/jwt.go+19 5 modified
    @@ -33,6 +33,13 @@ func parseJwtToken(cfg *config.Config, jwtString string) (*jwt.Token, error) {
     	return parseJwtTokenWithHMAC(cfg, jwtString)
     }
     
    +func parserOptionsWithAudience(cfg *config.Config) []jwt.ParserOption {
    +	if cfg.AuthJwtAud == "" {
    +		return nil
    +	}
    +	return []jwt.ParserOption{jwt.WithAudience(cfg.AuthJwtAud)}
    +}
    +
     func getClaimsFromJwtToken(cfg *config.Config, jwtString string) (jwt.MapClaims, error) {
     	token, err := parseJwtToken(cfg, jwtString)
     
    @@ -56,7 +63,8 @@ func parseJwtTokenWithRemoteKey(cfg *config.Config, jwtToken string) (*jwt.Token
     		return nil, err
     	}
     
    -	return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, jwt.WithAudience(cfg.AuthJwtAud))
    +	opts := parserOptionsWithAudience(cfg)
    +	return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, opts...)
     }
     
     var (
    @@ -148,24 +156,30 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token
     		return nil, err
     	}
     
    -	return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
    +	keyFunc := func(token *jwt.Token) (interface{}, error) {
     		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
     			return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
     		}
     
     		return pubKey, nil
    -	})
    +	}
    +
    +	opts := parserOptionsWithAudience(cfg)
    +	return jwt.Parse(jwtString, keyFunc, opts...)
     }
     
     // Hash-based Message Authentication Code
     func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
    -	return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
    +	keyFunc := func(token *jwt.Token) (interface{}, error) {
     		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
     			return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
     		}
     
     		return []byte(cfg.AuthJwtHmacSecret), nil
    -	})
    +	}
    +
    +	opts := parserOptionsWithAudience(cfg)
    +	return jwt.Parse(jwtString, keyFunc, opts...)
     }
     
     func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {
    
  • service/internal/auth/otjwt/jwt_test.go+21 1 modified
    @@ -66,12 +66,19 @@ func newMux() *http.ServeMux {
     }
     
     func createJWTTokenWithExpiration(t *testing.T, privateKey *rsa.PrivateKey, expire int64) string {
    +	return createJWTTokenWithExpirationAndAudience(t, privateKey, expire, "")
    +}
    +
    +func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
     	token := jwt.New(jwt.SigningMethodRS256)
     	claims := token.Claims.(jwt.MapClaims)
     	claims["nbf"] = time.Now().Unix() - 1000
     	claims["exp"] = time.Now().Unix() + expire
     	claims["sub"] = "test"
     	claims["olivetinGroup"] = "test"
    +	if audience != "" {
    +		claims["aud"] = audience
    +	}
     
     	tokenStr, err := token.SignedString(privateKey)
     	if err != nil {
    @@ -108,6 +115,10 @@ func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
     }
     
     func testJwkValidation(t *testing.T, expire int64, expectCode int) {
    +	testJwkValidationWithAudience(t, expire, expectCode, "", "")
    +}
    +
    +func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
     	privateKey, publicKeyPath := createKeys(t)
     	defer os.Remove(publicKeyPath)
     
    @@ -116,8 +127,9 @@ func testJwkValidation(t *testing.T, expire int64, expectCode int) {
     	cfg.AuthJwtClaimUsername = "sub"
     	cfg.AuthJwtClaimUserGroup = "olivetinGroup"
     	cfg.AuthJwtHeader = "Authorization"
    +	cfg.AuthJwtAud = configAudience
     
    -	tokenStr := createJWTTokenWithExpiration(t, privateKey, expire)
    +	tokenStr := createJWTTokenWithExpirationAndAudience(t, privateKey, expire, tokenAudience)
     	handler := setupJWTTestHandler(t, cfg)
     
     	srv := httptest.NewServer(handler)
    @@ -135,6 +147,14 @@ func TestJWTSignatureVerificationFails(t *testing.T) {
     	testJwkValidation(t, -500, 403)
     }
     
    +func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) {
    +	testJwkValidationWithAudience(t, 1000, 403, "expected-audience", "wrong-audience")
    +}
    +
    +func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) {
    +	testJwkValidationWithAudience(t, 1000, 200, "expected-audience", "expected-audience")
    +}
    +
     func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups interface{}) string {
     	token := jwt.New(jwt.SigningMethodRS256)
     	claims := token.Claims.(jwt.MapClaims)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.