Jan 27, 2026·5 min read·17 visits
Dozzle v9.0.2 implemented access control on the front door but left the back window open. While the UI filtered which containers you could see in a list, the backend API for retrieving a specific container by ID completely ignored your access permissions. This allows any authenticated user to access, view logs, and execute commands on any container managed by Dozzle agents, regardless of their assigned restrictions.
A critical authorization bypass in Dozzle's agent service allows restricted users to access any container by ID, effectively rendering Label-Based Access Control (LBAC) useless for direct lookups.
Dozzle is the darling of the Docker world for developers who want a lightweight, no-nonsense log viewer. It’s fast, it’s real-time, and it doesn't require a database. But like any tool that sits on top of the Docker socket, it wields immense power. If you control Dozzle, you control the containers. To mitigate this risk in multi-tenant or multi-team environments, Dozzle introduced Label-Based Access Control (LBAC).
The idea is simple: You tag your containers with labels like env=dev or env=prod. You then tell Dozzle, "User Alice is only allowed to see containers where env=dev." In theory, this keeps Alice from accidentally (or intentionally) rm -rf /'ing the production database. It’s a velvet rope that separates the interns from the critical infrastructure.
But here’s the thing about velvet ropes: they only work if the bouncer actually checks who walks past them. CVE-2026-24740 is the story of a bouncer who was handed a list of banned guests, nodded politely, and then let everyone in anyway because they knew the secret password (the Container ID).
The vulnerability lies deep within the agentService logic. Dozzle supports a distributed mode where a master instance talks to agents running on other nodes. When a user logs in, their session is stamped with specific filter rules (the LBAC labels).
When the UI requests a list of containers, the system correctly applies these filters. Alice only sees her dev containers. The UI looks secure. The list is curated. Everything seems fine.
However, when the UI (or an attacker) requests a specific container by its ID—for example, to stream logs or start an interactive shell—the application calls a method named FindContainer. This method accepts the Container ID and the User's Labels. The expectation is that the method will say, "Okay, find container X, but only if it matches these labels."
In reality, the code treated the labels like a suggestion. The implementation for the agent service accepted the labels as an argument but failed to pass them along in the gRPC request to the remote agent. The agent, which has no knowledge of the user's session state, simply received a command: "Give me the handle for Container ID prod-db-123." Being a compliant piece of software, it obliged.
Let's look at the Go code responsible for this mishap. This is a textbook example of a function signature promising security checks that the implementation fails to deliver.
The Vulnerable Code (v9.0.2):
In internal/support/container/agent_service.go, the FindContainer method takes labels as an argument, but watch where that variable goes (spoiler: nowhere).
func (a *agentService) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
// The 'labels' parameter contains the security restrictions.
// Notice how it is completely ignored in the function call below?
return a.client.FindContainer(ctx, id)
}The Fix (v9.0.3): The patch involves properly serializing these labels into the gRPC request so the agent can enforce the filter. The developer had to update the client definition to accept the filter and pack it into the protobuf message.
// internal/agent/client.go in commit 620e59a
func (c *Client) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
in := &pb.FindContainerRequest{ContainerId: id}
// Now we actually use the labels!
if labels != nil {
in.Filter = make(map[string]*pb.RepeatedString)
for k, v := range labels {
in.Filter[k] = &pb.RepeatedString{Values: v}
}
}
return c.client.FindContainer(ctx, in)
}This is a classic "Interface Disconnect." The architect likely designed the interface to be secure, but the plumbing didn't connect the pipes.
Exploiting this requires an authenticated session, but it doesn't require high privileges. You just need to be a user with some access. Here is how a malicious "intern" could pivot to "admin":
dev-app containers. They want to access prod-payment-gateway.docker-compose.yml file committed to a shared repo).# Standard request to attach to a stream
GET /api/logs/stream/e3b0c44298fc?stdout=true&stderr=true HTTP/1.1
Cookie: auth=... (Valid Token with 'dev' scope)Because the backend FindContainer logic drops the 'dev' scope requirement when talking to the agent, the agent returns the stream for e3b0c44298fc (the Prod DB). The attacker now has a live view of production logs.
Escalation to RCE:
Dozzle isn't just a viewer. It supports exec. If the attacker sends a request to /api/containers/e3b0c44298fc/exec, the same flaw applies. They now have a root shell inside the production container.
This vulnerability turns a "Least Privilege" model into a "Most Privilege" suggestion. In many DevOps environments, developers are given read-only access to logs to debug issues. This CVE upgrades that access to full interactive control.
With exec access to a container, an attacker can:
Additionally, a secondary flaw fixed in the same release (Commit 58caca2) involved a log.Fatal call when parsing invalid filters. This meant that even if an attacker couldn't guess an ID, they could crash the entire Dozzle service just by sending a malformed header, denying access to everyone else.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Dozzle amir20 | <= 9.0.2 | 9.0.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-285 |
| Attack Vector | Network |
| CVSS | 8.8 (High) |
| Impact | Confidentiality, Integrity, Availability |
| Exploit Status | PoC Available |
| Architecture | Agent/Client gRPC |
Improper Authorization
An in-depth security audit of the skillctl command-line package manager revealed five critical and high-severity security vulnerabilities. The identified flaws span parameter-level command argument injection via the source_sha parameter, uncontrolled resource consumption (Denial of Service) through unnamed UNIX FIFOs and character devices, directory path traversal in the destination argument, commit-message trailer forgery via newline injection in skill names, and local credential exfiltration leveraging UNIX hardlinks. These vulnerabilities represent significant vectors for workstation compromise when executing agentic tasks in repositories containing untrusted files or pull requests. Remediation was introduced in version v0.1.3.
CVE-2026-48153 is a Server-Side Request Forgery (SSRF) vulnerability in the Budibase OAuth2 SDK prior to version 3.39.0. It allows authenticated low-privileged users to bypass outbound network security blacklists and send arbitrary requests to internal subnets or cloud metadata services.
The self-hosted Slack Nebula VPN control plane, nebula-mesh, stored high-privilege enrollment tokens in plaintext inside its SQLite database. This flaw allowed any adversary with read access to the database to retrieve pending tokens and enroll unauthorized hosts into the secure VPN mesh.
The devbridge-autocomplete package (jQuery-Autocomplete) fails to escape category headers and suggestion values when using default formatters formatGroup and formatResult. If suggestions contain untrusted input, arbitrary HTML and JavaScript execute directly in the victim's browser session.
OpenCTI versions prior to 6.1.9 fail to properly restrict GraphQL schema introspection queries due to a weak pattern-matching implementation. An unauthenticated attacker can bypass the introspection block list by stripping whitespace and carriage returns, enabling complete reconnaissance of the GraphQL schema.
An unrestricted file upload vulnerability in Paymenter's support ticket system (prior to version 1.2.11) allows authenticated users to upload arbitrary PHP scripts to a web-accessible directory. The application fails to validate file extensions or MIME types before storing the files, enabling remote code execution under the web server's privilege context.