VAITP Dataset

← Back to the dataset

CVE-2026-55665

Grist < 1.7.15 XSS in hrefs allows an editor to escalate to owner access.

  • CVSS 8.5
  • CWE-79
  • Input Validation and Sanitization
  • Remote

Grist is spreadsheet software using Python as its formula language. Prior to 1.7.15, Grist contained two cross-site scripting vulnerabilities where an attacker-controlled value reached a link's href without scheme validation, so a javascript URL could run in a victim's Grist origin on a single click. On the account-selection page, /welcome/select-account used its next query parameter as the account buttons' link target. In document tours, the GristDocTour table's Link_URL column became a clickable button, allowing an editor of a shared document to store a javascript URL there that ran when another user opened the document and clicked the tour link. Because the script runs in the victim's authenticated session, it can call Grist APIs as the victim, reading or modifying data and changing sharing settings and access rules. A document editor could therefore escalate to owner-level access. This issue is fixed in version 1.7.15.

CVSS base score
8.5
Published
2026-07-10
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Cross-Site Scripting (XSS)
Accessibility scope
Remote
Impact
Privilege Escalation
Affected component
Grist

Solution

Upgrade Grist to version 1.7.15 or later.

Vulnerable code sample

from flask import Flask, request, render_template_string

app = Flask(__name__)

# This code simulates the vulnerable '/welcome/select-account' endpoint from Grist
# prior to version 1.7.15. It takes the 'next' query parameter and uses it
# directly in a link's href attribute without any validation of the URL scheme.
# An attacker could supply a 'javascript:' URL to execute arbitrary code.
@app.route('/welcome/select-account')
def select_account():
    # The 'next' parameter is fetched directly from the user-controlled query string.
    # Example malicious URL: /welcome/select-account?next=javascript:alert('XSS')
    next_url = request.args.get('next', '/')

    # The template directly injects the unvalidated 'next_url' into the href
    # attribute of the account buttons. This is the source of the XSS vulnerability.
    template = """
<!DOCTYPE html>
<html>
<head>
    <title>Select Account</title>
</head>
<body>
    <h1>Select an Account</h1>
    <a href="{{ link_target }}">
        <button>Personal Account</button>
    </a>
    <br><br>
    <a href="{{ link_target }}">
        <button>Work Account</button>
    </a>
</body>
</html>
    """
    return render_template_string(template, link_target=next_url)

Patched code sample

import urllib.parse

def sanitize_url_for_href(url):
    """
    Represents the fix for CVE-2026-55665 by validating a URL's scheme
    before it is used in a link's href attribute.

    This prevents Cross-Site Scripting (XSS) by rejecting URLs with unsafe
    schemes like 'javascript:'. If the URL is unsafe, it returns a safe
    fallback value '#'.

    Args:
      url: The user-provided URL string to validate.

    Returns:
      The original url if it has a safe scheme (http, https) or is a
      valid relative path starting with '/', otherwise returns '#'.
    """
    if not isinstance(url, str):
        return '#'

    try:
        # Use urlparse to reliably extract the scheme from the URL.
        parsed_url = urllib.parse.urlparse(url)
        
        # Define a whitelist of schemes considered safe for use in an href.
        # An empty scheme ('') is included to allow for relative paths.
        safe_schemes = ('http', 'https', '')

        if parsed_url.scheme in safe_schemes:
            # If the URL is a relative path (scheme is ''), ensure it starts
            # with '/' to prevent misinterpretation by the browser.
            if parsed_url.scheme == '' and not url.startswith('/'):
                return '#'
            
            # If the scheme is safe or it's a valid relative path, return the URL.
            return url
        
        # If the scheme is not in the whitelist (e.g., 'javascript', 'data'),
        # it is considered unsafe.
        return '#'
        
    except ValueError:
        # In case of any parsing errors, default to a safe value.
        return '#'

Payload

javascript:alert('XSS')

Cite this entry

@misc{vaitp:cve202655665,
  title        = {{Grist < 1.7.15 XSS in hrefs allows an editor to escalate to owner access.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-55665},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-55665/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::