Skip to content

Security: gitProvider.getAll leaks plaintext credentials (privateKey, clientSecret, webhookSecret) for all configured providers #4441

@mrrobertkent

Description

@mrrobertkent

Summary

The gitProvider.getAll tRPC procedure in apps/dokploy/server/api/routers/git-provider.ts returns plaintext credentials (RSA private keys, OAuth client secrets, webhook secrets, access tokens) for every configured git provider (GitHub, GitLab, Bitbucket, Gitea) to any authenticated user with permission to list providers. This includes:

  • GitHub: githubPrivateKey (full RSA PEM-encoded private key), githubClientSecret (40-char hex), githubWebhookSecret (40-char hex)
  • GitLab: secret, accessToken, refreshToken (same shape inferred from schema)
  • Bitbucket: appPassword, apiToken
  • Gitea: clientSecret, accessToken, refreshToken

Verified persists through v0.29.4 (latest release at time of report). The affected router file is unchanged between v0.26.5 and v0.29.4 per gh api repos/Dokploy/dokploy/compare/v0.26.5...v0.29.4.

Impact

Any user / API token with the LIST permission on git providers can extract the GitHub App private key. With the private key + clientId + installationId (also returned), an attacker can mint installation access tokens for any repo the App has access to — a privilege escalation from "can list git providers on this Dokploy instance" to "can push to / read from every repo connected to this Dokploy".

Blast radius is amplified by the fact that the leaked tokens are long-lived (App-level, not session-scoped). Rotation requires manual GitHub-side regeneration.

Reproducer

// Authenticated as any user with git-provider list permission:
const providers = await trpc.gitProvider.getAll.query({});
console.log(providers[0].github?.githubPrivateKey?.startsWith("-----BEGIN")); // true
console.log(providers[0].github?.githubClientSecret?.length); // 40
console.log(providers[0].github?.githubWebhookSecret?.length); // 40

Or via the public MCP tool surface exposed by @dokploy/mcp: invoking mcp__dokploy-mcp__gitProvider-getAll returns the same leaked structure.

Root cause (source code reference)

In apps/dokploy/server/api/routers/git-provider.ts at v0.29.4 (commit b109e0e), the getAll procedure uses:

return await db.query.gitProvider.findMany({
  with: { gitlab: true, bitbucket: true, github: true, gitea: true },
  // ...
});

The with: { github: true } form returns every column of the related table, including the plaintext credential columns. The sibling procedure allForPermissions in the same router demonstrates the correct pattern:

return await db.query.gitProvider.findMany({
  // ...
  with: {
    github: { columns: { /* explicit allowlist of safe columns only */ } },
    // ...
  },
});

Suggested fix

Mirror the allForPermissions pattern in getAll: add columns: {...} selectors to each with clause that explicitly excludes credential columns (githubPrivateKey, githubClientSecret, githubWebhookSecret, GitLab secret / accessToken / refreshToken, Bitbucket appPassword / apiToken, Gitea clientSecret / accessToken / refreshToken). The UI surface that consumes getAll does not appear to need these credential values — they're only used server-side for outbound API calls in worker handlers that read directly from the database.

Alternative (defense-in-depth): add a Drizzle schema-level select redactor middleware that strips fields tagged as secret from every query result outside of an explicit allow-list of callers.

Adjacent precedent

Discussion #3264 "Generated SSH Private Key Visible after generation" (resolved 2026-03-03) addressed the same systemic pattern in the SSH-key UI surface. The fix landed via a similar approach: hide the secret column from default reads.

Environment

  • Dokploy: v0.29.4 (latest at time of report)
  • Affected since: at least v0.26.5 (router file unchanged in the v0.26.5..v0.29.4 diff)
  • Self-hosted (single-server Swarm mode)
  • Reproduction was via @dokploy/mcp@latest invoking the tRPC gitProvider-getAll tool

Severity

P1 — confidentiality breach, not RCE. The auth-required gate prevents anonymous exploitation, but any user with project-level admin access can effectively extract the keys to every connected git repo, persisted long-term and rotatable only via GitHub-side regeneration.

Acknowledgements

Reported by an external Dokploy user during their self-hosted production stand-up; reproducer and fix sketch produced through code archaeology against the v0.29.4 tag.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions