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:
Mauricio Siu
2026-07-19 21:43:51 -06:00
parent c0afc48da8
commit ecbaf6060b
3 changed files with 19 additions and 121 deletions

View File

@@ -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),