Skip to content

fix: prevent SSH private key from leaking into build-failure notifications#4630

Open
jsheely wants to merge 3 commits into
Dokploy:canaryfrom
jsheely:feat/build-error-msg
Open

fix: prevent SSH private key from leaking into build-failure notifications#4630
jsheely wants to merge 3 commits into
Dokploy:canaryfrom
jsheely:feat/build-error-msg

Conversation

@jsheely

@jsheely jsheely commented Jun 13, 2026

Copy link
Copy Markdown

Problem

When a deployment fails, the build-failure notification (Discord, Slack, Telegram, etc.) shows the SSH private key instead of the actual build error. The "Error Message" field renders something like:

Command execution failed: Command failed: (set -e;set -e;
            echo "-----BEGIN OPENSSH PRIVATE KEY-----
...

This is both a secret-leak (the deploy key ends up in a chat channel) and a usability bug (the real build error is hidden, even though it shows correctly on the deployment tab).

Root cause

A deployment runs as a single shell command whose entire output is redirected into the deployment log file:

(set -e; <clone repo>; <apply patches>; <build>) >> ${logPath} 2>&1

For git/SSH sources, the clone step writes the deploy key inline:

echo "${sshKey.privateKey}" > /tmp/id_rsa

When a later step (e.g. the build) fails, Node's child_process.exec sets error.message to the whole command string (Command failed: <command>), which therefore contains the echoed private key. Because 2>&1 already routed the real output into the log file, error.stderr is empty — so the error handler fell back to that raw error.message and passed it straight to sendBuildErrorNotifications.

The actual build error only exists in the log file (which never contains the key, since the echo redirects the key to a file rather than stdout).

Solution

Add getDeploymentErrorMessage in services/deployment.ts, which reads the tail of the deployment log — the real build output — and use that as the notification's error message for both application and compose deployments:

  • Local deployments: read the last N lines of the log file.
  • Remote deployments: tail -n N <logPath> over SSH.
  • Falls back to a generic "Error building, check the logs for details." when the log is missing/empty/unreadable.

Because the message is now sourced from the log instead of Node's command string, the private key can no longer leak, and notifications show the actual build error.

Changes

  • packages/server/src/services/deployment.ts — new getDeploymentErrorMessage helper.
  • packages/server/src/services/application.ts — use it in the deploy error handler.
  • packages/server/src/services/compose.ts — use it in the deploy error handler.
  • apps/dokploy/__test__/deploy/deployment-error-message.test.ts — unit tests.

Testing

Added deployment-error-message.test.ts (9 tests, all passing) covering:

  • Returns the real build error from the log rather than the fallback.
  • Never returns the SSH private key (message is sourced from the log, not the command string).
  • Falls back when the log is missing, empty, or the path is ""/".".
  • Returns only the last maxLines lines.
  • Remote path reads the tail over SSH and falls back on SSH failure / empty log.

Existing application.command.test.ts still passes (no regression).

jsheely added 2 commits June 13, 2026 09:44
…d of leaking SSH key

Deployments run as a single shell command whose output is redirected into
the log file ((...) >> logPath 2>&1). When that command fails, Node's exec
error.message is the entire command string, which for git/ssh sources
contains the echo "<private key>" used to write the deploy key. That raw
message was passed straight to build-error notifications, so Discord (and
other channels) showed the SSH private key instead of the actual build error.

Add getDeploymentErrorMessage to read the tail of the deployment log (the
real build output, which never contains the key) and use it as the
notification error message for both application and compose deployments.
Adds unit tests verifying the deployment error message is read from the
deployment log (the real build error) rather than the raw command string,
so the SSH private key can no longer leak into notifications. Covers local
and remote reads, line limiting, and fallback behaviour.
@jsheely jsheely requested a review from Siumauricio as a code owner June 13, 2026 13:58
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Jun 13, 2026
@jsheely

jsheely commented Jun 13, 2026

Copy link
Copy Markdown
Author

I will note that at least the SSH key is clipped so while it does expose your key it's only about a third of the entire thing and not usable.

Discord, Lark and Teams capped the error message with
errorMessage.substring(0, 800), which keeps the FIRST 800 characters. Since
the deployment log tail is sent as the error and the actual build error is at
the bottom, this showed the top of the log and cut off the real error.

Extract a truncateErrorMessage helper that keeps the LAST `limit` characters
(prefixed with "...") and use it in all three channels.
@jsheely

jsheely commented Jun 13, 2026

Copy link
Copy Markdown
Author

Image of working solution

image

@jsheely

jsheely commented Jun 13, 2026

Copy link
Copy Markdown
Author

Would be nice if we could tease into those docker build file logs and obtain the actual build failure. In this case I have a test project that is meant to fail

https://github.com/jsheely/dokploy-build-failure

Ideally I would like to get the vite build error not the docker build failure message but that is a job for another PR.

image

For clarity this is the message from the log. Which is accurate but buries the actionable error message so the discord message won't see it and you have to view the logs.

image

But like I said, a future PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant