We <3 open source at Strix, and we <3 PostHog. It's an awesome tool, and we use it ourselves. So to secure ourselves and the community, we gave Strix the source code and told it to look for security issues.
One of its agents ended up on the survey page. It found that an attacker could create a survey link on the real PostHog domain and use it to run arbitrary JavaScript in another user's signed-in session. In our test, opening the link was enough to give the attacker a personal API key to the victim's account with maximum scope.
The survey looked to be working normally -- the victim had no way to know their account was compromised
Huge shoutout to PostHog for how quickly they remediated this and worked with us. Their team was responsive and moved quickly. They also audited all surveys and confirmed the issue had never been exploited beyond our own tests.
We coordinated disclosure with PostHog and shared a draft of this post with their team for review before publication.
How Strix found it
During a broad source review, a Strix agent noticed an odd gap in the survey code. PostHog had recently added an optional introduction screen: a title, a description, and a “Start” button before the first question.
The description was stored through the survey API and rendered as HTML by the browser SDK.Here is the agent's update from the actual run:
I've found a promising candidate: unsanitized appearance.introScreenDescription stored via the API, rendered by the shipped posthog-js bundle via dangerouslySetInnerHTML on the public hosted survey page — which is only protected by report-only CSP. Let me validate the sink dynamically with the real SDK bundle in a headless browser.
That was a strong lead, but Strix checked whether the backend actually let unsafe content through. It ran test HTML through the new introduction field and an older thank-you field, then checked PostHog's serializer.
The existing field came back cleaned. The new one kept its event handler:
serializer valid: True introScreenDescription : '<img src=x [EVENT HANDLER OMITTED]>' thankYouMessageDescription: '<img src="y">'
The executable handler is omitted from this excerpt. In the original result, it survived in introScreenDescription and was removed from thankYouMessageDescription.
We traced the vulnerable path to posthog-js PR #4436. Its merge commit introduced the browser path from introScreenDescription to an existing dangerouslySetInnerHTML renderer. The backend was already sanitizing older survey fields, but didn't include the new one. The change shipped in posthog-js 1.415.0 on August 10.
The final piece was where PostHog hosted public surveys: on the same origin as the authenticated app. A survey belonged to its creator, but the JavaScript it injected could make authenticated requests as the person who opened the link.
From a survey to an API key
We then worked with Strix using two accounts we controlled. We created a survey in one account and opened it in the other, which had a fresh signed-in session. Strix used the injected script to create a personal API key.
The receiver recorded a 201 response and * scope.
To verify this was a true positive, Strix then used the new key in a separate API request. PostHog returned the second account's identity and its organization owner membership:
[18:46:56] pat-minted
{
"status": 201,
"value": "[REDACTED_API_KEY]",
[nonessential identifier fields omitted]
"scopes": [
"*"
]
}[response fields before this point omitted] "email":"[REDACTED_TEST_ACCOUNT_EMAIL]" [intervening response fields omitted] "membership_level":15 [remaining response fields omitted] HTTP 200
Selected output from the run. The key, account email, and other identifiers are redacted.
The video above shows the same moment: a perfectly ordinary “Help us improve” survey on us.posthog.com, its Start button untouched, while the key has already been minted. Other test surveys returned account and organization information, customer records, and session-recording metadata.
The PR had quite a few checks
The PR ran CodeQL, Semgrep, Wiz SAST, Wiz Secret Scanner, Wiz Vulnerability Scanner, Wiz IaC Scanner, and Wiz Data Scanner, alongside unit, browser, and end-to-end tests. Those named security checks passed. So did those tests. They all missed this.
PostHog's fix, merged September 2, added sanitization for the intro fields and sandboxed hosted surveys without allow-same-origin, isolating survey content from the authenticated app. If you self-host PostHog, make sure your deployment includes that server-side fix.
PostHog has since shipped a strict CSP that disallows unsafe inline JavaScript. They're still considering a separate domain for surveys.
We love PostHog. This is why we build Strix: to find a real issue in a tool we depend on, follow it to a working exploit, and get the details to a team that can fix it.

