VYPR
High severityNVD Advisory· Published May 28, 2026

CVE-2026-33590

CVE-2026-33590

Description

Insecure default settings of Portainer CE grant regular (non-admin) users privileges that allow host filesystem access and host-level code execution. An authenticated non-administrative user with endpoint access can exploit these settings to read host files or obtain root equivalent

access on the host.

AI Insight

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

Default Portainer CE settings grant regular users excessive privileges, enabling host filesystem access and privilege escalation to root.

Vulnerability

Portainer CE versions up to 2.33.2 (and possibly earlier) ship with insecure default endpoint security settings that grant regular (non-admin) users privileges typically reserved for administrators. Specifically, by default, allowBindMountsForRegularUsers, allowPrivilegedModeForRegularUsers, allowHostNamespaceForRegularUsers, allowDeviceMappingForRegularUsers, allowSysctlSettingForRegularUsers, allowContainerCapabilitiesForRegularUsers, and allowStackManagementForRegularUsers are set to true [1]. This configuration is applied when creating endpoints (both TLS-secured and unsecured) [2]. As a result, any authenticated user with endpoint access can leverage these permissions to compromise the host.

Exploitation

An authenticated non-administrative user with access to a Portainer endpoint can exploit the default privileges. First, using allowBindMountsForRegularUsers, the user can create a container with a bind mount mapping any host path (e.g., /etc, /root/.ssh) into the container, enabling file read access. To achieve host-level code execution, the user can enable allowPrivilegedModeForRegularUsers to start a container in privileged mode, gaining root capabilities on the host. Additional privileges such as allowHostNamespaceForRegularUsers and allowDeviceMappingForRegularUsers further facilitate host escape [1]. The user does not need prior shell access; only a Portainer account with endpoint permissions is required.

Impact

By exploiting these default settings, a non-administrative user can read arbitrary host files, including sensitive configuration and credentials, and ultimately execute code with root privileges on the host system. This compromises the confidentiality and integrity of the host and all containers running on it [1]. The attack does not require any vulnerability beyond the misconfiguration; it is an intentional but insecure default.

Mitigation

Portainer has addressed the issue by introducing a function DefaultEndpointSecuritySettings() that likely sets all security-sensitive options to false by default. The fix is visible in commit ac8fa7672e732b44b970c9eaf928eddd2c68796c [2] and further refined in commit 3e2fdb1891e81a8e4c5c8beb60e45f07c8ecae52 [3] to enforce proper checks in stack deployments. Administrators should upgrade to a version containing these fixes (the exact fixed version is not stated, but commits are after 2.33.2). As a workaround, administrators should manually review and revoke the default endpoint security settings for regular users, especially disabling allowBindMountsForRegularUsers and allowPrivilegedModeForRegularUsers [1].

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

Affected products

2

Patches

2
3e2fdb1891e8

