Dokploy Panel RCE: A Member Becomes Host Root
A Dokploy member who can create a database can put a backtick in its password and run commands as root on the host; the fix is on canary only.
TL;DR
- A Dokploy organization member who can create a database service can run commands as root on the host. The trick is a backtick in a database password, which the validator allows, or a semicolon in the database user name, which has no validator at all.
- Dokploy v0.29.0 through v0.31.0 is affected, and v0.31.0, released November 5, 2026, is the current vulnerable release. The attacker needs a member account with the service create permission; this is not an unauthenticated flaw.
- No CVE or CVSS is assigned to this issue. The researcher reports a live proof against the official v0.31.0 image and notes that Dokploy scored the same precondition class 9.9 in CVE-2026-72868. No in-the-wild exploitation is known, and the issue is not in CISA's Known Exploited Vulnerabilities (KEV) catalog as of November 10, 2026.
- The fix is merged on Dokploy's canary branch, not in a tagged release. Remove the service create permission from untrusted members now, then apply the patch or wait for a release. See "How do I fix it?".
Am I affected?
All of these must hold:
- You run self-hosted Dokploy v0.29.0 through v0.31.0. The vulnerable password validator was introduced in v0.29.0 and the researcher says it is unchanged on every branch.
- You give an organization member the service create permission and access to a database service. Dokploy's
changePasswordprocedures checkservice: ["create"]before they run the database command. - The member can reach the panel. In a single-admin install there is no boundary to cross, because the administrator already holds the permission.
The researcher tested the official dokploy/dokploy v0.31.0 image and reports a live proof. The fix is the merge of pull request #5910 on the canary branch, commit 1c862c9cb0, merged November 9, 2026 at 16:59 UTC. The release tags stop at v0.31.0.
| Branch | Affected versions | Fixed version |
|---|---|---|
| Dokploy self-hosted | v0.29.0 through v0.31.0 | No tagged release as of November 10, 2026; the fix is on canary in pull request #5910 |
Check the running image and version:
docker inspect --format '{{.Config.Image}}' dokploy
docker images dokploy/dokploy
The panel also shows its version in the interface. An image tagged v0.29.0 through v0.31.0 is affected. A canary build that includes pull request #5910 has the fix, but it is not a release.
How do I fix it?
There is no fixed release yet. The fix is merged on canary, so choose one of these paths:
- Build or pull a Dokploy image that contains the canary commit for pull request #5910, then redeploy the panel. Confirm the build includes the
quote()call in the database routers before you rely on it. - If you cannot run a canary build, apply the same change to your build: every argument after
docker exec <container>goes throughquote()from theshell-quotepackage. - Until the panel is patched, remove the service create permission from every member who does not need it. Treat a member who can create a database as root-equivalent on the host.
- Restrict access to the Dokploy panel to trusted networks, and do not expose it directly to the internet.
- Review the audit log for database password changes you cannot account for, and rotate database credentials if you find any.
- Watch for a tagged Dokploy release that names pull request #5910 or this fix, then upgrade and confirm the version.
The researcher's paper is the reference for the fix scope. The pull request changes the Postgres, MySQL, MariaDB, MongoDB and Redis password paths.
What is the root cause?
Dokploy's database password validator blocks several shell characters but allows the backtick, semicolon, pipe, ampersand, parentheses and redirects. The changePassword procedures then interpolate the password into a docker exec command that bash parses, and the database user name is interpolated without a validator at all.
This is the validator in apps/dokploy/server/src/db/schema/utils.ts at v0.31.0:
/** Database password: blocks shell-dangerous characters like $ ! ' " \ / and spaces. */
export const DATABASE_PASSWORD_REGEX =
/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/;
The comment names the characters it blocks. The character class still includes `, ;, |, &, (, ), > and <. That is enough for shell command substitution and for a second command.
This is the Postgres changePassword procedure at v0.31.0:
const command = `docker exec ${container.Id} psql -U ${databaseUser} -d postgres -c "ALTER USER \\"${databaseUser}\\" WITH PASSWORD '${password}';"`;
await db.transaction(async (tx) => {
...
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
databaseUser comes from the stored Postgres record and has no validator. It is placed after -U without quoting. The password sits in single quotes, but the whole -c argument is inside double quotes. Bash expands backticks and $() inside double quotes, so a password such as P2`id` runs id while the shell builds the command. A semicolon in databaseUser ends the command and starts another.
The command runs through execAsync with /bin/bash in the Dokploy server process. That process is root in the server container, which holds the host's Docker socket. Command execution there is host root in practice.
The same construction exists in the MySQL, MariaDB and MongoDB routers. The researcher reports firing the Postgres and MySQL paths and calls the other two source-identical. The Redis path puts the password in top-level single quotes and did not fire, which is the control that proves the mechanism.
How is it exploited?
A member who can create a database service changes its password or user name to a value that closes the shell quoting. The Dokploy server then executes the injected command as root.
- The attacker signs in to Dokploy as an organization member with the service create permission, or creates such a member if they already control the organization.
- The attacker creates a database service, or uses one they can access. The service's Postgres record stores a database user name.
- The attacker calls the
changePasswordprocedure with a password that passesDATABASE_PASSWORD_REGEXbut contains a backtick, for exampleP2`id>x`. The validator accepts it because it does not ban the backtick. - Dokploy builds the
docker execcommand and passes it to/bin/bash. Bash performs command substitution on the backtick and runsid, redirecting its output to a file the attacker chose. The database password change also completes. - Alternatively, the attacker sets the database user name to a value containing a semicolon. The user name has no validator, so it reaches the unquoted
psql -Uargument and starts a second command. - The command runs as root in the Dokploy server container. The container holds the host's Docker socket, so the attacker can use it to reach the host and the other tenants' services.
The researcher's live proof used a benign password first, then a $(id) password that the validator rejected. The backtick password then returned 200 and left a canary file reading uid=0(root) gid=0(root) inside the server container. The test ran against the official v0.31.0 image on a localhost-bound rig. The researcher also reports firing the MySQL path as a second sink and a semicolon user name as a third.
No in-the-wild exploitation is known. The public record is the researcher's oss-security message, the paper it links and the merged fix.
How does the attack flow?
A member with the service create permission submits a password the validator accepts, and the password reaches a bash command inside docker exec. Bash then runs the injected command as root in the container that holds the host's Docker socket.
What can an attacker do?
An attacker who fires the injection runs commands as root in the Dokploy server container and can reach the host through its Docker socket. That is control of the panel, every server it manages and every tenant's secrets.
The conditions are part of the impact:
- The attacker needs an authenticated Dokploy member account with the service create permission and access to the database service. The researcher states this is not unauthenticated and not a cross-organization failure.
- In a single-admin install, the administrator already holds the permission, so there is no boundary between the panel user and the host.
- The command runs as root in the server container. That container mounts or reaches the host's Docker socket by design, so the attacker can start privileged containers, read other services' environment variables and reach the host filesystem.
- The injection fires when the password change is submitted. The database password is also changed, so the operation can look successful while the payload runs.
- No in-the-wild exploitation is known, and no victim is named.
The impact is host-level, not database-level. A member who can create a database is already trusted with that service; this flaw turns that trust into control of the host and the other tenants on it.
Why does the fix work?
The fix builds each docker exec command from an argument array with quote() from shell-quote, so the password and user name stay single arguments and bash never parses their contents as syntax.
This is the change in apps/dokploy/server/api/routers/postgres.ts from pull request #5910:
- const command = `docker exec ${container.Id} psql -U ${databaseUser} -d postgres -c "ALTER USER \\"${databaseUser}\\" WITH PASSWORD '${password}';"`;
+ const command = `docker exec ${container.Id} ${quote([
+ "psql",
+ "-U",
+ databaseUser,
+ "-d",
+ "postgres",
+ "-c",
+ `ALTER USER "${databaseUser}" WITH PASSWORD '${password}';`,
+ ])}`;
quote() returns a shell-quoted string for each argument, so a backtick or semicolon inside databaseUser or password stays inside its argument. The SQL statement remains one -c argument, and the database receives the literal password. The pull request says the password is still safe inside the SQL string because the validator forbids the single quote.
The same rewrite covers the MySQL, MariaDB, MongoDB and Redis routers. The fix does not change the validator, so a member can still set a password with a backtick; the difference is that the backtick no longer reaches a shell.
How do I check if I was compromised?
No indicators of compromise are published. Check the panel's audit log and the database records for the inputs the exploit needs.
- Review the Dokploy audit log for database password changes and database service creation you cannot account for. The
changePasswordprocedure writes an audit entry after the command runs. - Inspect the stored database passwords and user names for backticks, semicolons, pipes or other shell metacharacters. A value that the validator allows but that looks like shell syntax is a lead.
- Look for unexpected files in the Dokploy server container, especially a canary file or a shell script created by a database command.
- Review the server container for unexpected root processes, new privileged containers and outbound connections, and check the host's Docker events for containers you did not start.
- If you find a lead, preserve the panel database, the server container and the Docker events before rebuilding the host. Rotate every database and service credential the panel stores.
A failed injection may leave no trace beyond the audit entry for the password change. The audit entry alone does not prove exploitation.
Timeline
- 2026-11-05: Dokploy releases v0.31.0, the current release and the version the researcher tested.
- 2026-11-07: Pull request #5910, "fix(security): database changePassword command injection (all 5 DB types)", is opened.
- 2026-11-09 16:59 UTC: The pull request is merged to the canary branch. No tagged release contains it.
- 2026-11-10 02:11 UTC: The researcher publishes the live-proven disclosure on the oss-security mailing list.
FAQ
Is the Dokploy flaw unauthenticated?
No. The attacker needs an authenticated organization member with the service create permission and access to the database service. The researcher states the precondition plainly and says the flaw is not a cross-organization failure.
Does Dokploy v0.31.0 fix it?
No. v0.31.0 is the current release and the version the researcher tested. The fix is on the canary branch in pull request #5910, merged November 9, 2026, and no tagged release contains it as of November 10, 2026.
Is there a CVE for this issue?
No CVE is assigned as of November 10, 2026. Dokploy scored a different flaw with the same precondition class, CVE-2026-72868, at CVSS v3.1 9.9; that score belongs to the earlier destination.testConnection injection, not to this one.
Is there a public exploit?
The researcher published the method, the live proof and a paper, and the proof is reproducible against the official v0.31.0 image. No standalone exploit repository is linked in the public message.
Should I treat every Dokploy member as root?
A member who can create a database service can reach host root through this flaw, so yes for that permission. Remove service create from members who do not need it, and treat the panel itself as a root-equivalent system.
Does this affect a hosted Dokploy service?
Dokploy is self-hostable software. The disclosure and the fix concern the self-hosted panel. No hosted service is named in the public record, so this post does not make a claim about one.
References
- The researcher's oss-security disclosure, "Dokploy: the validator allows the backtick : a member's database password change is host RCE, 9.9 on the vendor's own vector (v0.31.0, live-proven)", November 9, 2026, with the live proof, the scope and the paper link: https://www.openwall.com/lists/oss-security/2026/11/10/2 (checked November 10, 2026)
- Dokploy pull request #5910, "fix(security): database changePassword command injection (all 5 DB types)", opened November 7, 2026 and merged November 9, 2026: https://github.com/Dokploy/dokploy/pull/5910 (checked November 10, 2026)
- The vulnerable
DATABASE_PASSWORD_REGEXinapps/dokploy/server/src/db/schema/utils.tsat v0.31.0: https://github.com/Dokploy/dokploy/blob/v0.31.0/apps/dokploy/server/src/db/schema/utils.ts#L50-L52 (checked November 10, 2026) - The vulnerable Postgres
changePasswordcommand inapps/dokploy/server/api/routers/postgres.tsat v0.31.0: https://github.com/Dokploy/dokploy/blob/v0.31.0/apps/dokploy/server/api/routers/postgres.ts#L448 (checked November 10, 2026) - Dokploy v0.31.0 release, published November 5, 2026: https://github.com/Dokploy/dokploy/releases/tag/v0.31.0 (checked November 10, 2026)
- CVE-2026-72868 record, the earlier Dokploy
destination.testConnectionrclone injection, CVSS v3.1 9.9, cited as the vendor's score for the same precondition class: https://www.cve.org/CVERecord?id=CVE-2026-72868 (checked November 10, 2026) - Docker Hub repository for
dokploy/dokploy, 16,116,660 pulls, checked November 10, 2026: https://hub.docker.com/r/dokploy/dokploy (checked November 10, 2026) - CISA Known Exploited Vulnerabilities catalog JSON feed, checked November 10, 2026, with no Dokploy entry: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (checked November 10, 2026)