From 0b7b32bdc367a4c712d580bcacf38fe6fa12af59 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 25 Jul 2025 16:55:21 -0400 Subject: [PATCH] Add proper URI component encoding + hostname check --- backend/src/lib/validator/validate-url.ts | 5 ++++ .../github/github-connection-fns.ts | 2 +- .../secret-sync/github/github-sync-fns.ts | 24 +++++++++---------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/backend/src/lib/validator/validate-url.ts b/backend/src/lib/validator/validate-url.ts index 8f195e0b5..a4c07b37d 100644 --- a/backend/src/lib/validator/validate-url.ts +++ b/backend/src/lib/validator/validate-url.ts @@ -14,6 +14,11 @@ export const blockLocalAndPrivateIpAddresses = async (url: string) => { if (appCfg.isDevelopmentMode) return; const validUrl = new URL(url); + + if (validUrl.username || validUrl.password) { + throw new BadRequestError({ message: "URLs with user credentials (e.g., user:pass@) are not allowed" }); + } + const inputHostIps: string[] = []; if (isIPv4(validUrl.hostname)) { inputHostIps.push(validUrl.hostname); diff --git a/backend/src/services/app-connection/github/github-connection-fns.ts b/backend/src/services/app-connection/github/github-connection-fns.ts index b38b9406b..57d01be29 100644 --- a/backend/src/services/app-connection/github/github-connection-fns.ts +++ b/backend/src/services/app-connection/github/github-connection-fns.ts @@ -241,7 +241,7 @@ export const getGitHubEnvironments = async ( return await makePaginatedGitHubRequest( appConnection, gatewayService, - `/repos/${owner}/${repo}/environments`, + `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/environments`, (data) => data.environments ); } catch (error) { diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts index 022490da6..b37d5e90e 100644 --- a/backend/src/services/secret-sync/github/github-sync-fns.ts +++ b/backend/src/services/secret-sync/github/github-sync-fns.ts @@ -26,16 +26,16 @@ const getEncryptedSecrets = async ( let path: string; switch (destinationConfig.scope) { case GitHubSyncScope.Organization: { - path = `/orgs/${destinationConfig.org}/actions/secrets`; + path = `/orgs/${encodeURIComponent(destinationConfig.org)}/actions/secrets`; break; } case GitHubSyncScope.Repository: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/actions/secrets`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/actions/secrets`; break; } case GitHubSyncScope.RepositoryEnvironment: default: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/environments/${destinationConfig.env}/secrets`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/environments/${encodeURIComponent(destinationConfig.env)}/secrets`; break; } } @@ -58,16 +58,16 @@ const getPublicKey = async ( let path: string; switch (destinationConfig.scope) { case GitHubSyncScope.Organization: { - path = `/orgs/${destinationConfig.org}/actions/secrets/public-key`; + path = `/orgs/${encodeURIComponent(destinationConfig.org)}/actions/secrets/public-key`; break; } case GitHubSyncScope.Repository: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/actions/secrets/public-key`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/actions/secrets/public-key`; break; } case GitHubSyncScope.RepositoryEnvironment: default: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/environments/${destinationConfig.env}/secrets/public-key`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/environments/${encodeURIComponent(destinationConfig.env)}/secrets/public-key`; break; } } @@ -96,16 +96,16 @@ const deleteSecret = async ( let path: string; switch (destinationConfig.scope) { case GitHubSyncScope.Organization: { - path = `/orgs/${destinationConfig.org}/actions/secrets/${encryptedSecret.name}`; + path = `/orgs/${encodeURIComponent(destinationConfig.org)}/actions/secrets/${encodeURIComponent(encryptedSecret.name)}`; break; } case GitHubSyncScope.Repository: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/actions/secrets/${encryptedSecret.name}`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/actions/secrets/${encodeURIComponent(encryptedSecret.name)}`; break; } case GitHubSyncScope.RepositoryEnvironment: default: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/environments/${destinationConfig.env}/secrets/${encryptedSecret.name}`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/environments/${encodeURIComponent(destinationConfig.env)}/secrets/${encodeURIComponent(encryptedSecret.name)}`; break; } } @@ -135,7 +135,7 @@ const putSecret = async ( switch (destinationConfig.scope) { case GitHubSyncScope.Organization: { const { visibility, selectedRepositoryIds } = destinationConfig; - path = `/orgs/${destinationConfig.org}/actions/secrets/${payload.secret_name}`; + path = `/orgs/${encodeURIComponent(destinationConfig.org)}/actions/secrets/${encodeURIComponent(payload.secret_name)}`; body = { ...payload, visibility, @@ -146,12 +146,12 @@ const putSecret = async ( break; } case GitHubSyncScope.Repository: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/actions/secrets/${payload.secret_name}`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/actions/secrets/${encodeURIComponent(payload.secret_name)}`; break; } case GitHubSyncScope.RepositoryEnvironment: default: { - path = `/repos/${destinationConfig.owner}/${destinationConfig.repo}/environments/${destinationConfig.env}/secrets/${payload.secret_name}`; + path = `/repos/${encodeURIComponent(destinationConfig.owner)}/${encodeURIComponent(destinationConfig.repo)}/environments/${encodeURIComponent(destinationConfig.env)}/secrets/${encodeURIComponent(payload.secret_name)}`; break; } }