NEWBravos is availableCreate your account
Coordinated disclosureCritical

Two Portainer Advisories: Critical Bind-Mount Bypass and SSRF

Portainer published two security advisories on 8 October 2026 that credit Bugb among many independent reporters: a critical non-admin bind-mount restriction bypass (GHSA-jmr9-98g4-2phv, CVSS 9.4) and a server-side request forgery across multiple outbound features (GHSA-wqqf-pwjh-2hm6, CVSS 5.3). Both were found with Bravos, Bugb's whitebox pentesting tool, and reported privately through GitHub's security advisory process.

Open Source SecurityBy Shahid Parvez Hakim
Two Portainer Advisories: Critical Bind-Mount Bypass and SSRF case study cover
2
Advisories
CVSS 9.4
Highest severity
2.45.2 / 2.45.1
Fixed in
Coordinated
Disclosure
01

The challenge

  • A default-on control (hide bind mounts for non-administrators) that was enforced by matching one exact request shape
  • A URL validator that checked a destination's syntax but not the address it resolved to
  • The same weakness classes reported independently by many researchers over several months
  • Impact bounded by how each operator deploys, segments and filters egress for Portainer
02

The outcome

  • GHSA-jmr9-98g4-2phv (Critical, CVSS 9.4) fixed in Portainer 2.45.2
  • GHSA-wqqf-pwjh-2hm6 (Medium, CVSS 5.3) fixed in Portainer 2.45.1
  • Bugb credited as Reporter on the bind-mount advisory and Finder on the SSRF advisory
  • Both reported privately and fixed by Portainer before public disclosure
Evidence

From private report to public fix

  1. 01Reportedprivately
  2. 02Fixed2.45.2 / 2.45.1
  3. 03PublishedOct 8, 2026
03

The full story

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 through GitHub's security advisory process. Many other researchers reported the same issues independently, and Portainer credits all of them.

Both advisories were reported privately, fixed by Portainer, and then published by Portainer.

The two advisories, in plain terms

Non-admin users could reach the host machine

GHSA-jmr9-98g4-2phv — Critical, CVSS 9.4. Fixed in Portainer 2.45.2.

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.

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. Nineteen researchers are credited in total.

The server could be made to fetch internal addresses

GHSA-wqqf-pwjh-2hm6 — Medium, CVSS 5.3. Fixed in Portainer 2.45.1.

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.

We reported the Git repository file preview, where the response from the requested address came back inside the clone error message. Thirteen researchers are credited in total.

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. Being credited means our report was accurate, reproducible, and reported the right way, to a maintainer who then fixed it.

How we found them with Bravos — step by step

Both issues came out of a single Bravos run against Portainer's open-source code. Bravos is Bugb's whitebox pentesting tool: you point it at a codebase and it works the way a careful security engineer would — understand the app, model where it could break, stand it up, and prove what is actually exploitable — end to end, without a human driving each step. Below is exactly what it did on Portainer Community Edition 2.39.2, in plain terms, with the real detail underneath. You can follow the same steps on your own services.

Bravos first used GuardLink to read Portainer's source and write a threat model directly into the code. On this run GuardLink scanned 4,606 files and left 881 annotations across the Go API and the TypeScript/JavaScript front end — notes that live in the repo next to the code they describe.

What matters is not the count but the map it produced. GuardLink works out how the application is actually structured and where its trust boundaries are. For Portainer it established the single most important fact about the app:

The Portainer API is a higher-privilege surface than any user account. Portainer holds root-equivalent credentials for every environment it manages, so an authenticated standard user is one authorization bug away from the Docker socket — and the Docker socket is root on the host.

With that framing, GuardLink marked every place where something a user controls reaches a sensitive action, and asked one question at each: is there a control in the way, and does that control actually cover this path? Two kinds of spot came back worth testing:

  • Container creation / Docker proxy. A standard (non-admin) user supplies container settings, and a default-on control is supposed to stop them mounting host folders. GuardLink flagged the Docker proxy as the critical boundary because a gap here reaches the host.
  • Outbound fetches. Several features make the server fetch a URL the user supplies — registry "ping", a Kubernetes Helm repository, the OAuth/OIDC provider, and a stack's Git remote. GuardLink traced them all to one shared helper, IsURL, and annotated it plainly: it accepts any scheme and any host — no allowlist, no private-IP or metadata block, no DNS-rebind defense.

