Skip to content

Commit 6c7e41a

Browse files
committed
improvement(validation): generalize isSameOrigin and reuse across connectors/tools
Add an optional base argument to the shared isSameOrigin (defaulting to the app base URL) so callers can pin a URL to any trusted origin. The GitLab connector's cursor host-check and the tools self-origin check now consume the shared helper instead of their own URL-parsing.
1 parent 0a7ebd8 commit 6c7e41a

3 files changed

Lines changed: 10 additions & 32 deletions

File tree

apps/sim/connectors/gitlab/gitlab.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createLogger } from '@sim/logger'
22
import { getErrorMessage, toError } from '@sim/utils/errors'
33
import { GitLabIcon } from '@/components/icons'
4+
import { isSameOrigin } from '@/lib/core/utils/validation'
45
import { fetchWithRetry, VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils'
56
import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types'
67
import { computeContentHash, joinTagArray, parseTagDate } from '@/connectors/utils'
@@ -103,21 +104,6 @@ function parseNextLink(linkHeader: string | null): string | undefined {
103104
return undefined
104105
}
105106

106-
/**
107-
* Returns true when `candidate` resolves to the same origin as `base`. Used to
108-
* pin a persisted pagination cursor to the configured GitLab host before
109-
* following it with an `Authorization` header, so a tampered or corrupted
110-
* `fileNextUrl` cannot exfiltrate the access token to an attacker-controlled
111-
* host. Returns false on any unparseable URL.
112-
*/
113-
function isSameOrigin(candidate: string, base: string): boolean {
114-
try {
115-
return new URL(candidate).origin === new URL(base).origin
116-
} catch {
117-
return false
118-
}
119-
}
120-
121107
/**
122108
* Returns the ordered list of active sync phases for a content-type choice.
123109
*/

apps/sim/lib/core/utils/validation.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { getBaseUrl } from './urls'
22

33
/**
4-
* Checks if a URL is same-origin with the application's base URL.
5-
* Used to prevent open redirect vulnerabilities.
4+
* Checks if a URL is same-origin with a base URL. Defaults to the application's
5+
* base URL, used to prevent open redirect vulnerabilities; pass an explicit
6+
* `base` to pin a URL to another trusted origin (e.g. a configured API host)
7+
* before following it with credentials.
68
*
79
* @param url - The URL to validate
10+
* @param base - The origin to compare against (defaults to the app base URL)
811
* @returns True if the URL is same-origin, false otherwise (secure default)
912
*/
10-
export function isSameOrigin(url: string): boolean {
13+
export function isSameOrigin(url: string, base: string = getBaseUrl()): boolean {
1114
try {
12-
const targetUrl = new URL(url)
13-
const appUrl = new URL(getBaseUrl())
14-
return targetUrl.origin === appUrl.origin
15+
return new URL(url).origin === new URL(base).origin
1516
} catch {
1617
return false
1718
}

apps/sim/tools/index.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '@/lib/core/utils/stream-limits'
2020
import { getBaseUrl, getInternalApiBaseUrl } from '@/lib/core/utils/urls'
2121
import { isUserFile } from '@/lib/core/utils/user-file'
22+
import { isSameOrigin } from '@/lib/core/utils/validation'
2223
import { SIM_VIA_HEADER, serializeCallChain } from '@/lib/execution/call-chain'
2324
import { parseMcpToolId } from '@/lib/mcp/utils'
2425
import { resolveWorkspaceFileReference } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
@@ -1364,17 +1365,7 @@ function isErrorResponse(
13641365
* the platform's own workflow execution endpoints via absolute URL.
13651366
*/
13661367
function isSelfOriginUrl(url: string): boolean {
1367-
try {
1368-
const targetOrigin = new URL(url).origin
1369-
const publicOrigin = new URL(getBaseUrl()).origin
1370-
if (targetOrigin === publicOrigin) return true
1371-
1372-
const internalOrigin = new URL(getInternalApiBaseUrl()).origin
1373-
if (targetOrigin === internalOrigin) return true
1374-
} catch {
1375-
return false
1376-
}
1377-
return false
1368+
return isSameOrigin(url, getBaseUrl()) || isSameOrigin(url, getInternalApiBaseUrl())
13781369
}
13791370

13801371
/**

0 commit comments

Comments
 (0)