This is a booby-trapped badge photo pinned to a public bulletin board, not a master key to the building
CVE-2026-39970 is a stored XSS flaw in Typebot's profile-picture upload flow: affected builds <= 3.15.2 let an authenticated user upload a malicious SVG as an avatar, and the file is then publicly retrievable. The bug is persistence, not code execution on the server; the attacker plants active browser content inside an uploaded SVG and waits for someone to open the hosted file.
The original advisory language calls this *critical*, but that overshoots the real deployment story. In practice the chain needs authenticated upload rights, a victim who opens the raw SVG URL, and useful security context on the file-serving origin; on Typebot Cloud the sample payload is hosted from s3.typebot.io, which materially limits blast radius versus a same-origin app XSS. That combination keeps this out of the emergency lane even though the underlying bug is valid.
5 steps from start to impact.
Get low-privileged builder access with Burp or a browser
Burp Suite, they upload an SVG containing scriptable payloads instead of a benign image.- Authenticated access to a vulnerable Typebot instance
- Version
<= 3.15.2 - Profile/avatar upload allowed for the account
- This is not unauthenticated internet-to-RCE; the attacker already has an account
- Many enterprise Typebot deployments are admin-only or tightly scoped to internal staff
Land the payload on public object storage
- Upload succeeds without SVG sanitization
- Uploaded object is retrievable over HTTP(S)
- The advisory sample shows storage on
s3.typebot.io, not the main app origin - If the browser treats the file as an image in an
<img>context, script usually does not execute
.svg uploads and fetches.Social-engineer the victim into opening the raw SVG
- Victim receives the exact file URL
- Victim opens it in a browser context that executes SVG script
- This is a *user-action* bug, not an auto-trigger from merely existing in storage
- Secure email gateways, browser isolation, and user skepticism break a lot of these campaigns
Execute JavaScript on the file origin
<script>, external fetches, or credential-harvesting DOM overlays.- Browser executes active content in the delivered SVG
- Response headers and embedding context do not suppress execution
- Running on a storage origin is not the same as running on
app.typebot.io - Same-origin protections block direct access to app cookies/local storage unless cookie scoping or self-hosting choices collapse the boundary
Monetize only if origin boundaries are weak
- Shared auth scope, weak cookie domain settings, or same-origin file serving
- Victim has useful privileges
- Default cloud-style split hosting heavily reduces value
- HTTPOnly cookies, origin separation, and modern browser controls limit post-execution leverage
The supporting signals.
| In-the-wild status | No public evidence of active exploitation found as of 2026-05-29 in the sources reviewed; not KEV-listed. |
|---|---|
| Proof-of-concept availability | Public PoC is effectively included in the GitHub advisory: malicious SVG upload plus a public object URL and demo artifact. That makes reproduction straightforward for anyone with a low-priv account. |
| EPSS | User-supplied EPSS is 0.00052. That is extremely low and lines up with the real-world friction in the exploit chain. |
| KEV status | No entry in CISA KEV. No KEV due date applies. |
| Affected versions | GitHub advisory lists Typebot <= 3.15.2 as affected. |
| Fixed version | Patched in 3.16.0. Release notes also mention fix XSS on Rating and file upload inputs. |
| Severity baseline | There is no authoritative vendor CVSS baseline on the advisory/NVD pages reviewed, so this rating is first-principles assessed, not compared. |
| Practical vector | Realistically this behaves like authenticated remote upload + victim interaction + client-side execution on the file-hosting origin. That is materially weaker than a same-origin stored XSS in the main app. |
| Exposure/scanning reality | Internet search engines are the wrong lens here: the risky step is an authenticated upload workflow followed by public object access, not a distinct exposed service banner. Public exposure volume therefore depends more on who can get builder accounts than on Shodan/Censys discoverability. |
| Disclosure and credit | Published 2026-05-22 via GitHub Security Advisory GHSA-jj87-c343-26vp; reporter credited as bugshunter68-lgtm. |
noisgate verdict.
The decisive downgrade from the advisory's *critical* tone is attacker position: this starts with authenticated upload rights and usually ends on a separate file-hosting origin, not the main application origin. That combination slashes both reachable population and post-execution blast radius in real enterprise deployments.
Why this verdict
- Requires authenticated remote access: the attacker must already hold a valid Typebot account that can hit the avatar upload flow. That is immediate downward pressure because this is post-account-acquisition, not perimeter compromise.
- Needs victim interaction: the payload is not a worm sitting in the main UI; a victim must open the raw SVG or equivalent active rendering context. Email security, browser isolation, and ordinary user skepticism stop a meaningful slice of attempts.
- Default blast radius is usually origin-limited: the advisory's own sample shows hosting from
s3.typebot.io, which means script execution is likely on the storage origin rather thanapp.typebot.io. Unless self-hosting choices collapse that boundary, the attacker gets less than a normal same-origin stored XSS.
Why not higher?
It is not higher because the chain stacks three big frictions: authenticated access, victim click/open, and likely origin separation. Those are compounding brakes, not minor caveats. A same-origin admin-panel stored XSS with unauthenticated planting or automatic trigger behavior would be a different conversation.
Why not lower?
It is not lower because the bug is persistent, trivially reproducible, and weaponizable on a trusted vendor domain. If your deployment serves uploads from the same origin, uses broad cookie domains, or exposes builder access to many semi-trusted users, impact can jump from annoyance to real credential or session abuse.
What to do — in priority order.
- Block SVG avatar uploads — Enforce MIME and extension restrictions for profile/avatar uploads so only non-active image types like PNG/JPEG are accepted. If you cannot patch immediately, deploy this as your first compensating control; for a MEDIUM verdict there is no mitigation SLA, but it is still the cleanest temporary risk reduction.
- Force downloads for uploaded SVG — Serve any existing
.svgobjects withContent-Disposition: attachmentand a non-executable content type where your stack allows it. This reduces script-capable rendering while you move to 3.16.0 within the remediation window. - Separate auth and file origins — Keep uploaded content on an origin that does not share cookies, local storage, or parent-domain auth scope with the builder app. This matters most on self-hosted deployments and should be validated before your 365-day remediation deadline.
- Audit builder-account sprawl — Review who can create or edit workspaces and who can upload profile assets. Because exploitation starts with a valid account, pruning stale or over-privileged builder users meaningfully lowers exposure while you schedule the patch.
- Hunt for existing malicious SVGs — Search your object store or upload directory for
.svgavatars and unusual XML/script content. Removing already-planted payloads limits persistence even if some nodes remain unpatched during the remediation window.
- A generic network IPS does not solve this because the dangerous payload is a valid file upload and later a normal HTTPS object fetch.
- Relying on perimeter internet-exposure scans does not solve this because the exploitable step is behind authentication and user workflow, not a naked bannered service.
- Assuming
HttpOnlyalone makes it harmless does not solve this because attackers can still phish, abuse DOM APIs on the file origin, or exploit weak self-hosted origin/cookie layouts.
Crowdsourced verification payload.
Run this on the Typebot host or inside the Typebot container where the application files are present. Invoke it as sudo bash check_cve_2026_39970.sh /opt/typebot or docker exec -it <typebot-container> bash /tmp/check_cve_2026_39970.sh /app; it needs read access to package.json only.
#!/usr/bin/env bash
# check_cve_2026_39970.sh
# Determine whether a local Typebot installation is affected by CVE-2026-39970.
# Usage: bash check_cve_2026_39970.sh /path/to/typebot
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN / error
set -euo pipefail
TARGET_DIR="${1:-}"
if [[ -z "$TARGET_DIR" ]]; then
echo "UNKNOWN: missing path argument"
exit 2
fi
PKG="$TARGET_DIR/package.json"
if [[ ! -f "$PKG" ]]; then
echo "UNKNOWN: package.json not found at $PKG"
exit 2
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "UNKNOWN: python3 is required for version comparison"
exit 2
fi
VERSION=$(python3 - <<'PY' "$PKG"
import json, sys
p = sys.argv[1]
try:
with open(p, 'r', encoding='utf-8') as f:
data = json.load(f)
v = data.get('version')
if not v:
print('')
else:
print(str(v).strip())
except Exception:
print('')
PY
)
if [[ -z "$VERSION" ]]; then
echo "UNKNOWN: could not read version from $PKG"
exit 2
fi
python3 - <<'PY' "$VERSION"
import re, sys
from itertools import zip_longest
version = sys.argv[1].strip()
# Normalize strings like v3.15.2, 3.15.2-beta, etc.
def normalize(v):
v = v.lstrip('vV')
main = re.split(r'[-+]', v, 1)[0]
parts = [int(x) for x in main.split('.') if x.isdigit()]
return parts
def cmp(a, b):
for x, y in zip_longest(a, b, fillvalue=0):
if x < y:
return -1
if x > y:
return 1
return 0
cur = normalize(version)
if not cur:
print('UNKNOWN: unparseable version')
sys.exit(2)
vuln_max = normalize('3.15.2')
patched_min = normalize('3.16.0')
if cmp(cur, vuln_max) <= 0:
print(f'VULNERABLE: Typebot {version} <= 3.15.2')
sys.exit(1)
elif cmp(cur, patched_min) >= 0:
print(f'PATCHED: Typebot {version} >= 3.16.0')
sys.exit(0)
else:
print(f'UNKNOWN: Typebot {version} falls in an unexpected comparison state')
sys.exit(2)
PYIf you remember one thing.
<= 3.15.2, verify whether uploads are served from a separate origin, and restrict or strip SVG avatar uploads anywhere untrusted users can get builder accounts. For a MEDIUM verdict there is no noisgate mitigation SLA — go straight to the 365-day remediation window; plan the actual upgrade to 3.16.0 within the noisgate remediation SLA of 365 days, but pull that forward if your deployment serves uploads same-origin or shares cookies across the file and app domains.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.