CVE-2026-9318: tablib Stored XSS via HTML Export Dataset Title

CVE-2026-9318: tablib Stored XSS via HTML Export Dataset Title

tablib, a Python tabular data library with over 147 million PyPI downloads, contains a stored cross-site scripting vulnerability in its HTML export functionality. Attackers can embed JavaScript payloads in dataset titles that execute when the exported HTML file is opened in a browser. Fixed in version 3.10.0.

4 min read778 words
Contents

TL;DR: tablib, a Python tabular data library with over 147 million PyPI downloads, contains a stored cross-site scripting vulnerability in its HTML export functionality. Attackers can embed JavaScript payloads in dataset titles that execute when the exported HTML file is opened in a browser. The fix is available in version 3.10.0.

What is tablib

tablib is a format-agnostic tabular dataset library for Python, originally created by Kenneth Reitz (the author of the requests library) and now maintained under the Jazzband collective. It lets developers import, manipulate, and export data across formats including XLSX, CSV, JSON, YAML, ODS, and HTML. The library has accumulated over 147.5 million downloads on PyPI, with roughly 4.5 million downloads per month and 58,500 downloads per day. It holds 4,700+ stars on GitHub.

Developers reach for tablib when they need to convert between spreadsheet formats or generate downloadable reports. A common workflow: import a CSV, clean the data, export it as an Excel file or an HTML table for a web application. That HTML export path is where this vulnerability lives.

What happened

The export_book method in tablib's _html.py format handler interpolates the dataset title directly into an <h3> HTML tag without any escaping or sanitization. When a dataset is created from an imported file (XLSX, ODS, XLS, YAML, or similar), the sheet or worksheet name becomes the dataset title. If that title contains a JavaScript payload like <script>alert(document.cookie)</script>, the payload passes through the import pipeline unchanged and lands in the HTML export as executable code.

The attack path is straightforward. An attacker crafts a spreadsheet file with a malicious worksheet name. They send that file to a target who uses tablib to process and re-export data. When the exported HTML output is viewed in a browser, the embedded script runs. This can lead to session hijacking, cookie theft, unauthorized administrative actions, or exfiltration of sensitive page content.

The root cause is in the _html.py format handler. The export_book function constructs an HTML document by string-interpolating the dataset title into a template. No call to html.escape() or any equivalent sanitization is applied to the title before insertion. The title flows from the imported file through the Dataset object's title attribute and directly into the HTML output.

Who is affected

Any application using tablib versions prior to 3.10.0 that processes user-supplied spreadsheet files and exports to HTML is vulnerable. The exposure surface depends on whether the HTML output reaches a browser context where JavaScript can execute. Applications that import files from untrusted sources (user uploads, third-party data feeds, email attachments processed programmatically) and render the HTML export in a web view are at highest risk.

Applications that only export to non-HTML formats (CSV, XLSX, JSON) are not affected by this specific vulnerability, since the payload does not execute outside a browser rendering context.

What to do

Upgrade to tablib 3.10.0 immediately:

pip install tablib==3.10.0

If you cannot upgrade right away, apply a temporary mitigation by sanitizing dataset titles before exporting to HTML:

import html
from tablib import Dataset

ds = Dataset()
ds.title = html.escape(ds.title)  # sanitize before export
ds.export('html')

Alternatively, avoid the HTML export format entirely until you can upgrade. Use CSV or XLSX outputs, which do not execute embedded scripts.

Audit your codebase for any code path that imports spreadsheet files from untrusted sources and passes them through tablib's HTML export. Even after upgrading, review whether your application's import pipeline needs additional input validation on worksheet and sheet names.

Why it matters

tablib's download numbers tell the story. With 147 million lifetime downloads and 4.5 million monthly downloads, the library is embedded in thousands of Python applications, from internal reporting tools to customer-facing data export features. The vulnerability is classified as CWE-79 (Improper Neutralization of Input During Web Page Generation), the same weakness class that accounts for a large share of real-world web application attacks.

The fix was released as part of tablib 3.10.0 through PR #668 on the Jazzband repository. The patch adds HTML escaping to the dataset title before interpolation into the export template.

What makes this worth attention is the trust chain. A user imports a spreadsheet, the library processes it, and the output is rendered in a browser. The gap between "data import tool" and "code execution surface" is invisible to most developers using the library. They see a data processing function, not an XSS vector. That mismatch is how stored XSS vulnerabilities persist in widely used libraries for years before someone traces the full path from file import to browser render.

References

Continue reading

All posts