NEWBravos is availableCreate your account

Reported privately, fixed by Portainer, then published by Portainer@flows <source> -> <target> [via <mechanism>]Documents data moving from one component to another, and what carries it.@flows in the GuardLink reference

Case study

Two Portainer security advisories credit Bugb

Portainer is open-source software for running Docker and Kubernetes from a web interface. On 8 October 2026 it published two security advisories that list Bugb among the researchers credited.

We found both issues with Bravos, our whitebox pentesting tool, and reported them to Portainer privately. Many other researchers reported the same issues independently, and Portainer credits all of them.

Published by Portainer on . Credited as Shahid-BugB.

The two advisories, in plain terms

Critical

Non-admin users could reach the host machine

Portainer has a setting, on by default, that stops users who are not administrators from mounting folders from the host machine into their containers.

The check looked for one exact wording of that request. Docker accepts other wordings that do the same thing, so a user could ask for the same mount another way and get it. With the host's files mounted, they could read and change them, and from there take control of the machine.

Our part
We reported the route through container creation. Portainer already had reports of the same flaw, so it merged ours into this advisory as a duplicate, with credit.
We reported it
First reported by anyone
Researchers credited
19, including Bugb
Fixed in
Portainer 2.45.2

Portainer’s title: Non-admin bind-mount restriction bypass via Docker local-driver volumes and automatic Git redeploy

Medium

The server could be made to fetch internal addresses

Several Portainer features fetch something from a web address that a user supplies, such as a Git repository.

Portainer checked that the address was well formed, but not where it pointed. A signed-in user could point the server at services inside the network that they could not reach themselves, such as a cloud provider's metadata service. How much that exposes depends on how the network around Portainer is set up.

Our part
We reported the Git repository file preview, where the response from the requested address came back inside the clone error message.
We reported it
First reported by anyone
Researchers credited
13, including Bugb
Fixed in
Portainer 2.45.1

Portainer’s title: Server-Side Request Forgery across multiple outbound-request features

In both cases Portainer already had a check in place. It checked the wrong thing: the wording of a request rather than what it does, or the form of an address rather than where it leads.

What the credit means

Each advisory gathers many independent reports of the same underlying problem into one write-up. Bugb is one of the credited researchers on each.

We were not the first to report either issue. Other researchers reported the bind-mount bypass from May 2026 and the request forgery from November 2025. Portainer lists every report, ours included, in each advisory’s timeline.

How we found them

Both came out of a Bravos run, started from its command line, against Portainer’s open-source code. Bravos writes a threat model into a codebase with GuardLink, then tests a running copy of the app with Cert-X-Gen to confirm or refute each threat.

We sent each result to Portainer through GitHub’s private security advisory process, and wrote this page only after Portainer had shipped fixes and published the advisories.

See how Bravos works

If you run Portainer

  • Upgrade to 2.45.2. It carries the fixes for both advisories.
  • On the 2.39.x long-term support line, the bind-mount advisory says the fix was not yet released for that line when it was published. Follow its workarounds until it is.
  • Limit what the Portainer server can reach. Block its outbound access to cloud metadata (169.254.169.254) and to internal ranges it has no reason to contact. The new destination controls for request forgery are off by default and only Business Edition can turn them on.

Each advisory lists its full workarounds: GHSA-jmr9-98g4-2phv and GHSA-wqqf-pwjh-2hm6.

Technical detail

For readers who want the mechanics. Everything here comes from Portainer’s published advisories.

Bind-mount restriction bypass

Severity
Critical, CVSS 4.0 score 9.4
Vector
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
Weakness
CWE-284, improper access control
Fixed in
2.45.2. The 2.39.x line had no fix at publication.

Docker’s local volume driver turns the options type=none, o=bind, device=<host path> into a real bind mount of that host path. Portainer’s filters only blocked the literal forms. Container creation rejected mounts typed "bind" but never read a volume mount’s driver options. The volume and Swarm service filters compared type with "bind", while a bind mount keys off o. Every check was also case-sensitive, and Docker is not, so "Bind" passed. A separate path, automatic Git redeploy by webhook or polling, deployed the pulled Compose file without any security-setting check.

The container-creation filter, as quoted in the advisory:

// api/http/proxy/factory/docker/containers.go
if !securitySettings.AllowBindMountsForRegularUsers && len(partialContainer.HostConfig.Mounts) > 0 {
    for _, mount := range partialContainer.HostConfig.Mounts {
        if mount.Type == "bind" {
            return forbiddenResponse, ErrBindMountsForbidden
        }
    }
}

The advisory credits our report with the container-creation bypass: the guard is a denylist that matches only the literal "bind", while the canonical local-driver form is type=none, o=bind, device=<host path>, and the container-creation request model never parses Mounts[].VolumeOptions at all.

The fix routes every mount-creating path through one shared helper. It treats a mount as a host bind when its type is bind or npipe in any letter case, when the o option contains bind or rbind, or when a device is set on the local driver. It also resolves named volumes, walks the Compose top-level volumes: block, and validates automatic Git redeploys.

Server-side request forgery

Severity
Medium, CVSS 4.0 score 5.3
Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N
Weakness
CWE-918, server-side request forgery
Fixed in
2.45.1, the first release that covers every path the advisory names

Git clones, Helm repositories, registry pings, template and manifest URLs, the GitLab proxy and the OAuth resource URI all took a destination from input and opened a connection to it. The shared validator checked only the URL’s syntax, as quoted in the advisory:

// pkg/validate/validate.go
func IsURL(urlString string) bool {
  strTemp := urlString
  if !strings.Contains(urlString, "://") {
    strTemp = "http://" + urlString
  }
  u, err := url.Parse(strTemp)
  return err == nil && u.Host != ""
}

Our report covered the Git repository file preview, where the upstream response was reflected in the clone error, so the request was not blind.

The fix adds a safe dialer. It resolves the hostname, checks every resolved address against the destination policy, then connects to the address it checked, which closes the DNS rebinding window. Redirects go back through the same dialer. The policy has an Audit and an Enforce mode, ships disabled, and can only be switched on in Business Edition.

Test your own code the same way

Bravos writes a threat model into your code, then tests a running copy of your app to confirm or refute each threat. Free to start.