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

CVE-2026-47666: Penpot XSS Vulnerability

CVE-2026-47666 is a stored XSS flaw in Penpot that allows attackers to inject malicious scripts through custom font family names. This post covers technical details, affected versions, impact, and mitigation.

Published:

CVE-2026-47666 Overview

CVE-2026-47666 is a stored cross-site scripting (XSS) vulnerability in Penpot, an open-source design and prototyping platform. The flaw exists in versions up to and including 2.14.3. Penpot accepts arbitrary strings as custom font family names and interpolates them into a @font-face CSS rule. The frontend then writes the resulting style through innerHTML without sanitization. An attacker can supply a font family name containing a closing </style> tag followed by a <script> block, breaking out of the style element and executing JavaScript on the Penpot origin. The issue is fixed in version 2.15.3 and is categorized under [CWE-79].

Critical Impact

Any team member who opens a file referencing a malicious font triggers script execution on the Penpot origin, exposing session cookies, files, and projects.

Affected Products

  • Penpot versions up to and including 2.14.3
  • Fixed in Penpot version 2.15.3
  • Self-hosted and cloud-hosted Penpot instances that share files across team members

Discovery Timeline

  • 2026-08-26 - CVE-2026-47666 published to NVD
  • 2026-08-26 - Last updated in NVD database

Technical Details for CVE-2026-47666

Vulnerability Analysis

The vulnerability is a stored XSS driven by unsanitized CSS injection. Penpot's backend accepts an arbitrary font-family string when a user uploads or registers a custom font. The frontend later constructs a @font-face CSS rule that embeds the attacker-controlled name verbatim. That constructed rule is written into the DOM through innerHTML, which parses HTML tags encountered inside the style content.

A payload containing </style><script>...</script> closes the style element early and injects executable script into the Penpot origin. Because the attack is passive, no user interaction beyond opening the shared file is required. Once triggered, the script runs with the victim's session, granting the attacker access to session cookies, project data, and any action the victim can perform in the application.

Root Cause

The root cause is missing input validation on the font-family field combined with unsafe DOM insertion. The backend did not restrict the character set for font family names, and the frontend used innerHTML instead of a text-safe assignment or CSSOM API. The fix introduces a shared schema in common/src/app/common/types/font.cljc that restricts font family names to Unicode letters, digits, spaces, underscores, dots, and hyphens with a maximum length of 250 characters.

Attack Vector

An authenticated attacker with permissions to upload a custom font uploads a font whose family name contains HTML markup. The attacker then shares a file referencing that font with a target team. When any team member opens the file, the browser renders the malicious style block and executes attacker-controlled JavaScript on the Penpot origin.

clojure
;; Security patch: common/src/app/common/types/font.cljc
;; Introduces a strict schema for font family names to prevent CSS/HTML injection

(ns app.common.types.font
  (:require
   [app.common.schema :as sm]))

(def ^:private font-family-re
  ;; \p{L} (Unicode letter) works in Java regex natively, but in JavaScript it
  ;; requires the "u" flag which ClojureScript regex literals don't support.
  #?(:clj  #"[\p{L}\d _.-]+"
     :cljs (js/RegExp. "[\\p{L}\\d _.-]+" "u")))

(def schema:font-family
  [:and
   [::sm/text {:max 250}]
   [:fn {:error/code "errors.font-family-invalid-chars"}
    (fn [s] (boolean (re-matches font-family-re s)))]])

Source: Penpot commit 67d95679

Detection Methods for CVE-2026-47666

Indicators of Compromise

  • Custom font records where the font-family field contains characters outside [\p{L}\d _.-], especially <, >, /, ", or '.
  • Font family strings containing substrings such as </style>, <script, onerror=, or javascript:.
  • Unexpected outbound network requests from browser sessions of users who recently opened shared Penpot files.
  • Session cookie reuse from unfamiliar IP addresses following access to a shared design file.

Detection Strategies

  • Query the Penpot database for font records whose family name fails the regex ^[\p{L}\d _.-]+$ or exceeds 250 characters.
  • Inspect audit logs for font uploads originating from low-trust or newly created accounts, particularly those followed by file-sharing events.
  • Review Content Security Policy (CSP) violation reports for script-src or style-src blocks on Penpot origins.

Monitoring Recommendations

  • Log and alert on all custom font creation events, including the raw font-family value submitted by the client.
  • Monitor for anomalous session activity such as bulk file access or export operations immediately after a file open.
  • Correlate browser-side CSP reports with user identifiers to identify targeted victims.

How to Mitigate CVE-2026-47666

Immediate Actions Required

  • Upgrade Penpot to version 2.15.3 or later on all self-hosted deployments.
  • Audit existing custom fonts and delete any whose font-family value contains HTML or CSS metacharacters.
  • Rotate session tokens and force re-authentication for users who accessed shared files containing suspicious fonts.

Patch Information

The fix is delivered in Penpot 2.15.3. It adds a validated schema:font-family in common/src/app/common/types/font.cljc and applies it in backend/src/app/rpc/commands/fonts.clj. See the Penpot Security Advisory GHSA-w5hh-gj5c-5wpc and the remediation commit for full details.

Workarounds

  • Restrict custom font upload permissions to trusted administrators until the patch is applied.
  • Enforce a strict Content Security Policy that disallows inline scripts on the Penpot origin.
  • Deploy a reverse-proxy filter that rejects font upload requests with font-family values containing <, >, or /.
bash
# Example nginx filter to block obvious CSS/HTML injection in Penpot font uploads
location /api/rpc/command/create-font-variant {
    if ($request_body ~* "(</style|<script|javascript:|onerror=)") {
        return 400;
    }
    proxy_pass http://penpot_backend;
}

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

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

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.