GuardLink did not call either one a vulnerability. It is a map, not a verdict — it showed exactly where to look.

Step 2 — Rank the attack surface (CodeGraph)

Bravos then built a code graph of Portainer and ranked its entry points by what each one can reach. Portainer exposes dozens of routes, and a set of them sit outside authentication by design (POST /auth, the Edge agent and webhook routes, POST /restore, and more). Instead of testing everything blindly, Bravos put the handful of paths that lead to a host mount or a server-side fetch at the top of the list. That ranking is why two real issues surfaced quickly instead of being buried under hundreds of low-value routes.

Step 3 — Stand up a running copy and prove it (Cert-X-Gen)

A threat model says what could be wrong; it is not proof. So Bravos stood up a running copy of Portainer, created test users at the privilege level that matters — a normal standard user, not an admin — and used Cert-X-Gen to try each flagged path against the live application. A finding was only allowed to become "confirmed" when the app's own behaviour demonstrated it:

  • Server-side request forgery. Acting as the standard user, Cert-X-Gen sent POST /api/registries/ping with the registry URL pointed at an internal-only address instead of a real registry. Portainer's server made the request and the response came back to the low-privilege user. Same result through the Helm-repository and Git-preview features — all of them flow through the IsURL helper that never checks where the address leads. Confirmed by the server actually fetching something the user could not reach on their own.
  • Non-admin reaching the host. The bind-mount restriction matched one exact wording of a mount request. Cert-X-Gen submitted the same mount described another way that Docker accepts equally — and the host folder mounted. Confirmed by the mount actually happening, not by reading the code.

In both cases the same pattern showed up: a control that checked the shape of the input (the wording of a request, the form of an address) instead of its effect (what it mounts, where it leads).

Step 4 — Report only what was proven

Bravos does not report a guess as a finding. Each issue reached the report only after Cert-X-Gen observed the app behave the way the threat model predicted. We sent each result to Portainer privately through GitHub's security advisory process, and published this write-up only after Portainer had shipped fixes and the advisories were public.

The shape of the run is the whole point: understand the app, map where it can break, rank what matters, prove it on a live copy, and report only what is proven.

Replicate this on your own services

Everything above uses tools you can run yourself. GuardLink and Cert-X-Gen are open source; Bravos orchestrates them. The shape of a run against any codebase is:

  1. Point Bravos at the code and a running copy. Clone the target (here, Portainer CE 2.39.2) and have an instance running that you are authorized to test. Bravos can also stand the environment up for you, including test users at each role.
  2. Build the model. Bravos runs GuardLink over the repo to produce the threat model and the in-code annotations — the same threat-model.md and .guardlink/ notes this engagement produced. Read it: it tells you where the dangerous paths are before you send a single packet.
  3. Rank and target. Bravos builds the code graph and turns the highest-value exposures into concrete test goals, so testing starts where impact is highest.
  4. Prove on the running copy. Cert-X-Gen exercises each goal against the live app as a real, low-privilege user — e.g. pointing a server-side "fetch a URL" feature at an internal address, or re-wording a blocked request a way the backend still accepts — and records only what the app actually did.
  5. Act on proven findings only. Treat a finding as real when the running app demonstrated it, then disclose responsibly to the maintainer.

The engineering lesson is reusable: validate what a request does, not how it is worded, and where an address leads, not whether it parses. If your app has a feature that fetches a user-supplied URL, or a control that inspects one exact request shape, those are the first two places to point a run like this.

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 notes 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 the cloud metadata address (169.254.169.254) and to internal ranges it has no reason to contact. The new destination controls for the request forgery are off by default, and only Portainer Business Edition can enable them.

Each advisory lists its full workarounds. Read them for the details that fit your deployment.

References

Everything in this case study comes from Portainer's published advisories.

Put the same method to work

Find what matters. Prove it. Fix it.

Bravos maps the attack surface in your code, then tests a running copy to confirm what is real.