diff --git a/apps/dokploy/__test__/application/application-git-secret-redaction.test.ts b/apps/dokploy/__test__/application/application-git-secret-redaction.test.ts deleted file mode 100644 index 2866e8669..000000000 --- a/apps/dokploy/__test__/application/application-git-secret-redaction.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { redactApplicationGitSecrets } from "@dokploy/server/services/application"; -import { describe, expect, it } from "vitest"; - -describe("redactApplicationGitSecrets (application.one secret disclosure guard)", () => { - it("blanks github secrets while keeping non-secret fields", () => { - const app = { - applicationId: "app-1", - github: { - githubId: "gh-1", - githubClientId: "public-client-id", - githubClientSecret: "SECRET", - githubPrivateKey: "-----BEGIN KEY-----", - githubWebhookSecret: "whsec", - githubInstallationId: "12345", - }, - }; - - const out = redactApplicationGitSecrets(app); - - expect(out.github.githubClientSecret).toBe(""); - expect(out.github.githubPrivateKey).toBe(""); - expect(out.github.githubWebhookSecret).toBe(""); - // Non-secret identifiers must survive so the UI can still show the link. - expect(out.github.githubClientId).toBe("public-client-id"); - expect(out.github.githubInstallationId).toBe("12345"); - expect(out.applicationId).toBe("app-1"); - }); - - it("blanks gitlab / gitea / bitbucket tokens and passwords", () => { - const app = { - gitlab: { secret: "s", accessToken: "at", refreshToken: "rt" }, - gitea: { clientSecret: "cs", accessToken: "at", refreshToken: "rt" }, - bitbucket: { appPassword: "pw", apiToken: "tok" }, - }; - - const out = redactApplicationGitSecrets(app); - - expect(out.gitlab).toMatchObject({ - secret: "", - accessToken: "", - refreshToken: "", - }); - expect(out.gitea).toMatchObject({ - clientSecret: "", - accessToken: "", - refreshToken: "", - }); - expect(out.bitbucket).toMatchObject({ appPassword: "", apiToken: "" }); - }); - - it("does not mutate the original application", () => { - const app = { github: { githubClientSecret: "SECRET" } }; - redactApplicationGitSecrets(app); - expect(app.github.githubClientSecret).toBe("SECRET"); - }); - - it("handles applications without any git provider relation", () => { - const app: { - applicationId: string; - github?: null; - gitlab?: null; - gitea?: null; - bitbucket?: null; - } = { applicationId: "app-2", github: null }; - expect(redactApplicationGitSecrets(app)).toMatchObject({ - applicationId: "app-2", - github: null, - }); - }); -}); diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 722ffeedb..e1bbedcb9 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -13,7 +13,6 @@ import { mechanizeDockerContainer, readConfig, readRemoteConfig, - redactApplicationGitSecrets, removeDeployments, removeDirectoryCode, removeMonitoringDirectory, @@ -184,7 +183,7 @@ export const applicationRouter = createTRPCRouter({ } return { - ...redactApplicationGitSecrets(application), + ...application, hasGitProviderAccess, unauthorizedProvider, }; diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index 38b1c172c..0f2e5a4fc 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -102,10 +102,24 @@ export const findApplicationById = async (applicationId: string) => { redirects: true, security: true, ports: true, - gitlab: true, - github: true, - bitbucket: true, - gitea: true, + gitlab: { + columns: { secret: false, accessToken: false, refreshToken: false }, + }, + github: { + columns: { + githubClientSecret: false, + githubPrivateKey: false, + githubWebhookSecret: false, + }, + }, + bitbucket: { columns: { appPassword: false, apiToken: false } }, + gitea: { + columns: { + clientSecret: false, + accessToken: false, + refreshToken: false, + }, + }, server: true, previewDeployments: true, registry: { columns: { password: false } }, @@ -122,51 +136,6 @@ export const findApplicationById = async (applicationId: string) => { return application; }; -// Blanks the nested git-provider secret columns before an application is sent to -// a client (server-side clone paths use findApplicationById directly). -export const redactApplicationGitSecrets = < - T extends { - github?: Record | null; - gitlab?: Record | null; - gitea?: Record | null; - bitbucket?: Record | null; - }, ->( - application: T, -): T => { - const blank = ( - provider: Record | null | undefined, - fields: readonly string[], - ) => { - if (!provider) return provider; - const redacted = { ...provider }; - for (const field of fields) { - if (field in redacted) redacted[field] = ""; - } - return redacted; - }; - - return { - ...application, - github: blank(application.github, [ - "githubClientSecret", - "githubPrivateKey", - "githubWebhookSecret", - ]), - gitlab: blank(application.gitlab, [ - "secret", - "accessToken", - "refreshToken", - ]), - gitea: blank(application.gitea, [ - "clientSecret", - "accessToken", - "refreshToken", - ]), - bitbucket: blank(application.bitbucket, ["appPassword", "apiToken"]), - }; -}; - export const findApplicationByName = async (appName: string) => { const application = await db.query.applications.findFirst({ where: eq(applications.appName, appName),