I found this issue while reviewing Typebot, an open-source chatbot builder, with a simple security question in mind:
What happens if SSRF protection validates hostname text, but the real destination is decided later by DNS?
In this case, that question led to a real bug.
Typebot's SSRF protection for Webhook / HTTP Request blocks validated only:
It did not resolve hostnames before allowing the request.
That meant a hostname like ssrf-repro.example could look harmless during validation, then resolve to:
127.0.0.1169.254.169.254and still be fetched by the backend HTTP client.
That issue became CVE-2026-34207.
Typebot: Typebot on GitHub
CVE: CVE-2026-34207
Fixed in: 3.16.0
This affected Typebot, a widely used open-source chatbot platform. On its official site, Typebot is presented as trusted by 650+ companies worldwide. The site also advertises 2M+ monthly chats and 1.5M+ published bots.
attacker-controlled Webhook URL -> hostname passes literal-only SSRF validation -> no DNS resolution before allow decision -> backend HTTP client resolves hostname to internal target -> server-side request reaches loopback / metadata / private network -> response data becomes available through execution logs
Typebot is a chatbot builder.
It lets users create flows that can:
Webhook / HTTP Request blocksThat means outbound request execution is a real security boundary.
The important question here was not whether Typebot supported Webhook blocks.
The real question was:
Does the SSRF protection validate the actual destination the server will connect to, or only the hostname text that appears in the URL?
In this case, it only validated the text form first.
That was the mistake.
SSRF defenses fail in very predictable ways.
Most of the time, the interesting mistakes are not:
169.254.169.254"localhost"The stronger mistakes are boundary mistakes:
That was the right place to look here.
Typebot already had SSRF hardening logic for literal metadata IPs, loopback, private ranges, and encoded IP tricks.
That made the next question obvious:
What if the hostname is not literally dangerous, but resolves to a dangerous destination later?
That is exactly what happened.
The root issue was destination validation based on hostname text instead of resolved IP address.
In the vulnerable implementation, validateHttpReqUrl():
http: and https:metadata.google.internal, metadata.goog, metadata, and localhostThat is the important part.
If the hostname was a normal value like:
ssrf-repro.example
then parseIPAddress(hostname) returned null, and the validator stopped there.
No DNS resolution happened before approval.
So the vulnerable logic effectively reduced to:
That means:
The second half of the bug was in the execution path.
In executeHttpRequest(), Typebot first ran validation and then later performed the actual request with ky(request.url, ...).
So the sequence was:
That is the whole vulnerability.
The important distinction is where the trust decision was made.
A lot of bugs look small if you describe them badly.
If you describe this one as:
"the hostname filter was incomplete"
it sounds like a quality issue.
That is not the real problem.
The real problem was:
That is not cosmetic filtering weakness.
That is a trust-boundary failure.
And because the HTTP executor recorded response data in execution logs, the issue was not even blind in the strongest cases.
So this was not just:
It was:
That is a real SSRF vulnerability.
I used two proof layers because they demonstrated two different things.
The first PoC isolated the root cause cleanly.
I used a small local harness that:
127.0.0.1http://ssrf-repro.example:18080/...ssrf-repro.example resolved to 127.0.0.1That demonstrated the exact flaw:
The captured output showed:
That proved the validation gap directly.
The second PoC showed the bug through the actual feature path that matters.
The simplest reproduction was:
Webhook block pointing to that benign hostnameA representative block looked like this:
With a hosts-file entry such as:
the validator still accepted the URL because:
ssrf-repro.example was not in blockedHostnameslocalhostparseIPAddress("ssrf-repro.example") returned nullThen the backend HTTP client resolved the hostname to 127.0.0.1 and connected anyway.
That established the full claim:
The first PoC proves root cause.
The second PoC proves product impact.
That split matters.
If you only show:
"this filter accepts a hostname"
you have not shown enough.
If you only show:
"an internal request happened"
you have not isolated why.
The stronger report is:
That is the full story.
This issue was reasonably classified as High.
The advisory classification was:
That makes sense.
This was not an unauthenticated internet-wide SSRF by itself.
The privileges required were Low because the standalone bug required an actor who could configure or trigger a Webhook / HTTP Request block.
But within that boundary, the impact was serious:
That is a strong SSRF issue on its own.
And it gets even more dangerous when combined with other bugs that broaden reachability.
Some people see authenticated SSRF and immediately underrate it.
That is a mistake.
The real question is not:
"Was the attacker logged in?"
The real question is:
"Could that attacker make the server connect somewhere the security model was supposed to forbid?"
Here, the answer was yes.
The validator claimed to protect:
But a hostname-resolution gap let those same destinations back in through a different representation.
That is exactly the kind of bug that deserves a CVE.
The fix was solid because it corrected the real boundary, not just a symptom.
In the patched implementation, validateHttpReqUrl() was changed to resolve hostnames before approving the request.
The validator now:
lookup from node:dns/promisesThat is the right fix because it changes the trust decision from:
to:
That is the security property that should have existed from the start.
The remediation also added regression coverage for:
That is the kind of hardening you want in a real SSRF fix:
The issue was resolved in Typebot 3.16.0.
This issue was reported privately through GitHub's security reporting flow.
The report included:
validateHttpReqUrl()executeHttpRequest()The maintainers accepted the issue, fixed the validation logic, and the vulnerability was later published as:
CVE-2026-34207
with the fix released in Typebot 3.16.0.
The key lesson here is simple:
security decisions must be made on the same representation the network stack will actually use.
That sounds obvious.
But a lot of SSRF protections fail exactly because they do not follow that rule.
That is enough.
This bug also reinforces something important about SSRF review in general:
If you do not resolve and validate the real destination, your SSRF filter is still incomplete.
That is the real takeaway.
This vulnerability was not about a fancy payload.
It was about asking the right boundary question.
In Typebot, the SSRF validator checked the hostname text first. The actual destination was decided later by DNS. The network client followed the resolved address.
That gap was the bug.
That is why this became CVE-2026-34207.
Fixed in Typebot 3.16.0.