Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Network-accessible unauthenticated endpoint, no interaction or privileges required; impact is availability degradation only with no confidentiality or integrity exposure, and no scope change occurs.
Primary rating from Vendor (https://github.com/forgekeep/nebula-mesh).
CVSS VectorVendor: https://github.com/forgekeep/nebula-mesh
Lifecycle Timeline
3DescriptionCVE.org
Summary
When OIDC is enabled, GET /ui/oidc/login is reachable without authentication and is registered outside the Web UI rate-limited auth routes. Every request creates a fresh random OIDC state value and stores it in an in-memory map for 10m. Expired states are swept lazily, but there is no rate limit or maximum live-state cap on the allocation path. An unauthenticated remote client can therefore grow OIDC.states for the full state TTL, bounded by request throughput rather than by configured auth rate limits.
Details
The OIDC login route is registered directly by WithOIDC:
internal/web/web.go:153registersw.router.Get("/ui/oidc/login", o.HandleLogin).internal/web/web.go:154rate-limits onlyGET /ui/oidc/callbackwithw.rateLimitMiddleware("auth").
The normal /ui/* route group applies rate limiting to login/register form submissions, but this direct registration happens outside that group:
internal/web/web.go:287throughinternal/web/web.go:292show the rate-limited local login, TOTP, and register POST routes.
The OIDC login handler allocates persistent server-side state before redirecting to the configured identity provider:
internal/web/oidc.go:105definesHandleLogin.internal/web/oidc.go:106creates a random state token.internal/web/oidc.go:112callso.rememberState(state).internal/web/oidc.go:122redirects too.oauth.AuthCodeURL(state).
The state storage has a TTL but no maximum size:
internal/web/oidc.go:24throughinternal/web/oidc.go:26defineoidcStateTTL = 10 * time.Minute.internal/web/oidc.go:353throughinternal/web/oidc.go:358sweep expired states and then add the new state too.states.internal/web/oidc.go:360throughinternal/web/oidc.go:373delete only expired states.
Because the route is unauthenticated and not rate-limited, a remote client can repeatedly request /ui/oidc/login and force live state entries to accumulate for ten minutes. OIDC must be enabled for exposure. No IdP callback, valid credentials, or user interaction is required to trigger the allocation.
Affected version evidence: OIDC login support was introduced by commit 3f46685 (feat(auth): add OIDC operator login (Keycloak/Authentik/Okta/...) (#24)), and git tag --contains 3f46685 --sort=version:refname returns v0.2.0 and every later release through v0.3.8. Pattern checks across all release tags showed the OIDC login route and state allocation are present in v0.2.0 and in every v0.3.x release from v0.3.0 to v0.3.8, and absent from v0.1.x. The current checkout at commit d92dd9a60de291e2bc1caf73b4e9a99567b31ec0 (git describe: v0.3.8-1-gd92dd9a) remains affected.
PoC
Safe local PoC run from a clean checkout at commit d92dd9a60de291e2bc1caf73b4e9a99567b31ec0 on 2026-06-12. The PoC is a temporary Go test that uses httptest and an in-memory OIDC object; it does not start a real server, does not contact an IdP, and uses 1000 requests only to demonstrate linear state growth.
- Create a temporary test file
internal/web/security_audit_poc_test.goin packageweb. - Create a test Web UI with
newTestWeb(t). - Install a deliberately tiny auth rate limiter: group
authwith rate0.001and burst2. - Attach an
OIDCinstance with an emptystatesmap and anoauth2.Configwhose authorization endpoint ishttps://idp.example.test/auth. - Send 1000 unauthenticated
GET /ui/oidc/loginrequests from the sameRemoteAddrthroughw.ServeHTTP. - Assert no request returns
429 Too Many Requests, then inspecto.stateCount().
Command run:
go test ./internal/web -run 'TestSecurityAuditPOC' -count=1 -vObserved vulnerable output from this environment:
=== RUN TestSecurityAuditPOC_OIDCLoginAllocatesUnrateLimitedState
POC_OIDC_STATE_GROWTH attempts=1000 live_states=1000 ttl=10m0s rate_limit_group=auth_burst_2
--- PASS: TestSecurityAuditPOC_OIDCLoginAllocatesUnrateLimitedState (0.10s)The meaningful control is that local login/register/TOTP POST routes and the OIDC callback are rate-limited: internal/web/web.go:287 through internal/web/web.go:292 and internal/web/web.go:154. The PoC specifically shows the OIDC login allocation route does not share that protection. After recording the output, the temporary test file was removed and git status --short returned clean. The PoC was re-run after drafting this report and produced the output shown above.
Impact
In deployments with OIDC enabled, an unauthenticated remote client can cause application-layer memory growth by repeatedly requesting /ui/oidc/login. Each request stores a new state entry for ten minutes, and the growth is not bounded by the configured auth rate limiter or by a maximum map size. The demonstrated impact is availability degradation risk through retained in-memory state growth. The PoC used 1000 local requests to avoid disruptive load while proving the source-to-sink behavior (1000 requests resulted in 1000 live states).
Suggested remediation: apply the existing auth rate limiter to GET /ui/oidc/login, add a maximum number of live OIDC states per client and/or globally, and fail closed when the cap is reached. Add a regression test that attaches a low-burst auth limiter, sends repeated GET /ui/oidc/login requests from the same client, and expects 429 or bounded live-state count after the configured burst.
AnalysisAI
Unbounded in-memory state growth in nebula-mesh's OIDC login endpoint allows unauthenticated remote clients to cause availability degradation by flooding GET /ui/oidc/login, which allocates a new server-side state entry per request for a 10-minute TTL with no rate limit and no maximum live-state cap. Affected versions span v0.2.0 through v0.3.8; the attack requires only that OIDC be enabled and requires no credentials, IdP interaction, or user involvement. A publicly available PoC demonstrates 1,000 requests producing 1,000 live state entries with zero 429 responses; no public exploit identified at time of analysis as actively exploited (not in CISA KEV), though the low-barrier PoC meaningfully reduces the effort required.
Technical ContextAI
nebula-mesh is a Go-based network mesh management web UI (pkg:go/github.com/forgekeep/nebula-mesh). The affected component is the OIDC authentication subsystem introduced in v0.2.0 via commit 3f46685. The root cause is CWE-400 (Uncontrolled Resource Consumption): HandleLogin in internal/web/oidc.go generates a random state token per request and stores it in an in-memory map (o.states) with a 10-minute TTL but no entry ceiling. The route is registered directly by WithOIDC at internal/web/web.go:153, bypassing the rate-limited /ui/* group that protects local login, TOTP, and register POST routes (web.go:287-292). The OIDC callback does receive rate limiting (web.go:154), but the login initiation path does not. Lazy sweeping of expired entries (oidc.go:353-373) cannot reclaim entries within their TTL window, so a flood of requests accumulates non-expiring state entries faster than they age out. The OAuth2 authorization code flow inherently requires server-side state for CSRF protection, but the implementation lacked both throttling and a hard cap.
RemediationAI
Upgrade to nebula-mesh v0.5.0, which is confirmed fixed via commit bc387086cc0e4b9c1654468b7391af19cacfe367 (https://github.com/forgekeep/nebula-mesh/commit/bc387086cc0e4b9c1654468b7391af19cacfe367) and the release at https://github.com/forgekeep/nebula-mesh/releases/tag/v0.5.0. The fix introduces a hard cap of 4,096 concurrent pending OIDC states (oidcMaxLiveStates = 4096); when the ceiling is reached, HandleLogin returns HTTP 503 with a Retry-After: 60 header rather than allocating further entries. If an immediate upgrade is not feasible, disabling OIDC and reverting to local-account authentication eliminates the vulnerable endpoint entirely (trade-off: loss of SSO functionality). Alternatively, placing a reverse proxy with aggressive per-IP rate limiting on GET /ui/oidc/login (e.g., 5 requests per minute per source IP using nginx limit_req or Traefik middleware) reduces but does not eliminate risk, as its effectiveness depends on correct proxy configuration and does not address the unbounded cap in the application layer. Neither workaround should replace upgrading to v0.5.0.
Same weakness CWE-400 – Uncontrolled Resource Consumption
View allSame technique Denial Of Service
View allVendor StatusVendor
SUSE
Severity: Moderate| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Affected |
| openSUSE Leap 15.5 | Affected |
| openSUSE Leap 15.6 | Affected |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-71718
GHSA-m3cx-mwpg-32jg