Portainer CVE-2026-44883
HIGHSeverity by source
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
Lifecycle Timeline
7DescriptionGitHub Advisory
Summary
Portainer's authentication middleware accepts JWT bearer tokens passed as the ?token=<JWT> URL query parameter on any authenticated API endpoint, in addition to the standard Authorization: Bearer header. URLs are recorded in reverse-proxy access logs, browser history, and HTTP Referer headers on outbound navigation, so any JWT passed this way can be harvested by anyone with access to those logs or by an external site the user subsequently visits. A leaked token grants the full privileges of the user it was issued to, until the token expires (default 8 hours, configurable).
The ?token= parameter was used by Portainer's browser-based container attach, exec, and pod shell features, so any user with exec or attach rights on a container was exposed - not only administrators.
Severity
High
Attack complexity is High because exploitation depends on the attacker obtaining a leaked token from a log, referer, or shared URL. Once obtained, a leaked token grants the privileges of the user it was issued to; for administrator tokens this compromises confidentiality, integrity, and availability of Portainer itself and of every Docker/Kubernetes environment it manages - container exec and stack deployment make host-level compromise reachable, so subsequent-system impact is also High.
Affected Versions
Query-parameter token acceptance has existed since JWT authentication was introduced in Portainer.
Fixes are included in the following releases:
| Branch | First vulnerable | Fixed in |
|---|---|---|
| 2.33.x (LTS) | 2.33.0 | 2.33.8 |
| 2.39.x (LTS) | 2.39.0 | 2.39.2 |
| 2.40.x (STS) | all prior | 2.41.0 |
Portainer releases prior to 2.33.0 are end-of-life and will not receive a fix. Users on EOL versions should upgrade to a supported LTS branch.
Workarounds
Administrators who cannot immediately upgrade can reduce exposure by:
- Stripping
?token=at the reverse proxy. A rewrite rule in nginx, Traefik, or equivalent that removes thetokenquery parameter before the request reaches Portainer blocks the query-parameter auth path entirely. Container exec and interactive shells rely on the query-parameter token for WebSocket upgrade and will stop working until the patched release is deployed. - Auditing existing logs. Search reverse-proxy access logs and application logs for
?token=or&token=occurrences and treat any captured JWT as compromised. Resetting the affected user's password invalidates their sessions; reducing the JWT session timeout in Portainer settings shortens the exposure window for tokens already issued. - Administrator hygiene. Do not share Portainer URLs that contain
?token=in chat, email, or tickets, and avoid navigating to external sites from within the Portainer UI on unpatched instances - theRefererheader will carry the token.
None of these replace the fix.
Affected Code
Pre-fix, extractBearerToken in api/http/security/bouncer.go read the JWT from the token query parameter before falling back to the Authorization header. The query.Del("token") call scrubs the parameter from r.URL.RawQuery on the way through Portainer, but by that point the original URL has already been recorded by any upstream reverse proxy, access logger, or browser.
func extractBearerToken(r *http.Request) (string, bool) {
query := r.URL.Query()
token := query.Get("token")
if token != "" {
query.Del("token")
r.URL.RawQuery = query.Encode()
return token, true
}
tokens, ok := r.Header[jwtTokenHeader]
if !ok || len(tokens) == 0 {
return "", false
}
// ...
}The fix removes the query-parameter path entirely. Authenticated requests now carry the JWT via the Authorization header for API clients, or via the portainer_api_key HttpOnly cookie for the browser UI - cookies are sent automatically on same-origin WebSocket upgrade requests, so the browser-based container attach, exec, and pod shell features continue to work without exposing the token in the URL. The WebSocket handlers that previously documented ?token= as a required query parameter have been updated to match.
Impact
- Token leakage to infrastructure. Intermediate systems that observe the request URL - reverse proxies, load balancers, access logs, WAFs, and corporate network monitoring - capture the full JWT in plaintext.
- Token leakage via the browser. URLs containing
?token=are recorded in browser history and forwarded in theRefererheader on any outbound navigation from the Portainer UI. - Account takeover. Anyone with access to a leaked JWT acts as the authenticated user for the remainder of the token's validity, without needing the password. If the leaked token belongs to an administrator, the attacker gains full API access including user management, container exec, and stack deployment.
- Reach beyond Portainer. Container exec with an administrator JWT reaches the host filesystem of managed environments and can be used to execute commands on those hosts.
Timeline
- 2026-03-06: Reported via GitHub Security Advisory by scanpwn.
- 2026-04-14: Fix merged to
develop. - 2026-04-29: 2.41.0 released.
- 2026-05-07: 2.39.2-LTS and 2.33.8-LTS released.
Credit
- scanpwn - identified and reported the query-parameter JWT acceptance and the resulting token-leakage vectors.
AnalysisAI
Token leakage in Portainer's authentication middleware allows JWT bearer tokens passed via the ?token=<JWT> URL query parameter to be harvested from reverse-proxy access logs, browser history, and HTTP Referer headers, enabling account takeover for the validity window of the token (default 8 hours). The flaw affected any user with container exec/attach rights - not just administrators - and a leaked admin token grants full control of Portainer and every managed Docker/Kubernetes environment. No public exploit identified at time of analysis, though the underlying behavior was present since JWT auth was introduced and the GitHub Security Advisory provides sufficient detail to weaponize.
Technical ContextAI
Portainer is a widely deployed container management UI for Docker, Swarm, Kubernetes, Nomad, and Podman environments. The root cause is CWE-598 (Use of GET Request Method With Sensitive Query Strings): the extractBearerToken function in api/http/security/bouncer.go read the JWT from r.URL.Query().Get("token") before falling back to the Authorization header. Although Portainer scrubbed the parameter via query.Del("token") after extraction, the original URL had already been observed by upstream reverse proxies, WAFs, load balancers, access loggers, and the browser itself. The ?token= mechanism existed because browser-based WebSocket upgrade requests for container attach, exec, and pod shell features cannot easily set custom Authorization headers; the fix replaces this with the portainer_api_key HttpOnly cookie, which browsers send automatically on same-origin WebSocket upgrades. Affected packages per the GHSA are go/github.com/portainer/portainer across the 2.33.x LTS, 2.39.x LTS, and 2.40.x STS branches.
RemediationAI
Vendor-released patches are available: upgrade to Portainer 2.33.8 (LTS), 2.39.2 (LTS), or 2.41.0 (STS) per the GitHub releases at https://github.com/portainer/portainer/releases/tag/2.33.8, /2.39.2, and /2.41.0. Users on end-of-life branches (<2.33.0) must migrate to a supported LTS line. If immediate upgrade is not possible, deploy a reverse-proxy rewrite rule in nginx, Traefik, or equivalent that strips the token query parameter before requests reach Portainer - note this will break browser-based container attach, exec, and pod shell features until the patched build is deployed, since those features rely on ?token= for WebSocket upgrade in unpatched versions. Audit existing reverse-proxy and application logs for occurrences of ?token= or &token= and treat any captured JWTs as compromised by resetting the affected users' passwords (which invalidates their existing sessions) and shortening the JWT session timeout in Portainer settings to reduce the window of exposure for already-issued tokens. Operationally, instruct administrators not to share URLs containing ?token= in chat, email, or tickets, and to avoid navigating to external sites from within the Portainer UI on unpatched instances because the Referer header will carry the token.
More in Kubernetes
View allA critical vulnerability in Kubernetes ingress-nginx controller allows unauthenticated attackers with pod network access
Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio
Kubernetes ingress-nginx contains a configuration injection vulnerability via the mirror-target and mirror-host Ingress
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-url` Ingres
A security issue was discovered in ingress-nginx https://github.com/kubernetes/ingress-nginx where the `auth-tls-match-c
Kubernetes API server in all versions allow an attacker who is able to create a ClusterIP service and set the spec.exter
Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. Rated critical severity (CVSS 9.9), this vulne
The Kubernetes integration in GitLab Enterprise Edition 11.x before 11.2.8, 11.3.x before 11.3.9, and 11.4.x before 11.4
Kyverno Kubernetes policy engine prior to 1.x has a privilege escalation vulnerability (CVSS 9.9) allowing policy bypass
Kamaji is the Hosted Control Plane Manager for Kubernetes. Rated critical severity (CVSS 9.9), this vulnerability is rem
Jumpserver is a popular open source bastion host, and Koko is a Jumpserver component that is the Go version of coco, ref
String filter bypass in Inspektor Gadget Kubernetes eBPF tooling before fix. Insufficient string escaping enables filter
Same technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-jvp4-q659-95mj