Portainer: The Compose Fields configs and secrets Bypass the Bind Mount Block

The stack validator rejects bind mounts in volumes but never inspects configs and secrets, whose file names any host path.

Advisory ID: TP-2026-092
Product: Portainer (management interface for Docker and Kubernetes, Go)
Vulnerability type: Path traversal in Compose file fields (CWE-22)
CVE: requested (GitHub CNA), assignment pending
CVSS 4.0: 9.4 (Critical) · CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Affected versions: >= 2.39.0 to <= 2.39.8 and >= 2.40.0 to <= 2.45.1
Vendor advisory: GHSA-9gxj-xvxm-77rj
Fixed in: 2.45.2
Reported: 7 June 2026

Summary

Portainer lets users without administrator rights deploy Compose stacks to an environment too. For that case there is a per-environment setting to hide bind mounts from non-administrators, and the validator then rejects them server-side, both in the top-level volumes section and in each service's volumes. It never inspects the configs and secrets sections, although their file field names a path on the host that the Compose engine materialises as a bind mount into the container. A standard user therefore submits exactly the request Portainer rejects as a direct bind mount and receives the host file's contents in their container. Mounting the Docker socket by the same route gives them the host's daemon.

Root cause

The validator IsValidStackFile rejects bind mounts from non-administrators in the top-level volumes section and in each service's volumes, yet never examines the top-level configs and secrets sections. Their file field names a path on the host that the Compose engine materialises as a bind mount into the referencing container, both as an absolute path such as /etc/shadow and through ../ climbing out of the stack directory. The same gap extends to other Compose fields that name a file, such as env_file: on a Swarm deployment the Portainer server reads that file itself and writes its contents into the service definition. Portainer operates a shared-responsibility model in which administrators decide how permissive an environment is and Portainer enforces that decision; this finding sits on Portainer's side of that line. Environments where the setting is disabled already permit bind mounts for non-administrators and gain no additional exposure from the finding.

Proof of Concept

# as a standard user with stack rights, bind mount blocking enabled:
configs:
  stolen:
    file: /etc/shadow
services:
  app:
    image: alpine
    command: ["sleep", "infinity"]
    configs:
      - source: stolen
        target: /loot

# the validator never looks at the configs section, the Compose engine
# mounts the host file, and the container reads /loot.
# with file: /var/run/docker.sock this becomes control of the daemon.

The same request written as a direct entry under volumes is rejected by Portainer, which shows the difference between the checked and the unchecked surface.

Impact

  • Disclosure of arbitrary files from the host filesystem by a user in the standard role.
  • Full control of the host's Docker daemon as soon as the socket is mounted by the same route.
  • Disclosure of files from the Portainer server itself, including its database, through env_file on a Swarm deployment.
  • Bypass of a setting the administration deliberately enabled to prevent exactly this.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.