VCR.py: Arbitrary code execution via unsafe YAML deserialization of cassette files
Date: 2026-06
Affected: pip/vcrpy
Attack vector: deserialization
Category: vulnerability-disclosure
Severity: High
CWE: CWE-502
CVSS: 7.8 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

### Summary vcrpy deserializes YAML cassette files with PyYAML's object-constructing loader (`yaml.CLoader` / `yaml.Loader`) instead of the safe loader (`yaml.CSafeLoader` / `yaml.SafeLoader`). A cassette containing a `!!python/object/apply:` (or similar) tag therefore executes arbitrary Python code the moment the cassette is loaded — including through the normal `VCR().use_cassette()` path, before any HTTP interaction is replayed. This is **not** limited to environments lacking the libYAML C extension. `CLoader` uses the C parser but PyYAML's full Python *constructor*, so Python object tags execute under `CLoader` exactly as under the pure-Python `Loader`. Confirmed against vcrpy 8.1.1 + PyYAML 6.0.3 with `CLoader` active. ### Affected component - `vcr/serializers/yamlserializer.py` — `deserialize()` → `yaml.load(cassette_string, Loader=Loader)` where `Loader` is `CLoader`/`Loader`. Reached on **every** cassette load. - `vcr/migration.py` (~line 107) — `yaml.load(preprocess_yaml(...), Loader=Loader)`. A second sink reached when the migration tool is run on a `.yaml` file. `preprocess_yaml()` only strips three known legacy tags, so other tags still execute. Present in all releases inspected, 1.0.0 through 8.1.1. ### Proof of concept ```python import vcr, requests # Attacker-supplied cassette. The payload sits in an ignored top-level key # so the rest of the cassette stays valid; it fires during load. open("evil.yaml", "w").write("""interactions: - request: body: n


Mitigations:

Architectural layers (MAESTRO):