CVE-2026-72876 Overview
CVE-2026-72876 is a critical cross-tenant command injection vulnerability in Dokploy, a self-hostable Platform as a Service (PaaS). Versions prior to 0.29.13 fail to enforce organization ownership on Swarm read procedures and interpolate a caller-controlled nodeId into a remote shell command. An authenticated user with server:read permission can supply another organization's serverId and execute arbitrary commands as the configured SSH user on another tenant's server. The flaw is tracked under [CWE-78] OS Command Injection and combines an insecure direct object reference (IDOR) with unsafe shell interpolation.
Critical Impact
Any authenticated tenant with server:read can achieve remote code execution on servers owned by other organizations sharing the Dokploy instance.
Affected Products
- Dokploy versions prior to 0.29.13
- apps/dokploy/server/api/routers/swarm.ts (missing ownership check)
- packages/server/src/services/docker.ts (getNodeInfo unsafe interpolation)
Discovery Timeline
- 2026-08-10 - CVE-2026-72876 published to NVD
- 2026-08-10 - Last updated in NVD database
Technical Details for CVE-2026-72876
Vulnerability Analysis
The vulnerability chains two defects in the Dokploy Swarm management API. The tRPC procedures swarm.getNodes, swarm.getNodeInfo, swarm.getNodeApps, and swarm.getAppInfos accept a serverId parameter without validating that the referenced server belongs to the caller's activeOrganizationId. This alone constitutes a cross-tenant IDOR that exposes Swarm metadata across organizations.
Downstream, getNodeInfo in packages/server/src/services/docker.ts builds a Docker command by interpolating the untrusted nodeId string directly into a shell invocation passed to execAsyncRemote. Because the SSH transport executes the composed string via a shell, standard metacharacters (;, `, $(...)) break out of the intended docker invocation. Combining both defects lets a low-privileged tenant target another organization's server and run arbitrary commands as the SSH user configured for that server.
Root Cause
The root cause is missing authorization on server-scoped read procedures combined with unsanitized command construction. The router did not call an ownership guard before dispatching to Swarm helpers, and execAsyncRemote received a shell string built through template literal interpolation instead of an escaped argument list.
Attack Vector
Exploitation requires a network-reachable Dokploy instance and an authenticated account holding the server:read permission in any organization on the shared instance. The attacker enumerates or guesses another tenant's serverId, then submits a crafted nodeId containing shell metacharacters to swarm.getNodeInfo. The server executes the injected payload over SSH against the victim tenant's host.
// Security patch: apps/dokploy/server/api/routers/swarm.ts
// fix(security): enforce org-scope on swarm reads and escape nodeId
import { createTRPCRouter, withPermission } from "../trpc";
import { containerIdRegex } from "./docker";
// Ensures a caller-supplied serverId belongs to the caller's active
// organization before any Swarm read runs against it. Without this, the
// server-scoped read procedures were reachable cross-organization (IDOR).
const assertServerInActiveOrg = async (
serverId: string | undefined,
activeOrganizationId: string | undefined,
) => {
if (!serverId) return;
const server = await findServerById(serverId);
if (server.organizationId !== activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
});
}
};
export const swarmRouter = createTRPCRouter({
getNodes: withPermission("server", "read")
.input(
z.object({
serverId: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
await assertServerInActiveOrg(
// Source: https://github.com/Dokploy/dokploy/commit/5563699f71b2058b49eebdfd66c6c3dbd92ede9c
The companion fix in packages/server/src/services/docker.ts introduces shell-quote to escape node identifiers before they reach execAsyncRemote:
// Security patch: packages/server/src/services/docker.ts
import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
import { quote } from "shell-quote";
export const getContainers = async (serverId?: string | null) => {
try {
// Source: https://github.com/Dokploy/dokploy/commit/5563699f71b2058b49eebdfd66c6c3dbd92ede9c
Detection Methods for CVE-2026-72876
Indicators of Compromise
- Requests to tRPC endpoints swarm.getNodes, swarm.getNodeInfo, swarm.getNodeApps, or swarm.getAppInfos where the authenticated user's organization does not own the supplied serverId.
- nodeId values containing shell metacharacters such as ;, &&, |, backticks, or $(...) in application logs.
- Unexpected outbound connections, reverse shells, or new SSH sessions from Dokploy-managed Swarm nodes.
- Anomalous processes spawned by the configured SSH user on managed hosts around the time of Swarm API calls.
Detection Strategies
- Parse Dokploy application logs and correlate serverId values against the caller's activeOrganizationId to flag cross-tenant access attempts.
- Alert on execAsyncRemote invocations whose command strings contain characters outside the expected Docker node identifier pattern.
- Deploy host-based monitoring on Swarm nodes to identify child processes of the SSH daemon that do not match Docker binaries.
Monitoring Recommendations
- Enable verbose tRPC request logging with authenticated user, organization, and input payload captured for audit review.
- Forward Dokploy and Swarm host telemetry into a centralized data lake for behavioral analysis and retention.
- Baseline normal Swarm API usage per tenant and alert on deviations in call volume or parameter shape.
How to Mitigate CVE-2026-72876
Immediate Actions Required
- Upgrade Dokploy to version 0.29.13 or later on every node without delay.
- Rotate SSH keys and credentials used by Dokploy to manage Swarm nodes after patching.
- Audit organization membership and revoke server:read from accounts that no longer require it.
- Review historical logs for cross-tenant serverId usage and suspicious nodeId payloads.
Patch Information
The fix ships in Dokploy 0.29.13, released via the GitHub Release v0.29.13. The patch is implemented in Pull Request #4858 and commit 5563699. Refer to the GitHub Security Advisory GHSA-jj6h-388v-9rwm for coordinated details. The fix adds assertServerInActiveOrg before Swarm reads and uses shell-quote to escape nodeId before shell execution.
Workarounds
- Restrict Dokploy instance access to a single trusted organization until the upgrade is complete.
- Remove the server:read permission from all non-administrator roles as a temporary compensating control.
- Place the Dokploy management interface behind a VPN or identity-aware proxy to limit exposure to authenticated attackers.
# Upgrade Dokploy to the patched release
docker pull dokploy/dokploy:0.29.13
docker service update --image dokploy/dokploy:0.29.13 dokploy
# Verify the running version
docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

