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

OliveTin: Unauthenticated DoS via concurrent map writes in OAuth2 state handling

CVE-2026-28789

Description

OliveTin gives access to predefined shell commands from a web interface. Prior to version 3000.10.3, an unauthenticated denial-of-service vulnerability exists in OliveTin’s OAuth2 login flow. Concurrent requests to /oauth/login can trigger unsynchronized access to a shared registeredStates map, causing a Go runtime panic (fatal error: concurrent map writes) and process termination. This allows remote attackers to crash the service when OAuth2 is enabled. This issue has been patched in version 3000.10.3.

AI Insight

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

An unauthenticated denial-of-service DoS via concurrent map writes in OliveTin's OAuth2 login flow causes Go runtime panic and process termination.

Vulnerability

OliveTin, a web interface for executing predefined shell commands, contains an unauthenticated denial-of-service vulnerability in its OAuth2 login flow prior to version 3000.10.3. The root cause is unsynchronized access to a shared registeredStates map in the OAuth2 handler. When multiple concurrent requests hit the /oauth/login endpoint, Go's runtime map writes occur without proper locking, triggering a Go runtime panic (fatal error: concurrent map writes) and immediate process termination [1][4].

Exploitation

Exploitation

An attacker can exploit this vulnerability by sending a high volume of concurrent HTTP requests to the /oauth/login endpoint while OAuth2 is enabled. No authentication is required, and the attack is network-reachable via the publicly registered route. The advisory includes a proof-of-concept using 80 workers and 120,000 requests to reliably trigger the crash [4. The vulnerability affects all versions prior to the patch, including container images like ghcr.io/olivetin/olivetin:3000.10.0 [4].

Impact

Successful exploitation results in a complete denial of service: the OliveTin process crashes, making the web interface and all shell command execution unavailable until the service is manually restarted. Since the crash is caused by a runtime panic, there is no graceful recovery. The impact is limited to availability; there is no evidence of data corruption or unauthorized access [1][4].

Mitigation

The issue has been patched in OliveTin version 3000.10.3. The fix introduces a sync.RWMutex to protect all reads and writes to the registeredStates map, as shown in the commit f044d90 [2]. Users should upgrade to the latest version immediately. If upgrading is not possible, disabling OAuth2 authentication is a viable workaround, as the vulnerable code path is only reachable when OAuth2 is enabled [1][4].

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-20260301235225-f044d90d5525c0.0.0-20260301235225-f044d90d5525c

Affected products

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

Patches

1
f044d90d5525

security: Remote crash in OAuth2 GHSA-45m3-398w-m2m9 Thanks @kule500 for the responsible disclosure. CVE to follow.

https://github.com/OliveTin/OliveTinjamesreadMar 1, 2026via ghsa
1 file changed · +27 17
  • service/internal/auth/otoauth2/restapi_auth_oauth2.go+27 17 modified
    @@ -11,6 +11,7 @@ import (
     	"io"
     	"net/http"
     	"os"
    +	"sync"
     	"time"
     
     	authTypes "github.com/OliveTin/OliveTin/internal/auth/authpublic"
    @@ -21,6 +22,7 @@ import (
     
     type OAuth2Handler struct {
     	cfg                 *config.Config
    +	mu                  sync.RWMutex
     	registeredStates    map[string]*oauth2State
     	registeredProviders map[string]*oauth2.Config
     }
    @@ -144,11 +146,13 @@ func (h *OAuth2Handler) HandleOAuthLogin(w http.ResponseWriter, r *http.Request)
     		return
     	}
     
    +	h.mu.Lock()
     	h.registeredStates[state] = &oauth2State{
     		providerConfig: provider,
     		providerName:   providerName,
     		Username:       "",
     	}
    +	h.mu.Unlock()
     
     	h.setOAuthCallbackCookie(w, r, "olivetin-sid-oauth", state)
     
    @@ -177,7 +181,9 @@ func (h *OAuth2Handler) checkOAuthCallbackCookie(w http.ResponseWriter, r *http.
     		return nil, state, false
     	}
     
    +	h.mu.RLock()
     	registeredState, ok := h.registeredStates[state]
    +	h.mu.RUnlock()
     	if !ok {
     		log.Errorf("State not found in server: %v", state)
     		http.Error(w, "State not found in server", http.StatusBadRequest)
    @@ -287,8 +293,10 @@ func (h *OAuth2Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Reque
     	userInfoClient := h.createUserInfoClient(ctx, registeredState.providerConfig, tok, clientSettings)
     	userinfo := getUserInfo(h.cfg, userInfoClient, providerConfig)
     
    +	h.mu.Lock()
     	h.registeredStates[state].Username = userinfo.Username
     	h.registeredStates[state].Usergroup = h.computeUsergroup(userinfo, providerConfig)
    +	h.mu.Unlock()
     
     	http.Redirect(w, r, "/", http.StatusFound)
     }
    @@ -366,34 +374,36 @@ func getDataField(data map[string]any, field string) string {
     	return stringVal
     }
     
    -func (h *OAuth2Handler) CheckUserFromOAuth2Cookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
    -	cookie, err := context.Request.Cookie("olivetin-sid-oauth")
    -
    -	user := &authTypes.AuthenticatedUser{}
    -
    -	if err != nil {
    -		return nil
    +func (h *OAuth2Handler) lookupOAuth2UserByState(state string) (*authTypes.AuthenticatedUser, bool) {
    +	h.mu.RLock()
    +	serverState, found := h.registeredStates[state]
    +	if !found {
    +		h.mu.RUnlock()
    +		return nil, false
    +	}
    +	user := &authTypes.AuthenticatedUser{
    +		Username:      serverState.Username,
    +		UsergroupLine: serverState.Usergroup,
    +		Provider:      "oauth2",
    +		SID:           state,
     	}
    +	h.mu.RUnlock()
    +	return user, true
    +}
     
    -	if cookie.Value == "" {
    +func (h *OAuth2Handler) CheckUserFromOAuth2Cookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
    +	cookie, err := context.Request.Cookie("olivetin-sid-oauth")
    +	if err != nil || cookie.Value == "" {
     		return nil
     	}
     
    -	serverState, found := h.registeredStates[cookie.Value]
    -
    +	user, found := h.lookupOAuth2UserByState(cookie.Value)
     	if !found {
     		log.WithFields(log.Fields{
     			"sid":      cookie.Value,
     			"provider": "oauth2",
     		}).Warnf("Stale session")
    -
     		return nil
     	}
    -
    -	user.Username = serverState.Username
    -	user.UsergroupLine = serverState.Usergroup
    -	user.Provider = "oauth2"
    -	user.SID = cookie.Value
    -
     	return user
     }
    

Vulnerability mechanics

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

References

4

News mentions

0

No linked articles in our index yet.