fix(swarm): fix environment security checks BE-12541 (#1666)

https://github.com/portainer/portainerandres-portainerJan 14, 2026via nvd-ref
2 files changed · +3 11
  • api/stacks/deployments/deployment_compose_config.go+1 8 modified
    @@ -79,14 +79,7 @@ func (config *ComposeStackDeploymentConfig) Deploy() error {
     
     	securitySettings := &config.endpoint.SecuritySettings
     
    -	if (!securitySettings.AllowBindMountsForRegularUsers ||
    -		!securitySettings.AllowPrivilegedModeForRegularUsers ||
    -		!securitySettings.AllowHostNamespaceForRegularUsers ||
    -		!securitySettings.AllowDeviceMappingForRegularUsers ||
    -		!securitySettings.AllowSysctlSettingForRegularUsers ||
    -		!securitySettings.AllowContainerCapabilitiesForRegularUsers) &&
    -		!isAdminOrEndpointAdmin {
    -
    +	if !isAdminOrEndpointAdmin {
     		if err := stackutils.ValidateStackFiles(config.stack, securitySettings, config.FileService); err != nil {
     			return err
     		}
    
  • api/stacks/deployments/deployment_swarm_config.go+2 3 modified
    @@ -78,9 +78,8 @@ func (config *SwarmStackDeploymentConfig) Deploy() error {
     
     	settings := &config.endpoint.SecuritySettings
     
    -	if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
    -		err = stackutils.ValidateStackFiles(config.stack, settings, config.FileService)
    -		if err != nil {
    +	if !isAdminOrEndpointAdmin {
    +		if err := stackutils.ValidateStackFiles(config.stack, settings, config.FileService); err != nil {
     			return err
     		}
     	}
    
ac8fa7672e73

fix(environments): improve the default environment security settings BE-12391 (#1656)

https://github.com/portainer/portainerandres-portainerJan 14, 2026via nvd-ref
4 files changed · +19 50
  • api/datastore/datastore_test.go+1 12 modified
    @@ -90,18 +90,7 @@ func newEndpoint(endpointType portainer.EndpointType, id portainer.EndpointID, n
     }
     
     func setEndpointAuthorizations(endpoint *portainer.Endpoint) {
    -	endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
    -		AllowVolumeBrowserForRegularUsers: false,
    -		EnableHostManagementFeatures:      false,
    -
    -		AllowSysctlSettingForRegularUsers:         true,
    -		AllowBindMountsForRegularUsers:            true,
    -		AllowPrivilegedModeForRegularUsers:        true,
    -		AllowHostNamespaceForRegularUsers:         true,
    -		AllowContainerCapabilitiesForRegularUsers: true,
    -		AllowDeviceMappingForRegularUsers:         true,
    -		AllowStackManagementForRegularUsers:       true,
    -	}
    +	endpoint.SecuritySettings = portainer.DefaultEndpointSecuritySettings()
     }
     
     func (store *Store) CreateEndpoint(t *testing.T, name string, endpointType portainer.EndpointType, URL string, tls bool) portainer.EndpointID {
    
  • api/http/handler/endpoints/endpoint_create.go+1 12 modified
    @@ -546,18 +546,7 @@ func (handler *Handler) snapshotAndPersistEndpoint(tx dataservices.DataStoreTx,
     }
     
     func (handler *Handler) saveEndpointAndUpdateAuthorizations(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint) error {
    -	endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
    -		AllowVolumeBrowserForRegularUsers: false,
    -		EnableHostManagementFeatures:      false,
    -
    -		AllowSysctlSettingForRegularUsers:         true,
    -		AllowBindMountsForRegularUsers:            true,
    -		AllowPrivilegedModeForRegularUsers:        true,
    -		AllowHostNamespaceForRegularUsers:         true,
    -		AllowContainerCapabilitiesForRegularUsers: true,
    -		AllowDeviceMappingForRegularUsers:         true,
    -		AllowStackManagementForRegularUsers:       true,
    -	}
    +	endpoint.SecuritySettings = portainer.DefaultEndpointSecuritySettings()
     
     	if err := tx.Endpoint().Create(endpoint); err != nil {
     		return err
    
  • api/internal/endpointutils/endpoint_setup.go+2 26 modified
    @@ -91,19 +91,7 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.
     		Status:             portainer.EndpointStatusUp,
     		Snapshots:          []portainer.DockerSnapshot{},
     		Kubernetes:         portainer.KubernetesDefault(),
    -
    -		SecuritySettings: portainer.EndpointSecuritySettings{
    -			AllowVolumeBrowserForRegularUsers: false,
    -			EnableHostManagementFeatures:      false,
    -
    -			AllowSysctlSettingForRegularUsers:         true,
    -			AllowBindMountsForRegularUsers:            true,
    -			AllowPrivilegedModeForRegularUsers:        true,
    -			AllowHostNamespaceForRegularUsers:         true,
    -			AllowContainerCapabilitiesForRegularUsers: true,
    -			AllowDeviceMappingForRegularUsers:         true,
    -			AllowStackManagementForRegularUsers:       true,
    -		},
    +		SecuritySettings:   portainer.DefaultEndpointSecuritySettings(),
     	}
     
     	if strings.HasPrefix(endpoint.URL, "tcp://") {
    @@ -149,19 +137,7 @@ func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStor
     		Status:             portainer.EndpointStatusUp,
     		Snapshots:          []portainer.DockerSnapshot{},
     		Kubernetes:         portainer.KubernetesDefault(),
    -
    -		SecuritySettings: portainer.EndpointSecuritySettings{
    -			AllowVolumeBrowserForRegularUsers: false,
    -			EnableHostManagementFeatures:      false,
    -
    -			AllowSysctlSettingForRegularUsers:         true,
    -			AllowBindMountsForRegularUsers:            true,
    -			AllowPrivilegedModeForRegularUsers:        true,
    -			AllowHostNamespaceForRegularUsers:         true,
    -			AllowContainerCapabilitiesForRegularUsers: true,
    -			AllowDeviceMappingForRegularUsers:         true,
    -			AllowStackManagementForRegularUsers:       true,
    -		},
    +		SecuritySettings:   portainer.DefaultEndpointSecuritySettings(),
     	}
     
     	if err := snapshotService.SnapshotEndpoint(endpoint); err != nil {
    
  • api/portainer.go+15 0 modified
    @@ -2449,3 +2449,18 @@ const (
     	HelmInstallStatusFailed       HelmInstallStatus = "failed"
     	HelmInstallStatusUninstalling HelmInstallStatus = "uninstalling"
     )
    +
    +func DefaultEndpointSecuritySettings() EndpointSecuritySettings {
    +	return EndpointSecuritySettings{
    +		AllowBindMountsForRegularUsers:            false,
    +		AllowContainerCapabilitiesForRegularUsers: false,
    +		AllowDeviceMappingForRegularUsers:         false,
    +		AllowHostNamespaceForRegularUsers:         false,
    +		AllowPrivilegedModeForRegularUsers:        false,
    +		AllowSysctlSettingForRegularUsers:         false,
    +		AllowVolumeBrowserForRegularUsers:         false,
    +		EnableHostManagementFeatures:              false,
    +
    +		AllowStackManagementForRegularUsers: true,
    +	}
    +}
    

Vulnerability mechanics

Root cause

"Portainer CE shipped with insecure default endpoint security settings that granted regular (non-admin) users dangerous privileges such as bind mounts, privileged mode, host namespace sharing, device mapping, container capabilities, and sysctl control — all set to true by default."

Attack vector

An authenticated non-administrative user who has access to a Portainer endpoint can exploit the default-permissive security settings [ref_id=1]. The researcher demonstrated a proof-of-concept that abuses `allowBindMountsForRegularUsers` (set to `true` by default) to bind-mount the host's `/etc/shadow` file into a container and read its contents [ref_id=1]. Because `allowPrivilegedModeForRegularUsers` and `allowHostNamespaceForRegularUsers` are also enabled by default, an attacker can escalate further — launching a privileged container that shares the host PID namespace, enabling host-level code execution with root-equivalent capabilities [ref_id=1]. The attack requires only a valid regular-user login and network access to the Portainer API.

Affected code

The insecure defaults were defined inline in `createTLSSecuredEndpoint` and `createUnsecuredEndpoint` in `api/internal/endpointutils/endpoint_setup.go`, and in `saveEndpointAndUpdateAuthorizations` in `api/http/handler/endpoints/endpoint_create.go` [patch_id=2980741]. The stack-deployment validation logic in `api/stacks/deployments/deployment_compose_config.go` and `api/stacks/deployments/deployment_swarm_config.go` had a conditional guard that could skip security validation for non-admin users [patch_id=2980740].

What the fix does

Patch [patch_id=2980741] introduces a new `DefaultEndpointSecuritySettings()` function in `api/portainer.go` that sets all dangerous flags to `false` — `AllowBindMountsForRegularUsers`, `AllowPrivilegedModeForRegularUsers`, `AllowHostNamespaceForRegularUsers`, `AllowDeviceMappingForRegularUsers`, `AllowContainerCapabilitiesForRegularUsers`, `AllowSysctlSettingForRegularUsers`, and `AllowVolumeBrowserForRegularUsers` are all `false` [patch_id=2980741]. Only `AllowStackManagementForRegularUsers` remains `true`. All endpoint-creation code paths now call this single function instead of inlining permissive defaults [patch_id=2980741]. Patch [patch_id=2980740] additionally fixes a logic bug in stack deployment: the old code only ran `ValidateStackFiles` when certain individual settings were disabled, but the new code always validates stack files for non-admin users regardless of which flags are toggled [patch_id=2980740]. Together these changes ensure that new Portainer installations start with a secure posture and that security validation cannot be bypassed through stack deployments.

Preconditions

  • authAttacker must have a valid regular (non-admin) Portainer user account
  • networkAttacker must have network access to the Portainer API
  • configThe Portainer endpoint must be using default security settings (pre-2.38.0 defaults)

Reproduction

The researcher published a proof-of-concept script at the URL referenced in [ref_id=1]. The script authenticates as a regular user, lists available endpoints, checks for the `alpine:latest` image, creates a container that bind-mounts the host `/etc/shadow` file, and saves the contents to `portainer_vulnerability_secret/etc_shadow.txt` [ref_id=1]. Replace the username and password variables in the script with those of a regular user account on a default Portainer CE 2.33.2 installation, then execute the script [ref_id=1].

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

References

3

News mentions

0

No linked articles in our index yet.