Skip to main content
Vulnerability Database/CVE-2026-104976

CVE-2026-104976: Plane Project Management Tool SSRF Vulnerability

CVE-2026-104976 is a server-side request forgery flaw in Plane that allows attackers to access internal networks through unvalidated Gitea OAuth flows and avatar URLs. This post covers the technical details, affected versions, and mitigation steps.

Published:

CVE-2026-104976 Overview

Plane is an open-source project management tool affected by a Server-Side Request Forgery (SSRF) vulnerability [CWE-918] in versions prior to 1.4.0. The application validates the GITEA_HOST environment variable only for URL scheme and does not reject hosts resolving to private or internal IP addresses. Four outbound requests in the Gitea OAuth flow are derived from this unvalidated host and skip the validate_url() check. Additionally, the avatar_url field is pulled from the Gitea user profile, which allows user-controlled external URLs. An attacker with a Gitea account on a legitimately configured instance can trigger server-side requests to internal targets.

Critical Impact

Authenticated attackers can coerce the Plane backend into issuing arbitrary outbound HTTP requests to internal infrastructure, enabling internal network reconnaissance and access to metadata endpoints.

Affected Products

  • Plane (makeplane/plane) versions prior to 1.4.0
  • Self-hosted Plane deployments with Gitea OAuth enabled
  • Plane instances configured with an administrator-defined GITEA_HOST

Discovery Timeline

  • 2026-10-05 - CVE CVE-2026-104976 published to NVD
  • 2026-10-06 - Last updated in NVD database

Technical Details for CVE-2026-104976

Vulnerability Analysis

The vulnerability resides in Plane's Gitea OAuth integration. The administrator-configured GITEA_HOST value is passed through a scheme-only validation step and used directly for outbound requests during the OAuth handshake. Because the host is never resolved and checked against private, loopback, or link-local address ranges, Plane will attempt requests to any internal endpoint reachable from the backend.

A second attack path involves the avatar_url field returned by the Gitea user profile API. Gitea permits users to configure external avatar URLs on their own profiles. When a Plane user authenticates through Gitea, Plane fetches the avatar from the attacker-controlled URL without passing it through validate_url().

Root Cause

The root cause is incomplete URL validation in the OAuth adapter. The scheme check accepts any http or https host without resolving the hostname or comparing it to a denylist of internal address ranges. Both the host-level requests and the per-user avatar_url fetch bypass the central validate_url() function, violating the project's own SSRF protection boundary.

Attack Vector

Exploitation requires an administrator to enable Gitea OAuth against a legitimate Gitea instance. A low-privileged attacker who can register on that Gitea instance sets their profile avatar to an internal URL such as a cloud metadata service address. On login through Gitea, the Plane backend fetches the avatar from the attacker-supplied internal URL. The attack vector is network-based and requires only low privileges on the OAuth provider.

python
# Patch excerpt - apps/api/plane/authentication/adapter/base.py
 import uuid
 from io import BytesIO
 
-import requests
 from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.core.validators import validate_email
+from plane.utils.url_security import pinned_fetch_following_redirects
 
 # Django imports
 from django.utils import timezone

Source: GitHub Commit 04622ce

The fix replaces the plain requests library with pinned_fetch_following_redirects from plane.utils.url_security, which enforces URL validation and safe redirect handling before each outbound call.

Detection Methods for CVE-2026-104976

Indicators of Compromise

  • Outbound HTTP requests from the Plane backend to RFC1918 ranges, loopback interfaces, or cloud metadata endpoints such as 169.254.169.254.
  • Gitea OAuth login events immediately followed by backend requests to non-Gitea hosts.
  • Unusual DNS resolutions originating from the Plane API container targeting internal hostnames.

Detection Strategies

  • Inspect egress proxy logs for Plane backend requests whose destination differs from the configured GITEA_HOST or Gitea profile domain.
  • Correlate authentication events from the Gitea OAuth adapter with subsequent outbound HTTP calls to detect avatar-fetch SSRF chains.
  • Audit the GITEA_HOST configuration for values that resolve to internal addresses or that lack explicit allowlisting.

Monitoring Recommendations

  • Deploy egress filtering between the Plane backend and internal subnets, cloud metadata services, and management APIs.
  • Enable verbose logging on the OAuth adapter module and ship logs to a central platform for correlation.
  • Alert on repeated 4xx or connection-refused responses from the Plane backend to internal IP ranges, which indicate SSRF probing.

How to Mitigate CVE-2026-104976

Immediate Actions Required

  • Upgrade Plane to version 1.4.0 or later, which introduces pinned_fetch_following_redirects for OAuth and avatar requests.
  • If immediate upgrade is not possible, disable the Gitea OAuth login method in Plane until patched.
  • Review the configured GITEA_HOST and ensure it points only to a trusted external Gitea instance.

Patch Information

The fix is included in Plane v1.4.0. The remediation hardens webhook, link, and OAuth-avatar flows against SSRF by routing all outbound requests through a URL security helper that validates destinations and controls redirect behavior. See the GitHub Security Advisory GHSA-hx79-5pj5-qh42, Pull Request #9163, and the v1.4.0 Release Notes.

Workarounds

  • Place the Plane backend behind an egress proxy that blocks access to RFC1918, loopback, and link-local address ranges.
  • Block outbound access from the Plane backend container to cloud metadata endpoints such as 169.254.169.254.
  • Restrict which Gitea accounts can authenticate to Plane using Gitea organization membership controls.
bash
# Example iptables egress restrictions for the Plane backend host
iptables -A OUTPUT -d 169.254.169.254 -j REJECT
iptables -A OUTPUT -d 10.0.0.0/8 -p tcp --dport 80 -j REJECT
iptables -A OUTPUT -d 10.0.0.0/8 -p tcp --dport 443 -j REJECT
iptables -A OUTPUT -d 172.16.0.0/12 -p tcp --dport 80 -j REJECT
iptables -A OUTPUT -d 192.168.0.0/16 -p tcp --dport 80 -j REJECT

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.