Feb 27, 2026·5 min read·3 visits
Beszel agents prior to v0.18.4 fail to sanitize container IDs before passing them to the Docker socket. Authenticated users can use '../' sequences to traverse the Docker API and access unauthorized system information.
A path traversal vulnerability exists in the Beszel server monitoring agent, allowing authenticated users to access arbitrary Docker Engine API endpoints. The vulnerability arises from improper sanitization of the 'container' query parameter when constructing requests to the Docker Unix socket. By injecting directory traversal sequences, an attacker with minimum privileges (including Read-Only) can escape the intended container scope and query sensitive host-level information, such as the Docker version, system info, or details of other containers running on the host.
Beszel is a lightweight server monitoring platform consisting of a central Hub and agents deployed on monitored systems. The agent gathers metrics, including Docker container statistics, by communicating with the local Docker engine via a Unix Domain Socket (UDS). To provide container-specific logs and information, the Hub proxies requests to the agent, which then queries the Docker API.
In versions prior to 0.18.4, the Beszel Agent contains a Path Traversal vulnerability (CWE-22). The agent accepts a container identifier from the Hub without sufficient validation. This identifier is used to construct the URL path for requests sent to the Docker daemon. Because the agent does not sanitize this input or strictly enforce an allowlist of valid container ID formats, an attacker can supply directory traversal sequences (../).
This flaw allows an authenticated user to break out of the intended API path (typically /containers/<id>/...) and access the root of the Docker Engine API. Consequently, an attacker can retrieve sensitive information about the host's Docker configuration and other running workloads, bypassing the intended restrictions of the Beszel interface.
The vulnerability is caused by the insecure concatenation of user-supplied input into a path used for a Unix socket connection, combined with specific behaviors of the Go HTTP client.
1. Unsafe String Interpolation:
The Beszel agent constructs the request URL for the Docker socket using fmt.Sprintf. The code takes the container string directly from the incoming request query parameters and injects it into a format string like /containers/%s/json. No prior validation checks if the input is a valid hexadecimal container ID or if it contains control characters.
2. Unix Socket Path Behavior:
When communicating over HTTP/TCP, many libraries and proxies automatically normalize paths, resolving ../ sequences before the request leaves the client. However, when using Go's http.Client to communicate over a Unix Domain Socket (via the http+unix scheme or similar custom transport), path normalization is often less aggressive or non-existent for the path component sent to the socket. The raw path containing ../ is transmitted to the Docker daemon.
3. Docker Daemon Resolution:
The Docker daemon receives the request path (e.g., /containers/../../version). The internal router of the Docker engine resolves the traversal, treating the request as a query for /version. This effectively bypasses the application logic that intended to restrict the user to specific container operations.
The following analysis highlights the vulnerable logic and the remediation applied in version 0.18.4. The fix introduces strict input validation and proper URL encoding.
In the vulnerable version, the agent accepted the container parameter and used it directly. The lack of url.PathEscape allowed special characters to pass through to the socket request.
// Vulnerable Code Pattern (Conceptual)
func getContainerLogs(w http.ResponseWriter, r *http.Request) {
containerID := r.URL.Query().Get("container")
// FLAW: Direct interpolation of user input into the path
targetPath := fmt.Sprintf("/containers/%s/logs", containerID)
// The client sends this path raw to the Unix socket
resp, err := dockerClient.Get("http://unix" + targetPath)
// ...
}The patch introduces two layers of defense: regex validation to ensure the ID is a hex string, and URL escaping as a fallback safety measure.
// Fixed Code Pattern
import (
"net/url"
"regexp"
)
// 1. Strict Validation: Enforce 12-64 char hex string
var containerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`)
func getContainerLogs(w http.ResponseWriter, r *http.Request) {
containerID := r.URL.Query().Get("container")
// 2. Reject invalid formats immediately
if !containerIDPattern.MatchString(containerID) {
http.Error(w, "Invalid container ID", http.StatusBadRequest)
return
}
// 3. Encode the path component (Defense in Depth)
// Even if the regex failed, this would convert "../" to "%2E%2E%2F"
targetPath := fmt.Sprintf("/containers/%s/logs", url.PathEscape(containerID))
resp, err := dockerClient.Get("http://unix" + targetPath)
// ...
}An attacker can exploit this vulnerability to enumerate the host system's Docker environment. Access to the Beszel Hub is required, but the account can have minimal privileges (e.g., a read-only user).
Prerequisites:
system ID of a target agent (visible in the URL when viewing a system in the dashboard).Attack Steps:
1).version endpoint.Proof of Concept:
GET /api/beszel/containers/info?system=1&container=../../version HTTP/1.1
Host: beszel.internal
Authorization: Bearer <JWT_TOKEN>Result:
The server responds with the JSON output from the Docker Engine's /version endpoint, disclosing the Go version, kernel version, and build details. By changing the payload to ../../info or ../../containers/json, the attacker can list all running containers or inspect swarm details.
The successful exploitation of CVE-2026-27734 results in unauthorized Information Disclosure. While the attack is authenticated, the ability for a low-privilege user to access the underlying infrastructure API represents a significant privilege escalation within the context of the application.
/info, /version, /images/json, and /containers/json. This exposes the internal architecture, list of running services, image names, and potential environment variables if /containers/<id>/json is targeted for arbitrary containers.GET requests (logs/info endpoints). There is no documented path to issue POST or DELETE commands via this specific vector, limiting the ability to modify state.CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N (6.5 Medium). The scope is Unchanged (S:U) because the vulnerable component (Beszel Agent) and the impacted component (Docker Engine) are on the same local system boundary, although the data accessed belongs to the infrastructure layer.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Beszel HenryGD | < 0.18.4 | 0.18.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network |
| CVSS | 6.5 (Medium) |
| Impact | Information Disclosure |
| Protocol | HTTP over Unix Socket |
| Exploit Status | Proof of Concept Available |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.