This is a poisoned employee badge, not a skeleton key to the building
CVE-2025-21616 is a stored XSS bug in Plane's profile-image upload path. In Plane versions earlier than 0.23, an authenticated user can upload an SVG avatar containing JavaScript; that script runs in another user's browser when the victim views the profile or any page that renders that avatar. The affected component is the Plane API service's profile image upload feature, and the vulnerable range is < 0.23 on all deployment platforms.
The vendor's 5.4 MEDIUM rating is defensible in a lab, but a bit generous for enterprise patch triage. The decisive friction is that exploitation already assumes a valid Plane account and then still needs a victim to render the malicious avatar, which makes this a *post-initial-access, user-interaction-dependent* browser-session issue rather than an initial foothold or infrastructure-level compromise path.
4 steps from start to impact.
Get a Plane account
Burp Suite for session handling and request replay. This is the biggest real-world gate because the bug is not usable by an unauthenticated internet rando.- Attacker has valid Plane credentials or equivalent session access
- Target organization actually runs self-hosted Plane
- Attacker can reach the same tenant/workspace as intended victims
- Many Plane deployments are internal-only or behind SSO
- Guest/member onboarding may be restricted to admins
- Compromising a valid user is already a separate security event
Upload a weaponized SVG avatar
Burp Suite, the attacker uploads an SVG file with embedded JavaScript as a profile image. The GHSA advisory includes a minimal PoC SVG with inline script, so exploit construction is trivial once authenticated. Because the payload is stored server-side, this is a persistent rather than reflected client-side issue.- Profile image upload accepts SVG content
- Server-side validation or sanitization is absent
- Uploaded asset is later rendered by other users' browsers
- Some reverse proxies, storage tiers, or custom WAF rules may already block
image/svg+xml - Tenant-specific hardening may strip or deny active SVG content
- If avatars are disabled or tightly controlled, the path disappears
Wait for a victim to render the avatar
Burp Collaborator-style callbacks or browser devtools can confirm render-time execution.- A victim user visits a page that renders the avatar
- The victim browser executes the SVG's script content
- The victim session is sufficiently privileged to matter
- User interaction is mandatory
- Low-value or inactive accounts may never be viewed
- Modern CSP or browser behavior can break some payload styles
Abuse the victim session in-browser
fetch() abuse or scripted DOM interaction; no memory corruption or server RCE chain is involved. Impact stays bounded to what the victim can do in Plane and what the browser session exposes.- Victim has meaningful Plane privileges
- Session artifacts or sensitive UI/API actions are reachable from browser context
- No CSP or frontend protections block the exfiltration/action path
- Impact is tenant-local and browser-session-local, not host-level compromise
- HttpOnly cookies and stronger CSP reduce easy token theft paths
- Server-side authorization still limits what the victim account can do
The supporting signals.
| In-the-wild status | No primary-source evidence of active exploitation found, and not listed in CISA KEV as checked against the official catalog. |
|---|---|
| Proof of concept | Public PoC exists inside the vendor GitHub advisory as a minimal malicious SVG payload. I did not find a separate weaponized exploit framework in primary sources. |
| EPSS | 0.01012 from your intel block, which is low. EPSS percentile was not independently verified from a primary source during this review. |
| KEV status | No. The authoritative KEV reference is the CISA Known Exploited Vulnerabilities Catalog. |
| CVSS vector meaning | AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N translates to remote reachability, but only after authentication and victim interaction, with low C/I impact and no direct availability hit. |
| Affected versions | Plane < 0.23; affected service is the Plane API Service profile image upload feature, on all supported deployment platforms. |
| Fixed version | Vendor fix is 0.23 or later. Ubuntu's tracker says the issue does not apply to software found in Ubuntu, so there is no distro backport story to lean on there. |
| Exposure / population reality | Plane is not obscure: the vendor advertises 1,000,000+ Docker pulls and thousands of production environments, so the software exists in the wild. That said, the *reachable vulnerable population* is narrower because this bug still needs a logged-in attacker inside the same workspace. |
| Disclosure | Published 2025-01-06 via GitHub advisory GHSA-rcg8-g69v-x23j; NVD shows the CVE was received from GitHub on the same date. |
| Researcher / source | GitHub advisory published by sriramveeraghanta on behalf of the project's security advisory flow. |
noisgate verdict.
The single biggest downward pressure is attacker position: this bug requires an authenticated user in the target Plane workspace before anything interesting can happen. That makes it a *foothold amplifier* inside a collaboration app, not an initial-access or infrastructure-compromise event.
Why this verdict
- Start from 5.4 MEDIUM, then cut for attacker position: exploitation requires a valid Plane account (
PR:L), which means the attacker is already inside the tenant or has already stolen creds. - Cut again for mandatory user interaction: this is stored XSS, but the payload still needs another user to load a page rendering the avatar (
UI:R), so exploitation is not self-driving at internet scale. - Cut for bounded blast radius, add back a little for persistence: impact is generally limited to the victim's Plane browser session and privileges, but the payload is stored and can hit multiple viewers over time in collaborative workflows.
Why not higher?
There is no unauthenticated reach, no direct server-side code execution, and no infrastructure takeover path in the advisory. Requiring *both* a logged-in attacker and a victim page view is compounded friction that keeps this out of MEDIUM/HIGH operational urgency for most enterprises.
Why not lower?
This is still a real stored XSS in a shared work-management platform, not a theoretical parser bug. In environments with external guests, contractors, or broadly shared workspaces, a single low-privilege user could target admins or project owners and perform actions in their browser context.
What to do — in priority order.
- Block SVG avatar uploads — Deny
image/svg+xmlfor profile images at the application, reverse proxy, or object-storage ingress layer. For a LOW verdict there is no SLA (treat as backlog hygiene), so do this in the next normal config cycle if you cannot upgrade immediately. - Enforce a tight CSP — Add or verify a Content Security Policy that prevents inline script execution and sharply restricts script sources for user-rendered content. This is an effective brake on browser-side payload execution and is the vendor's own fallback recommendation; for LOW, deploy during routine hardening rather than as emergency change.
- Constrain who can join the workspace — Limit guest invitations, self-signup, and dormant low-trust accounts because the exploit begins with a valid Plane identity. For LOW, there is no mitigation SLA, but this is smart hygiene for any externally shared collaboration stack.
- Review internet exposure and privileged users — If your Plane instance is exposed beyond VPN or has external collaborators, prioritize checking whether admins and automation users frequently view user profiles or activity feeds. This does not change the formal SLA bucket, but it tells you whether your local risk sits at the top or bottom of that LOW band.
- MFA alone does not fix this; MFA helps stop account theft, but a legitimate or already-compromised Plane user can still upload the malicious SVG.
- Server-side EDR is weak coverage because the exploit executes in the victim browser, not as a process on the Plane host.
- Unauthenticated perimeter scans will often miss this entirely because the vulnerable path is behind login and needs a multi-step stored-XSS workflow.
Crowdsourced verification payload.
Run this on the Plane host or from an auditor shell with read access to the deployment files; it can inspect a supplied Docker Compose file and, if available, running Docker containers. Invoke it as bash check_plane_cve_2025_21616.sh /opt/plane/docker-compose.yml; root is not required unless your Docker socket is root-only.
#!/usr/bin/env bash
# check_plane_cve_2025_21616.sh
# Detect likely exposure to CVE-2025-21616 in self-hosted Plane deployments.
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
THRESHOLD="0.23.0"
COMPOSE_FILE="${1:-}"
FOUND_ANY=0
FOUND_VULN=0
FOUND_UNKNOWN=0
declare -a TAGS
normalize_tag() {
local t="$1"
t="${t#v}"
t="${t%%-*}"
echo "$t"
}
is_semver_like() {
[[ "$1" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]
}
version_lt() {
local a="$1"
local b="$2"
[[ "$(printf '%s\n%s\n' "$a" "$b" | sort -V | head -n1)" != "$b" && "$a" != "$b" ]]
}
collect_from_compose() {
local file="$1"
[[ -f "$file" ]] || return 0
while IFS= read -r line; do
line="$(echo "$line" | sed -E 's/#.*$//')"
if echo "$line" | grep -Eq 'image:[[:space:]]*.*makeplane/'; then
img="$(echo "$line" | sed -E 's/.*image:[[:space:]]*//')"
tag="${img##*:}"
if [[ "$img" == "$tag" ]]; then
TAGS+=("UNKNOWN")
else
TAGS+=("$tag")
fi
fi
done < "$file"
}
collect_from_docker() {
if ! command -v docker >/dev/null 2>&1; then
return 0
fi
local images
images="$(docker ps --format '{{.Image}}' 2>/dev/null | grep 'makeplane/' || true)"
[[ -n "$images" ]] || return 0
while IFS= read -r img; do
[[ -n "$img" ]] || continue
tag="${img##*:}"
if [[ "$img" == "$tag" ]]; then
TAGS+=("UNKNOWN")
else
TAGS+=("$tag")
fi
done <<< "$images"
}
evaluate_tags() {
local seen=0
for raw in "${TAGS[@]}"; do
seen=1
FOUND_ANY=1
if [[ "$raw" == "latest" || "$raw" == "stable" || "$raw" == "dev" || "$raw" == "buildcache" || "$raw" == "UNKNOWN" || -z "$raw" ]]; then
FOUND_UNKNOWN=1
continue
fi
ver="$(normalize_tag "$raw")"
if ! is_semver_like "$ver"; then
FOUND_UNKNOWN=1
continue
fi
# Fixed in 0.23 and later; any 1.x/2.x release is therefore patched.
if version_lt "$ver" "$THRESHOLD"; then
FOUND_VULN=1
fi
done
[[ $seen -eq 1 ]] || FOUND_UNKNOWN=1
}
collect_from_compose "$COMPOSE_FILE"
collect_from_docker
evaluate_tags
if [[ $FOUND_VULN -eq 1 ]]; then
echo "VULNERABLE"
exit 1
fi
if [[ $FOUND_ANY -eq 1 && $FOUND_UNKNOWN -eq 0 ]]; then
echo "PATCHED"
exit 0
fi
echo "UNKNOWN"
exit 2
If you remember one thing.
0.23; if yes, this is not an emergency patch unless your Plane tenant is open to contractors, guests, or broad external collaboration. Under the noisgate mitigation SLA for a LOW finding there is no SLA (treat as backlog hygiene), and under the noisgate remediation SLA there is likewise no fixed deadline—so block SVG avatar uploads if that is easy, then roll 0.23+ into the next routine Plane maintenance cycle and document any deferral rationale.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.