mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-21 13:55:33 +02:00
refactor(security): exclude git provider secrets at query level in findApplicationById
Simpler and consistent with the existing registry column exclusion in the same
query: drop the secret columns from the nested github/gitlab/gitea/bitbucket
relations via columns:{ ...: false } instead of a post-fetch redaction helper.
Server-side clone paths re-fetch providers by id (find{Github,Gitlab,...}ById),
so deployments are unaffected. Column names are validated at compile time by
drizzle's typed columns config.
Closes GHSA-hg9j-j5mc-phf5, GHSA-wx75-vxph-2m2f
This commit is contained in:
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<string, unknown> | null;
|
||||
gitlab?: Record<string, unknown> | null;
|
||||
gitea?: Record<string, unknown> | null;
|
||||
bitbucket?: Record<string, unknown> | null;
|
||||
},
|
||||
>(
|
||||
application: T,
|
||||
): T => {
|
||||
const blank = (
|
||||
provider: Record<string, unknown> | 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),
|
||||
|
||||
Reference in New Issue
Block a user