CVE-2026-104979 Overview
CVE-2026-104979 is a stored Cross-Site Scripting (XSS) vulnerability [CWE-79] in Plane, an open-source project management tool. Versions prior to 1.4.0 fail to sanitize HTML in the intake issue creation endpoint. The IntakeIssuePublicViewSet.create method writes description_html through Issue.objects.create(...) without invoking validate_html_content from the nh3 sanitizer. Any authenticated user, including a new account with no workspace memberships, can inject arbitrary HTML into projects that expose a published DeployBoard with intake enabled. The payload leverages a TipTap javascript: parser bypass combined with a target="_self" click handler to execute JavaScript in a viewer's session and exfiltrate a long-lived API token.
Critical Impact
Authenticated attackers can hijack project member sessions and steal long-lived API tokens through planted HTML links in intake issue descriptions.
Affected Products
- Plane v1.3.1 and earlier versions prior to 1.4.0
- Plane deployments with published DeployBoards that have intake enabled
- Plane API serializers in apps/api/plane/api/serializers/issue.py and intake views in apps/api/plane/api/views/intake.py
Discovery Timeline
- 2026-10-05 - CVE-2026-104979 published to NVD
- 2026-10-05 - Last updated in NVD database
Technical Details for CVE-2026-104979
Vulnerability Analysis
The vulnerability stems from the intake creation flow accepting user-supplied description_html without routing it through the project's HTML sanitizer. Plane uses the nh3 library wrapped by the helper validate_html_content to strip dangerous markup elsewhere in the application. The intake path bypasses this control entirely, persisting raw HTML into the Issue model.
When a project member or viewer later opens a closed intake item, the stored markup is rendered through the TipTap editor on the client side. A parser quirk permits a javascript: URL to pass link validation, and the editor's target="_self" click handler executes the script in the viewer's authenticated origin. The attacker's payload then reads session storage and exfiltrates a long-lived API token to an attacker-controlled endpoint.
Root Cause
The root cause is missing input sanitization in the intake serializer path. The pre-patch code either accepted raw HTML or only applied lxml round-tripping, which does not strip script URIs. Both comment_html and intake description_html fields reached the database without passing through validate_html_content.
Attack Vector
An attacker creates an authenticated account, locates a public DeployBoard with intake enabled, and submits an intake issue containing a crafted TipTap-compatible link. The attack requires user interaction: a project member or viewer must click the planted link within the closed intake item. On click, JavaScript executes under the viewer's origin, enabling token theft and account takeover.
# Patch: apps/api/plane/api/serializers/issue.py
# Pre-patch logic parsed HTML via lxml without sanitizing script URIs.
# Post-patch routes comment_html through validate_html_content (nh3).
def validate(self, data):
if "comment_html" in data and data["comment_html"]:
is_valid, error_msg, sanitized_html = validate_html_content(data["comment_html"])
if not is_valid:
raise serializers.ValidationError({"comment_html": "HTML content is not valid"})
if sanitized_html is not None:
data["comment_html"] = sanitized_html
return data
Source: GitHub Commit 0d58adb
# Patch: apps/api/plane/api/views/intake.py
# Imports the sanitizer used to clean intake description_html before persistence.
from plane.utils.content_validator import validate_html_content
Source: GitHub Commit 0d58adb
Detection Methods for CVE-2026-104979
Indicators of Compromise
- Intake issues containing anchor tags with javascript: URI schemes in description_html
- Outbound HTTP requests from browser sessions to unknown domains carrying API token values in query strings or request bodies
- Unexpected API activity from long-lived tokens originating from new IP addresses or user agents
- New user accounts created shortly before intake submissions on projects with public DeployBoards
Detection Strategies
- Query the Issue table for description_html and comment_html values matching patterns such as javascript:, data:text/html, or onerror=
- Review web server logs for POST requests to IntakeIssuePublicViewSet endpoints followed by anonymous DeployBoard retrievals
- Correlate TipTap client errors or Content Security Policy violations with intake view sessions
- Audit API token usage logs for tokens that suddenly appear in network traffic patterns distinct from their owner's baseline
Monitoring Recommendations
- Enable Content Security Policy headers with script-src restrictions on the Plane web frontend to limit inline execution
- Alert on any Plane API tokens appearing in referrer headers, URL parameters, or external DNS queries
- Monitor creation of intake issues by accounts with zero workspace memberships, which is the attack precondition
- Track DeployBoard publication events and periodically audit which projects expose public intake
How to Mitigate CVE-2026-104979
Immediate Actions Required
- Upgrade Plane to version 1.4.0 or later, which applies validate_html_content to both comment and intake HTML fields
- Rotate all long-lived API tokens for users who may have viewed intake items on affected projects
- Disable public DeployBoards or disable intake on DeployBoards until the upgrade is complete
- Review and purge existing intake issues containing suspicious HTML in description_html
Patch Information
The fix is included in Plane Release v1.4.0 via Pull Request #9287. The patch imports validate_html_content from plane.utils.content_validator and applies it in both IntakeIssuePublicViewSet.create and the issue serializer's validate method. See the GitHub Security Advisory GHSA-hh2r-3hwp-mvq3 for the full advisory.
Workarounds
- Unpublish any DeployBoards or disable the intake feature on DeployBoards exposed to untrusted users until the patch is applied
- Instruct project members and viewers not to click links inside closed intake items on affected deployments
- Apply a reverse-proxy Content Security Policy that blocks javascript: URL schemes and inline scripts for Plane routes
- Short-circuit long-lived API token exposure by reducing token lifetime or scoping tokens to specific endpoints
# Upgrade Plane via Docker Compose to the patched release
git fetch --tags
git checkout v1.4.0
docker compose pull
docker compose up -d
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

