diff --git a/apps/sim/connectors/gitlab/gitlab.ts b/apps/sim/connectors/gitlab/gitlab.ts index a7f0917c7e9..66e71796d60 100644 --- a/apps/sim/connectors/gitlab/gitlab.ts +++ b/apps/sim/connectors/gitlab/gitlab.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage, toError } from '@sim/utils/errors' import { GitLabIcon } from '@/components/icons' +import { isSameOrigin } from '@/lib/core/utils/validation' import { fetchWithRetry, VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils' import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types' import { computeContentHash, joinTagArray, parseTagDate } from '@/connectors/utils' @@ -741,6 +742,9 @@ export const gitlabConnector: ConnectorConfig = { per_page: String(PAGE_SIZE), pagination: 'keyset', }) + if (state.fileNextUrl && !isSameOrigin(state.fileNextUrl, apiBase)) { + throw new Error('GitLab pagination cursor points to an unexpected host') + } const url = state.fileNextUrl ?? `${apiBase}/projects/${encodedProject}/repository/tree?${treeParams.toString()}` diff --git a/apps/sim/lib/core/utils/validation.ts b/apps/sim/lib/core/utils/validation.ts index 5fcfc4d3578..ddb3136c6fa 100644 --- a/apps/sim/lib/core/utils/validation.ts +++ b/apps/sim/lib/core/utils/validation.ts @@ -1,17 +1,18 @@ import { getBaseUrl } from './urls' /** - * Checks if a URL is same-origin with the application's base URL. - * Used to prevent open redirect vulnerabilities. + * Checks if a URL is same-origin with a base URL. Defaults to the application's + * base URL, used to prevent open redirect vulnerabilities; pass an explicit + * `base` to pin a URL to another trusted origin (e.g. a configured API host) + * before following it with credentials. * * @param url - The URL to validate + * @param base - The origin to compare against (defaults to the app base URL) * @returns True if the URL is same-origin, false otherwise (secure default) */ -export function isSameOrigin(url: string): boolean { +export function isSameOrigin(url: string, base: string = getBaseUrl()): boolean { try { - const targetUrl = new URL(url) - const appUrl = new URL(getBaseUrl()) - return targetUrl.origin === appUrl.origin + return new URL(url).origin === new URL(base).origin } catch { return false } diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index d98c6bd0ce3..3c83d634f01 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -19,6 +19,7 @@ import { } from '@/lib/core/utils/stream-limits' import { getBaseUrl, getInternalApiBaseUrl } from '@/lib/core/utils/urls' import { isUserFile } from '@/lib/core/utils/user-file' +import { isSameOrigin } from '@/lib/core/utils/validation' import { SIM_VIA_HEADER, serializeCallChain } from '@/lib/execution/call-chain' import { parseMcpToolId } from '@/lib/mcp/utils' import { resolveWorkspaceFileReference } from '@/lib/uploads/contexts/workspace/workspace-file-manager' @@ -1364,17 +1365,7 @@ function isErrorResponse( * the platform's own workflow execution endpoints via absolute URL. */ function isSelfOriginUrl(url: string): boolean { - try { - const targetOrigin = new URL(url).origin - const publicOrigin = new URL(getBaseUrl()).origin - if (targetOrigin === publicOrigin) return true - - const internalOrigin = new URL(getInternalApiBaseUrl()).origin - if (targetOrigin === internalOrigin) return true - } catch { - return false - } - return false + return isSameOrigin(url, getBaseUrl()) || isSameOrigin(url, getInternalApiBaseUrl()) } /**