Skip to content

Commit 7ebcc19

Browse files
committed
fix(posthog): reject unsafe self-hosted host values (SSRF)
The self-hosted host field accepted any string and only stripped trailing slashes before prepending https://, so a workflow could point it at loopback/private/link-local addresses (e.g. cloud instance- metadata endpoints) and the executor would make a real server-side request to it. Reuse the shared validateExternalUrl SSRF guard, same pattern already used for Convex's custom deployment URL (tools/convex/utils.ts); the tool executor separately re-validates with DNS resolution and pins the resolved IP for the actual request. Also drop an unused params arg in evaluate_flags' headers function.
1 parent fdecd8a commit 7ebcc19

2 files changed

Lines changed: 13 additions & 3 deletions

File tree

apps/sim/tools/posthog/evaluate_flags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export const evaluateFlagsTool: ToolConfig<EvaluateFlagsParams, EvaluateFlagsRes
8080
return `${baseUrl}/flags/?v=2`
8181
},
8282
method: 'POST',
83-
headers: (params) => ({
83+
headers: () => ({
8484
'Content-Type': 'application/json',
8585
}),
8686
body: (params) => {

apps/sim/tools/posthog/utils.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { validateExternalUrl } from '@/lib/core/security/input-validation'
2+
13
/**
24
* Shared PostHog base URL resolution.
35
*
@@ -7,7 +9,10 @@
79
* - The "ingest" API (`/i/v0/e`, `/batch/`, `/flags/`) at `us.i.posthog.com` / `eu.i.posthog.com`,
810
* authenticated with a project API key.
911
*
10-
* Self-hosted PostHog instances serve both families from a single custom host.
12+
* Self-hosted PostHog instances serve both families from a single custom host. That host is
13+
* validated with the shared SSRF guard so it can't be pointed at loopback/private/link-local
14+
* addresses (e.g. cloud instance-metadata endpoints); the tool executor additionally
15+
* re-validates with DNS resolution and pins the resolved IP for the actual request.
1116
*/
1217

1318
export function getPostHogAppBaseUrl(region?: 'us' | 'eu', host?: string): string {
@@ -26,5 +31,10 @@ export function getPostHogIngestBaseUrl(region?: 'us' | 'eu', host?: string): st
2631

2732
function normalizeHost(host: string): string {
2833
const trimmed = host.trim().replace(/\/+$/, '')
29-
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`
34+
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`
35+
const validation = validateExternalUrl(withProtocol, 'Self-hosted host')
36+
if (!validation.isValid) {
37+
throw new Error(`${validation.error} (e.g., posthog.mycompany.com)`)
38+
}
39+
return withProtocol
3040
}

0 commit comments

Comments
 (0)