mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(gitlab): pin pagination cursor to configured host + consolidate isSameOrigin (#4873)
* fix(gitlab): pin pagination cursor to configured host before following it The repository-tree keyset cursor stores GitLab's verbatim rel="next" URL and re-fetches it with an Authorization: Bearer header. Assert the cursor's origin matches the configured apiBase before following it, so a tampered or corrupted fileNextUrl cannot exfiltrate the access token to an attacker-controlled host. Fails closed on mismatch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 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. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
85942a5301
commit
80d966d336
@@ -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()}`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+2
-11
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user